I have a shared hosting plan which has only PHP(no Java, no node.js). I need to send firebase ID token from my android app and verify it by PHP-JWT.
I am following t
If anyone is still interested, @CFP Support's answer is quite good for servers using PHP 5.6, but it does have some bugs while trying to cache the expiration time of the current saved public keys. I've taken that code, and made the necessary corrections:
Requirements in composer.json
{
"require" : {
"firebase/php-jwt": "5.2.0"
}
}
Usage
$verified = verify_firebase_token();
?>
Functions
# the file for the downloaded public keys
$jwt['keys'] = 'jwt.publickeys.json';
# this file contains the next time the system has to revalidate the keys
$jwt['cache'] = 'jwt.publickeys.cache';
# project ID
$jwt['project_id'] = YOUR_FIREBASE_PROJECT_ID;
# verify token
function verify_firebase_token($token) {
global $jwt;
$return = array();
jwt_check_keys();
$keys_raw = jwt_get_keys();
if(!empty($keys_raw)) {
$keys = json_decode($keys_raw, true);
try {
$decoded = \Firebase\JWT\JWT::decode($token, $keys, ['RS256']);
if(!empty($decoded)) {
# follow best practices verification-wise
# https://firebase.google.com/docs/auth/admin/verify-id-tokens
# exp must be in the future
$exp = $decoded->exp > time();
# ist must be in the past
$iat = $decoded->iat < time();
# aud must be firebase project ID
$aud = $decoded->aud == $jwt['project_id'];
# iss must be https://securetoken.google.com/
$iss = $decoded->iss == 'https://securetoken.google.com/'.$jwt['project_id'];
# sub must be non-empty and is the UID of the user or device
$sub = $decoded->sub;
# check all items
if($exp && $iat && $aud && $iss && !empty($sub)) {
# confirmed firebase user
$return['user']['uid'] = $sub;
// $return['user']['email'] = $decoded->email;
// $return['user']['name'] = $decoded->name;
// $return['user']['picture'] = $decoded->picture;
// $return['all'] = $decoded;
} else {
}
}
} catch (\UnexpectedValueException $unexpectedValueException) {
$return['error'] = $unexpectedValueException->getMessage();
//$unexpectedValueException->getMessage()
}
}
return $return;
}
# checks whether new keys should be downloaded
# retrieves them if needed
function jwt_check_keys() {
global $jwt;
if(file_exists($jwt['cache'])) {
$fp_cache = fopen($jwt['cache'], 'r+');
if(flock($fp_cache, LOCK_SH)) {
$cachetime = fread($fp_cache, filesize($jwt['cache']));
if($cachetime > time()) {
# still valid - do nothing
flock($fp_cache, LOCK_UN);
} elseif(flock($fp_cache, LOCK_EX)) {
# expired - refresh public keys
jwt_refresh_keys();
flock($fp_cache, LOCK_UN);
} else {
throw new \RuntimeException('Cannot refresh keys: file lock upgrade error.');
}
} else {
throw new \RuntimeException('Cannot refresh keys: file lock error.');
}
fclose($fp_cache);
} else {
# refresh public keys
jwt_refresh_keys();
}
}
# downloads the public keys and writes them in a file
# sets the new cache revalidation time
function jwt_refresh_keys() {
global $jwt;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
$data = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = trim(substr($data, 0, $header_size));
$raw_keys = trim(substr($data, $header_size));
if(preg_match('/max-age=(\d+)/', $headers, $age_matches) === 1) {
# update new cache expiration timestamp
$fp_cache = fopen($jwt['cache'], 'w');
$age = $age_matches[1];
fwrite($fp_cache, ''.(time() + $age));
fflush($fp_cache);
# update public keys
$fp_keys = fopen($jwt['keys'], 'w');
if(flock($fp_keys, LOCK_EX)) {
fwrite($fp_keys, $raw_keys);
fflush($fp_keys);
flock($fp_keys, LOCK_UN);
}
fclose($fp_keys);
}
}
# retrieves the downloaded keys
# this should be called anytime you need the keys (i.e. for decoding / verification)
function jwt_get_keys() {
global $jwt;
$fp = fopen($jwt['keys'], 'r');
$keys = null;
if(flock($fp, LOCK_SH)) {
$keys = fread($fp, filesize($jwt['keys']));
flock($fp, LOCK_UN);
}
fclose($fp);
return $keys;
}
?>