Skip to content

Instantly share code, notes, and snippets.

@rochaeterno
Created July 4, 2023 16:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rochaeterno/cfb1bb0c8987201c1e4b78d885f607f0 to your computer and use it in GitHub Desktop.
Save rochaeterno/cfb1bb0c8987201c1e4b78d885f607f0 to your computer and use it in GitHub Desktop.
Some changes to avosalmon/FirebaseToken.php
<?php
namespace App\Services\Authentication;
use UnexpectedValueException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class FirebaseToken extends JWT
{
/**
* The list of allowed signing algorithms used in the JWT.
*/
const ALLOWED_ALGOS = 'RS256';
/**
* The public key used for verifying that the token is signed by the right private key.
*
* @var string
*/
const PUBLIC_KEY_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com';
/**
* The cache key for Firebase JWT public keys.
*
* @var string
*/
const CACHE_KEY = 'FIREBASE_JWT_PUBLIC_KEYS';
/**
* Firebase ID token.
*
* @var string
*/
private string $token;
/**
* @param string $token
*/
public function __construct(string $token)
{
$this->token = $token;
}
/**
* Verify the ID token and return the decoded payload.
*
* @param string $projectId
* @return object
* @throws UnexpectedValueException|Exception
*/
public function verify(string $projectId): object
{
try {
$keys = $this->getPublicKeys();
$payload = JWT::decode($this->token, new Key($keys[self::getTokenKid()], self::ALLOWED_ALGOS));
$this->validatePayload($payload, $projectId);
return $payload;
} catch (\Throwable $th) {
return $th;
}
}
private function getTokenKid(): string
{
$tks = \explode('.', $this->token);
$headerRaw = static::urlsafeB64Decode($tks[0]);
$header = static::jsonDecode($headerRaw);
return $header->kid;
}
/**
* Fetch JWT public keys.
*/
private function getPublicKeys(): array
{
if (Cache::has(self::CACHE_KEY)) {
return Cache::get(self::CACHE_KEY);
}
$response = Http::get(self::PUBLIC_KEY_URL);
if (!$response->successful()) {
throw new \Exception('Failed to fetch JWT public keys.');
}
$publicKeys = $response->json();
$cacheControl = $response->header('Cache-Control');
$maxAge = Str::of($cacheControl)->match('/max-age=(\d+)/');
Cache::put(self::CACHE_KEY, $publicKeys, now()->addSeconds($maxAge));
return $publicKeys;
}
/**
* Validate decoded payload.
*
* @param object $payload
* @param string $projectId
* @return void
* @throws UnexpectedValueException
*
* @see https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library
*/
private function validatePayload(object $payload, string $projectId): void
{
if ($payload->aud !== $projectId) {
throw new UnexpectedValueException("Invalid audience: {$payload->aud}");
}
if ($payload->iss !== "https://securetoken.google.com/{$projectId}") {
throw new UnexpectedValueException("Invalid issuer: {$payload->iss}");
}
// `sub` corresponds to the `uid` of the Firebase user.
if (empty($payload->sub)) {
throw new UnexpectedValueException('Payload subject is empty.');
}
}
}
@skfz
Copy link

skfz commented Nov 29, 2023

You have not provided the methods urlsafeB64Decode & jsonDecode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment