5

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

Mohamed
  • 51
  • 1
  • 3

4 Answers4

10

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

Delmontee
  • 1,898
  • 2
  • 26
  • 44
  • I tried this exact method suggested here but my access tokens contains a lot of '....' please help me out – Yeo Bryan May 17 '22 at 10:02
  • @YeoBryan I can't tell exactly but I would assume something hasn't been set up correctly in firebase, or the private key is wrong, or it can't find your json file. The php code should be working. – Delmontee May 17 '22 at 13:10
  • if possible could you please take a look at the question I posted and offer any potential solution? I have been stuck for hours not sure what I am doing wrongly https://stackoverflow.com/questions/72270918/why-does-my-oauth-access-token-obtained-using-google-api-client-contains-plenty?noredirect=1#comment127687873_72270918 – Yeo Bryan May 17 '22 at 15:00
  • I have the service account json file downloaded from firebase and placed into my PHP project. I think its able to find the file because its able to return results to me (https://stackoverflow.com/questions/72270918/why-does-my-oauth-access-token-obtained-using-google-api-client-contains-plenty?noredirect=1#comment127687873_72270918) – Yeo Bryan May 17 '22 at 15:01
5

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
$start = $time - 60;
$end = $time + 3600;

$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------
$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, and token_type. 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.

Peter Bruins
  • 807
  • 9
  • 25
  • Superb answer! it works like a charm. I have a few question though, the $result variable seems to be not used or was it supposedly to be used for $base64UrlSignature? Also is it ok to create 10-20 access token in a single minute or should i just save it somewhere to be retrieved and just generate a new one after it expires? – ShinMyth Jun 27 '23 at 03:37
  • `$result` can be used to check if the signing was successful. You should store the access token somewhere, and only request a new one when the old one almost expires, according to Google. It's probably rate limited. – Peter Bruins Jun 28 '23 at 14:08
  • Still Work like a charm... :) – Muhammad Hassan Aug 08 '23 at 20:06
  • Much simpler than every other implementation I found, all of which require a bunch of dependencies. For whatever reason the POST with `file_get_contents` didn't work for me, but it's fine after switching it to CURL. – Vatev Sep 01 '23 at 13:14
3

@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'];
Kjut
  • 56
  • 4
  • 1
    I tried this exact method suggested here but my access tokens contains a lot of '....' please help me out – Yeo Bryan May 17 '22 at 10:02
0

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"
        )
    )
);
Lucas Martini
  • 131
  • 3
  • 20