Since Firebase has migrated from legacy FCM APIs to HTTP v1, client-side apps (e.g., Swift apps) cannot directly generate access tokens. Instead, tokens must be created using a server-side service account.
However, I managed to generate an access token directly in Swift using CryptoKit
and SwiftJWT
and successfully send push notifications via Firebase Cloud Messaging (FCM).
Download your serviceAccount.json
file from Firebase.
Add it to your Xcode project.
Below is my implementation to generate an access token and send an FCM notification:
//
// Created by DRT on 14/02/20.
// Copyright © 2020 mk. All rights reserved.
//
import Foundation
import UIKit
import CryptoKit
import SwiftJWT
class PushNotificationSender {
func sendPushNotification(FCMtoken: String, title: String, body: String,type: String,targetFUserId: String,firebasePushId: String) {
self.getAccessToken { result in
switch result {
case .success(let token):
print(token)
self.sendFCMMessage(token: token,
fcmToken: FCMtoken,
title: title,
body: body,
type: type,
targetFUserId: targetFUserId,
firebasePushId: firebasePushId)
case .failure(let failure):
print(failure)
}
}
}
// this func will send the notification to the fcm token device
func sendFCMMessage(token: String, fcmToken: String, title: String, body: String,type: String,targetFUserId: String,firebasePushId: String) {
let url = URL(string: "https://fcm.googleapis.com/v1/projects/gymstar-94659/messages:send")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"message": [
"token": fcmToken,
"notification": [
"title": title,
"body": body
],
"data": [
"badge" : "1",
"sound" : "default",
"notification_type": type,
"message": body,
"firebaseUserId": targetFUserId,
"firebasePushId": firebasePushId
]
]
]
let bodyData = try! JSONSerialization.data(withJSONObject: body, options: [])
request.httpBody = bodyData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
do {
if let jsonData = data {
if let jsonDataDict = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject] {
NSLog("Received data:\n\(jsonDataDict))")
}
}
} catch let err as NSError {
print(err.debugDescription)
}
}
task.resume()
}
// this func will return access token and if some error occurs, it returns error
func getAccessToken(completion: @escaping (Result<String, Error>) -> Void) {
guard let jsonPath = Bundle.main.path(forResource: "serviceAccount", ofType: "json") else {
completion(.failure(NSError(domain: "FileNotFound", code: 404, userInfo: nil)))
return
}
do {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: jsonPath))
let credentials = try JSONDecoder().decode(ServiceAccountCredentials.self, from: jsonData)
let iat = Date()
let exp = Date(timeIntervalSinceNow: 3600) // Token valid for 1 hour
// Create the JWT claims
let claims = JWTClaims(
iss: credentials.client_email,
scope: "https://www.googleapis.com/auth/cloud-platform", // Change this to your required scope
aud: "https://oauth2.googleapis.com/token",
iat: iat,
exp: exp
)
// Create a JWT signer using the private key
var jwt = JWT(claims: claims)
// Private key in PEM format, needs to be cleaned
let privateKey = credentials.private_key
.replacingOccurrences(of: "-----BEGIN PRIVATE KEY-----", with: "")
.replacingOccurrences(of: "-----END PRIVATE KEY-----", with: "")
.replacingOccurrences(of: "\n", with: "")
let jwtSigner = JWTSigner.rs256(privateKey: Data(base64Encoded: privateKey)!)
let signedJWT = try jwt.sign(using: jwtSigner)
// Send the signed JWT to Google's token endpoint
getGoogleAccessToken(jwt: signedJWT) { result in
switch result {
case .success(let accessToken):
completion(.success(accessToken))
case .failure(let error):
completion(.failure(error))
}
}
} catch {
completion(.failure(error))
}
}
// Send the JWT to Google's OAuth 2.0 token endpoint
func getGoogleAccessToken(jwt: String, completion: @escaping (Result<String, Error>) -> Void) {
let tokenURL = URL(string: "https://oauth2.googleapis.com/token")!
var request = URLRequest(url: tokenURL)
request.httpMethod = "POST"
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let bodyParams = [
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": jwt
]
request.httpBody = bodyParams
.compactMap { "\($0)=\($1)" }
.joined(separator: "&")
.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NSError(domain: "NoData", code: 500, userInfo: nil)))
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let accessToken = json["access_token"] as? String {
completion(.success(accessToken))
} else {
completion(.failure(NSError(domain: "InvalidResponse", code: 500, userInfo: nil)))
}
} catch {
completion(.failure(error))
}
}
task.resume()
}
}
struct ServiceAccountCredentials: Codable {
let client_email: String
let private_key: String
}
struct JWTClaims: Claims {
let iss: String
let scope: String
let aud: String
let iat: Date
let exp: Date
}
extension Data {
func base64URLEncodedString() -> String {
return self.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
}
Generate an Access Token:
Read Firebase service account JSON file.
Create a JWT using SwiftJWT
.
Sign it with your Firebase private key.
Send it to Google's OAuth 2.0 token endpoint to get an access token.
Send FCM Notification:
Use the access token in an Authorization
header.
Send a push notification to the target FCM token
via https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send
.
Replace "YOUR_PROJECT_ID"
in the sendFCMMessage
method.
The serviceAccount.json
should be added to your project.
The private key must be properly formatted before using it for signing.