Files
Ontime/backendpanel/application/helpers/firebase_auth_helper.php
T
2026-03-11 15:29:37 +07:00

238 lines
8.1 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
defined('BASEPATH') or exit('No direct script access allowed');
/**
* Firebase Auth helper verify Firebase ID tokens (JWT) for API auth.
* Complies with Firebase Auth process: clients can send "Authorization: Bearer <id_token>"
* and the backend verifies the token with Google's public keys.
* Uses FCM_PROJECT_ID (or FIREBASE_PROJECT_ID) for aud/iss validation.
*/
if (!function_exists('firebase_auth_get_bearer_token')) {
/**
* Get Bearer token from Authorization header.
*
* @return string|null Token or null if missing/invalid
*/
function firebase_auth_get_bearer_token()
{
$auth = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : '';
if ($auth === '' && function_exists('apache_request_headers')) {
$headers = apache_request_headers();
$auth = isset($headers['Authorization']) ? $headers['Authorization'] : (isset($headers['authorization']) ? $headers['authorization'] : '');
}
if (preg_match('/^\s*Bearer\s+(\S+)\s*$/i', $auth, $m)) {
return $m[1];
}
return null;
}
}
if (!function_exists('firebase_auth_base64url_decode')) {
function firebase_auth_base64url_decode($data)
{
$pad = 4 - (strlen($data) % 4);
if ($pad !== 4) {
$data .= str_repeat('=', $pad);
}
return base64_decode(strtr($data, '-_', '+/'));
}
}
if (!function_exists('firebase_auth_fetch_google_jwks')) {
/**
* Fetch and cache Firebase Auth public keys (JWK set).
*
* @return array|null JWK set array or null on failure
*/
function firebase_auth_fetch_google_jwks()
{
$cache_key = 'firebase_auth_jwks';
$cache_ttl = 3600;
if (function_exists('get_instance')) {
$ci = &get_instance();
if (isset($ci->cache) && method_exists($ci->cache, 'get')) {
$cached = $ci->cache->get($cache_key);
if ($cached !== false) {
return $cached;
}
}
}
$url = 'https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com';
$ctx = stream_context_create(array('http' => array('timeout' => 10)));
$raw = @file_get_contents($url, false, $ctx);
if ($raw === false) {
log_message('error', 'Firebase Auth: Failed to fetch JWKs');
return null;
}
$jwks = json_decode($raw, true);
if (empty($jwks['keys'])) {
log_message('error', 'Firebase Auth: Invalid JWKs response');
return null;
}
if (function_exists('get_instance')) {
$ci = &get_instance();
if (isset($ci->cache) && method_exists($ci->cache, 'save')) {
$ci->cache->save($cache_key, $jwks, $cache_ttl);
}
}
return $jwks;
}
}
if (!function_exists('firebase_auth_jwk_to_pem')) {
/**
* Convert JWK (RSA n,e) to PEM for openssl.
*
* @param array $jwk JWK with kty RSA, n, e
* @return string|false PEM or false
*/
function firebase_auth_jwk_to_pem($jwk)
{
if (empty($jwk['n']) || empty($jwk['e']) || (isset($jwk['kty']) && $jwk['kty'] !== 'RSA')) {
return false;
}
$n = firebase_auth_base64url_decode($jwk['n']);
$e = firebase_auth_base64url_decode($jwk['e']);
if ($n === false || $e === false) {
return false;
}
$rsa = array(
'n' => $n,
'e' => $e,
);
$der = firebase_auth_build_rsa_der($rsa);
if ($der === false) {
return false;
}
$pem = "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($der), 64, "\n") . "-----END PUBLIC KEY-----";
return $pem;
}
}
if (!function_exists('firebase_auth_build_rsa_der')) {
function firebase_auth_build_rsa_der($rsa)
{
$n = $rsa['n'];
$e = $rsa['e'];
$n_len = strlen($n);
$e_len = strlen($e);
if ($n[0] & "\x80") {
$n_len++;
}
if ($e[0] & "\x80") {
$e_len++;
}
$seq = "\x02" . chr($e_len) . ($e[0] & "\x80" ? "\0" : '') . $e
. "\x02" . chr($n_len) . ($n[0] & "\x80" ? "\0" : '') . $n;
$bit_string = "\x03" . chr(strlen($seq) + 1) . "\x00" . $seq;
$oid = "\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00";
$inner = "\x30" . firebase_auth_der_len(strlen($bit_string) + strlen($oid)) . $bit_string . $oid;
$outer = "\x30" . firebase_auth_der_len(strlen($inner)) . $inner;
return $outer;
}
}
if (!function_exists('firebase_auth_der_len')) {
function firebase_auth_der_len($len)
{
if ($len < 128) {
return chr($len);
}
$buf = '';
while ($len > 0) {
$buf = chr($len & 0xff) . $buf;
$len >>= 8;
}
return chr(0x80 | strlen($buf)) . $buf;
}
}
if (!function_exists('firebase_auth_verify_id_token')) {
/**
* Verify a Firebase ID token (JWT) and return decoded payload.
*
* @param string $id_token The JWT string
* @param string|null $project_id Firebase project ID (default: FCM_PROJECT_ID or FIREBASE_PROJECT_ID constant)
* @return array|null Payload (uid, email, etc.) or null if invalid
*/
function firebase_auth_verify_id_token($id_token, $project_id = null)
{
if (!is_string($id_token) || $id_token === '') {
return null;
}
$parts = explode('.', $id_token);
if (count($parts) !== 3) {
return null;
}
$header = json_decode(firebase_auth_base64url_decode($parts[0]), true);
$payload = json_decode(firebase_auth_base64url_decode($parts[1]), true);
if (!$header || !$payload) {
log_message('debug', 'Firebase Auth: Invalid JWT decode');
return null;
}
if ($project_id === null || $project_id === '') {
if (defined('FCM_PROJECT_ID') && FCM_PROJECT_ID !== '') {
$project_id = FCM_PROJECT_ID;
} elseif (defined('FIREBASE_PROJECT_ID') && FIREBASE_PROJECT_ID !== '') {
$project_id = FIREBASE_PROJECT_ID;
} else {
$project_id = '';
}
}
if ($project_id === '') {
log_message('error', 'Firebase Auth: FCM_PROJECT_ID not set');
return null;
}
$expected_iss = 'https://securetoken.google.com/' . $project_id;
if (empty($payload['iss']) || $payload['iss'] !== $expected_iss) {
log_message('debug', 'Firebase Auth: iss mismatch');
return null;
}
if (empty($payload['aud']) || $payload['aud'] !== $project_id) {
log_message('debug', 'Firebase Auth: aud mismatch');
return null;
}
if (empty($payload['exp']) || (int) $payload['exp'] < time()) {
log_message('debug', 'Firebase Auth: token expired');
return null;
}
$jwks = firebase_auth_fetch_google_jwks();
if (!$jwks) {
return null;
}
$kid = isset($header['kid']) ? $header['kid'] : null;
$key = null;
foreach ($jwks['keys'] as $k) {
if (isset($k['kid']) && $k['kid'] === $kid) {
$key = $k;
break;
}
}
if (!$key) {
$key = $jwks['keys'][0];
}
$pem = firebase_auth_jwk_to_pem($key);
if (!$pem) {
return null;
}
$pub = openssl_pkey_get_public($pem);
if ($pub === false) {
return null;
}
$payload_to_verify = $parts[0] . '.' . $parts[1];
$sig_raw = firebase_auth_base64url_decode($parts[2]);
if ($sig_raw === false) {
openssl_free_key($pub);
return null;
}
$ok = openssl_verify($payload_to_verify, $sig_raw, $pub, OPENSSL_ALGO_SHA256);
openssl_free_key($pub);
if ($ok !== 1) {
log_message('debug', 'Firebase Auth: signature verification failed');
return null;
}
return $payload;
}
}