dbburgess dbburgess - 2 months ago 57
PHP Question

Firebase REST access using PHP with Authentication

I'm trying to access Firebase from a server using PHP, the Google Auth library, and a wrapper for Firebase's REST...This works great to accomplish that:

use Firebase\JWT\JWT;
use Google\Auth\Credentials\ServiceAccountCredentials;
use Google\Auth\HttpHandler\HttpHandlerFactory;
use GuzzleHttp\Client;

$email = 'account@email.com';
$key = 'private_key_goes_here';

$scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/firebase.database',
];

$creds = [
'client_email' => $email,
'private_key' => $key,
];

$serviceAccount = new ServiceAccountCredentials($scopes, $creds);
$handler = HttpHandlerFactory::build(new Client());
$token = $serviceAccount->fetchAuthToken($handler);

$firebase = new \Firebase\FirebaseLib($url, $token);
$value = $firebase->get('test/hello');
# $value now stores "world"


However, this requires the security rules in Firebase to be universal read / write, which I do not want. If I update my security rules to this:

{
"rules": {
"test": {
".read": "auth != null"
}
}
}


The result in
$value
becomes
{"error": "Permission denied"}
. I've searched extensively, and tried numerous permutations and possible solutions, with no conclusive results.

I've used this code to provide JWT tokens to end clients, which can successfully use them and leverage the security rules with no problem. I initially tried that same approach for the server, but was unsuccessful. I opted to try to combine the two methods:

# Snipping code that didn't change...
$serviceAccount = new ServiceAccountCredentials($scopes, $creds);
$handler = HttpHandlerFactory::build(new Client());

$payload = [
'iss' => $email,
'sub' => $email,
'aud' => 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
'iat' => time(),
'exp' => time() + 60 * 60,
'uid' => '123',
'claims' => [
'uid' => '123',
],
];

$payload = $serviceAccount->updateMetadata($payload);
$token = JWT::encode($payload, $key, 'RS256');

$firebase = new \Firebase\FirebaseLib($url, $token);
$value = $firebase->get('test/hello');


This seems to get close, but
$value
now contains
{"error": "Missing claim 'kid' in auth header."}
. To resolve this, I modified the encode call:

$token = JWT::encode($payload, $key, 'RS256', 'key_id_goes_here');


Which results in a slightly different error:
Invalid claim 'kid' in auth header.
, suggesting I'm on the right track...But not quite there. Using the JWT token directly yields the exact same results. Any ideas what I'm doing wrong? The email, private key, and key id all came directly from the
json
credential file provided when I created the service account.

I've looked at dozens of pages of documentation and posts, here are the ones that were the most helpful:



Cross posted to the Firebase Google Group.

Answer

Ultimately, the solution I ended up using was to switch PHP libraries. I initially dismissed this library because it is moving toward PHP7 only support, which I'm not ready to migrate to yet, but the current version (1.1) worked fine:

use Kreait\Firebase\Configuration;
use Kreait\Firebase\Firebase;

$clientId = '1234567890';
$email = 'account@email.com';
$key = 'private_key_goes_here';
$url = 'https://example.firebaseio.com';

$fbConfig = new Configuration();
$fbConfig->setAuthConfigFile([
    'type' => 'service_account',
    'client_id' => $clientId,
    'client_email' => $email,
    'private_key' => $key,
]);

$fb = new Firebase($url, $fbConfig);
$value = $fb->get('test/hello');
# $value now stores "world"
Comments