Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating OAUTH token for firebase cloud messaging PHP

I have a PHP page which i used to send notifications to the users of a mobile app i developed , this page works fine until last month , then it gave me this error

{"multicast_id":5174063503598899354,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}

i tried to generate OAUTH token using the documentation in this link https://firebase.google.com/docs/cloud-messaging/auth-server#node.js but it needs NODE.JS server and my server doesn't support Node.Js , i tried to use the Firebase Admin SDK but can't find anything. Here is the PHP code of the page

<?php

//Includes the file that contains your project's unique server key from the Firebase Console.
require_once("serverKeyInfo.php");

//Sets the serverKey variable to the googleServerKey variable in the serverKeyInfo.php script.
$serverKey = $googleServerKey;

//URL that we will send our message to for it to be processed by Firebase.
    $url = "https://fcm.googleapis.com/fcm/send";

//Recipient of the message. This can be a device token (to send to an individual device) 
//or a topic (to be sent to all devices subscribed to the specified topic).
$recipient = $_POST['rec'];

//Structure of our notification that will be displayed on the user's screen if the app is in the background.
$notification =array(
    'title'   => $_POST['title'],
    'body'   => $_POST['body'],
    'sound' => 'default'
);

//Structure of the data that will be sent with the message but not visible to the user.
//We can however use Unity to access this data.
$dataPayload =array( 

    "powerLevel" => "9001",
    "dataString" => "This is some string data"
);

//Full structure of message inculding target device(s), notification, and data.
$fields =array(

    'to'  => $recipient,
    'notification' => $notification,
    'data' => $dataPayload
);

//Set the appropriate headers
$headers = array(

'Authorization: key=' . $serverKey,
'Content-Type: application/json'
);
//Send the message using cURL.
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, $url);
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );

//Result is printed to screen.
echo $result;
?>

Can anyone send me an example o how can i do this ( I am beginner in PHP ) Thanks in advance

*Update : Also i tried to change the $url in the code to

$url = "https://fcm.googleapis.com/v1/projects/notifications-9ccdd/messages:send";

but it gives me this error

"error": { "code": 401, "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.", "status": "UNAUTHENTICATED" } Blockquote

like image 830
Mohamed Avatar asked Dec 06 '25 05:12

Mohamed


2 Answers

For anyone looking for an answer without the use of external libraries or packages. This code is based on this documentation from google: https://developers.google.com/identity/protocols/oauth2/service-account#httprest

Step 1: In your http://console.firebase.google.com under project->settings->service accounts locate the Firebase service account. Generate a new private key and download the json file.

// This function is needed, because php doesn't have support for base64UrlEncoded strings
function base64UrlEncode($text)
{
    return str_replace(
        ['+', '/', '='],
        ['-', '_', ''],
        base64_encode($text)
    );
}

// Read service account details
$authConfigString = file_get_contents("path_to_the_json_file_you_just_downloaded.json");

// Parse service account details
$authConfig = json_decode($authConfigString);

// Read private key from service account details
$secret = openssl_get_privatekey($authConfig->private_key);

// Create the token header
$header = json_encode([
    'typ' => 'JWT',
    'alg' => 'RS256'
]);

// Get seconds since 1 January 1970
$time = time();

// Allow 1 minute time deviation between client en server (not sure if this is necessary)
$start = $time - 60;
$end = $start + 3600;

// Create payload
$payload = json_encode([
    "iss" => $authConfig->client_email,
    "scope" => "https://www.googleapis.com/auth/firebase.messaging",
    "aud" => "https://oauth2.googleapis.com/token",
    "exp" => $end,
    "iat" => $start
]);

// Encode Header
$base64UrlHeader = base64UrlEncode($header);

// Encode Payload
$base64UrlPayload = base64UrlEncode($payload);

// Create Signature Hash
$result = openssl_sign($base64UrlHeader . "." . $base64UrlPayload, $signature, $secret, OPENSSL_ALGO_SHA256);

