This commit is contained in:
2026-03-03 16:30:57 +07:00
parent a13304e40e
commit c253e1a370
7569 changed files with 1324841 additions and 0 deletions
@@ -0,0 +1,237 @@
<?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;
}
}