Mohamed
Mohamed

Reputation: 171

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

Upvotes: 17

Views: 18660

Answers (5)

Peter B
Peter B

Reputation: 1581

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.

Upvotes: 36

Prosenjit
Prosenjit

Reputation: 171

For laravel run the following

sudo composer require google/apiclient

In the Model create a function called getBearerToken that stores the token for 55 min in cache. you can change the time, but in my case token lifetime was 1 hr.

use Google\Client as GoogleClient;
use Carbon\Carbon;

private function getBearerToken(){
    try{
        $cache_key  = md5('google-auth-cache');
        $token      = \Cache::get($cache_key); 
        if(empty($token)){
            $serviceAccountPath = 'path/to/google/service-account.json';                  
            $client = new GoogleClient();
            $client->setAuthConfig($serviceAccountPath);
            $client->addScope('https://www.googleapis.com/auth/firebase.messaging');
            $client->fetchAccessTokenWithAssertion();
            $token = $client->getAccessToken();
            $expiresAt  = Carbon::now()->addMinutes(55);
            \Cache::put($cache_key, $token, $expiresAt); 
        }
        $accesstoken = isset($token['access_token']) ? $token['access_token'] : '';

        return $accesstoken;
    }
    catch(\Exception $e){
        echo sprintf('Line : %s, Message : %s', $e->getLine(), $e->getMessage());
    }
}    

Now you can use this function for sending notification

$token = self::getBearerToken();
$headers = array("Authorization: Bearer $token", "Content-Type:application/json");

Upvotes: 0

Delmontee
Delmontee

Reputation: 2364

****** 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++.

Upvotes: 24

Lucas Martini
Lucas Martini

Reputation: 183

First you need JWT!

You can find here Link for JWT GitHub

Creating access token:

use Firebase\JWT\JWT;
use Firebase\JWT\Key;


require 'vendors/php-jwt-main/src/JWT.php';
require 'vendors/php-jwt-main/src/Key.php';

function returnFireBaseTkn(){
    $jsonInfo = json_decode(file_get_contents("YOUR JSON FILE WITH CREDENTIAL HERE"), true);

    $now_seconds = time();
    
    $privateKey = $jsonInfo['private_key'];
    
    $payload = [
        'iss' => $jsonInfo['client_email'],
        'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
        'aud' => $jsonInfo['token_uri'],
        //Token to be expired after 1 hour
        'exp' => $now_seconds + (60 * 60),
        'iat' => $now_seconds
    ];
    
    $jwt = JWT::encode($payload, $privateKey, 'RS256');
    
    // create curl resource
    $ch = curl_init();
    
    // set post fields
    $post = [
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion' => $jwt
    ];
    
    $ch = curl_init($jsonInfo['token_uri']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    
    // execute!
    $response = curl_exec($ch);
    
    // close the connection, release resources used
    curl_close($ch);
    
    // do anything you want with your response
    $jsonObj = json_decode($response, true);

    return $jsonObj['access_token'];
}

Sending notification:

function sendNotif($info){

    $apiurl = 'https://fcm.googleapis.com/v1/projects/your-project-id/messages:send';

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $apiurl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($info));

    $headers = array(
    //It's normal to find .............................. on the access token!!
        'Authorization: Bearer ' . 'access_token_ID_here',
        'Content-Type: application/json'
    );

    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    $result = curl_exec($ch);

    if (curl_errno($ch)) {
        echo 'Error:' . curl_error($ch);
    }
    curl_close($ch);
}

// Notification need to be in this format for single user!
$notification = array(
    "message" => array(
        "token" => "USER_ID",
        "notification" => array(
            "title" => "teste",
            "body" => "123"
        )
    )
);

sendNotif($notification);

If you want to use for a group of users:

$notification = array(
    "message" => array(
        "topic" => "GROUP_ID",
        "notification" => array(
            "title" => "teste",
            "body" => "123"
        )
    )
);

Upvotes: 1

Kjut
Kjut

Reputation: 116

@james have provided a very useful and clear answer. You can follow his guide or For pure php Simply

  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. Upload to a location on your server
  2. Install Google API Client: composer require google/apiclient
  3. In your PHP file
require "./vendor/autoload.php";
$client= new Google_Client();
$client->setAuthConfig("path-downloaded-json-file.json");
$client->addScope('https://www.googleapis.com/auth/firebase.messaging');
$client->refreshTokenWithAssertion();
$token = $client->getAccessToken();
echo $token['access_token'];

Upvotes: 8

Related Questions