// Encode Signature to Base64Url String
$base64UrlSignature = base64UrlEncode($signature);

// Create JWT
$jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;

//-----Request token, with an http post request------
$options = array('http' => array(
    'method'  => 'POST',
    'content' => 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion='.$jwt,
    'header'  => "Content-Type: application/x-www-form-urlencoded"
));
$context  = stream_context_create($options);
$responseText = file_get_contents("https://oauth2.googleapis.com/token", false, $context);

$response = json_decode($responseText);

The response has 3 fields:

  • access_token
  • expires_in: the number of seconds before the token expires, eg 3599 (3600s = 1h)
  • token_type: "Bearer"

Use the access_token in the calls to firebase to authenticate your call. You should also store this access_token together with the expires_in, and request a new token when it almost expires. The maximum lifetime of a token is 1 hour.

like image 162
Peter Bruins Avatar answered Dec 07 '25 20:12

Peter Bruins


****** 2023 UPDATE ****** FCM http Legacy has been officially deprecated, and will be removed entirely by June 2024. Anybody using the http Legacy version should migrate to V1 instead. The example below uses V1 and not the http Legacy version :)


For anybody still looking for an answer to this (2021), in order to send a push message via your own PHP system to the Firebase messaging system you need an access token from Google Credentials. Here's how to do it - please note I've only done this in PHP Laravel, not raw PHP. But you should be able to locate the vanilla PHP solution to this by modifying the steps to suit (Also same with Code Igniter and other PHP libraries)

  1. In your http://console.firebase.google.com under project->settings->service accounts locate the Firebase service account. Generate a new private key and download the json file. Store it on your server somewhere users can't get to it.

  2. Install Google API Client. For Laravel this is:

      composer require google/apiclient --with-all-dependencies
    
  3. Open composer.json, and add to the autoload array. For Laravel this is:

     "classmap": [
         "vendor/google/apiclient/src/Google"
     ],
    
  4. Create a new Service class (or create a new Class if vanilla PHP), and add the following method for retrieving an access token:

    private function getGoogleAccessToken(){
    
         $credentialsFilePath = 'the-folder-and-filename-of-your-downloaded-service-account-file.json'; //replace this with your actual path and file name
         $client = new \Google_Client();
         $client->setAuthConfig($credentialsFilePath);
         $client->addScope('https://www.googleapis.com/auth/firebase.messaging');
         $client->refreshTokenWithAssertion();
         $token = $client->getAccessToken();
         return $token['access_token'];
    }
    
  5. Now create a method to send all your message info to Firebase via CURL:

    public function sendMessage(){
    
     $apiurl = 'https://fcm.googleapis.com/v1/projects/your-project-id/messages:send';   //replace "your-project-id" with...your project ID
    
     $headers = [
             'Authorization: Bearer ' . $this->getGoogleAccessToken(),
             'Content-Type: application/json'
     ];
    
     $notification_tray = [
             'title'             => "Some title",
             'body'              => "Some content",
         ];
    
     $in_app_module = [
             "title"          => "Some data title (optional)",
             "body"           => "Some data body (optional)",
         ];
     //The $in_app_module array above can be empty - I use this to send variables in to my app when it is opened, so the user sees a popup module with the message additional to the generic task tray notification.
    
      $message = [
            'message' => [
                 'notification'     => $notification_tray,
                 'data'             => $in_app_module,
             ],
      ];
    
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $apiurl);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message));
    
      $result = curl_exec($ch);
    
      if ($result === FALSE) {
          //Failed
          die('Curl failed: ' . curl_error($ch));
      }
    
      curl_close($ch);
    
    }
    

Google recommends that you only use this method if you can't add the JSON file as an environmental variable on your server directly. I don't know why Google doesn't have better documentation on this subject for PHP, it seems to prefer node.js , Go, Java and C++.

like image 34
James Avatar answered Dec 07 '25 20:12

James



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!