79505227

Date: 2025-03-13 01:49:57
Score: 0.5
Natty:
Report link

How to Send Firebase Cloud Messaging (FCM) Notifications from Swift using HTTP v1?

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).

Solution

Step 1: Setup Service Account

Step 2: Implement Push Notification Sender in Swift

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: "")
    }
}

How It Works

  1. 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.

  2. 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.

Key Notes

Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • Starts with a question (0.5): How to
  • Low reputation (1):
Posted by: SYED M ABDUL REHMAN