Saltar al contenido

¿Cómo verificar el token de ID de base de fuego con PHP (JWT)?

Te doy la bienvenida a proyecto on line, en este lugar encontrarás la resolución de lo que necesitas.

Solución:

HS256 se usa solo si usa una contraseña para firmar el token. Firebase usa RS256 cuando emite un token, por lo tanto, necesita el público keys de la URL proporcionada, y debe configurar el algoritmo en RS256.

También tenga en cuenta que el token que obtiene en su aplicación no debe ser un array pero un string que tiene 3 partes: header, body, y signature. Cada parte está separada por un ., por lo que te da una simple string: header.body.signature

Lo que debe hacer para verificar que los tokens están descargando el público keys de la URL proporcionada con regularidad (consulte la Cache-Control encabezado para esa información) y guardarlo (el JSON) en un archivo, por lo que no tendrá que recuperarlo cada vez que necesite verificar el JWT. Luego puede leer el archivo y decodificar el JSON. El objeto decodificado se puede pasar al JWT::decode(...) función. Aquí hay una pequeña muestra:

$pkeys_raw = file_get_contents("cached_public_keys.json");
$pkeys = json_decode($pkeys_raw, true);

$decoded = JWT::decode($token, $pkeys, ["RS256"]);

Ahora el $decoded La variable contiene la carga útil del token. Una vez que tenga el objeto decodificado, aún debe verificarlo. De acuerdo con la guía sobre la verificación del token de identificación, debe verificar lo siguiente:

  • exp está en el futuro
  • iat Está en el pasado
  • iss: https://securetoken.google.com/
  • aud:
  • sub no está vacío

Entonces, por ejemplo, puede verificar iss así (donde FIREBASE_APP_ID es el ID de la aplicación de la consola de base de fuego):

$iss_is_valid = isset($decoded->iss) && $decoded->iss === "https://securetoken.google.com/" . FIREBASE_APP_ID;

Aquí hay una muestra completa para actualizar el keys y recuperarlos.

Descargo de responsabilidad: no lo he probado y esto es básicamente solo con fines informativos.

$keys_file = "securetoken.json"; // the file for the downloaded public keys
$cache_file = "pkeys.cache"; // this file contains the next time the system has to revalidate the keys

/**
 * Checks whether new keys should be downloaded, and retrieves them, if needed.
 */
function checkKeys()

    if (file_exists($cache_file)) 
        $fp = fopen($cache_file, "r+");

        if (flock($fp, LOCK_SH)) 
            $contents = fread($fp, filesize($cache_file));
            if ($contents > time()) 
                flock($fp, LOCK_UN);
             elseif (flock($fp, LOCK_EX))  // upgrading the lock to exclusive (write)
                // here we need to revalidate since another process could've got to the LOCK_EX part before this
                if (fread($fp, filesize($this->cache_file)) <= time()) 
                    $this->refreshKeys($fp);
                
                flock($fp, LOCK_UN);
             else 
                throw new RuntimeException('Cannot refresh keys: file lock upgrade error.');
            
         else 
            // you need to handle this by signaling error
            throw new RuntimeException('Cannot refresh keys: file lock error.');
        

        fclose($fp);
     else 
        refreshKeys();
    


/**
 * Downloads the public keys and writes them in a file. This also sets the new cache revalidation time.
 * @param null $fp the file pointer of the cache time file
 */
function refreshKeys($fp = null)

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]");
    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('/age:[ ]+?(d+)/i', $headers, $age_matches) === 1) 
        $age = $age_matches[1];

        if (preg_match('/cache-control:.+?max-age=(d+)/i', $headers, $max_age_matches) === 1) 
            $valid_for = $max_age_matches[1] - $age;
            ftruncate($fp, 0);
            fwrite($fp, "" . (time() + $valid_for));
            fflush($fp);
            // $fp will be closed outside, we don't have to

            $fp_keys = fopen($keys_file, "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).
 * @return null|string
 */
function getKeys()

    $fp = fopen($keys_file, "r");
    $keys = null;

    if (flock($fp, LOCK_SH)) 
        $keys = fread($fp, filesize($keys_file));
        flock($fp, LOCK_UN);
    

    fclose($fp);

    return $keys;

Lo mejor sería programar un cronjob para llamar checkKeys() siempre que sea necesario, pero no sé si su proveedor lo permite. En lugar de eso, puede hacer esto para cada solicitud:

checkKeys();
$pkeys_raw = getKeys(); // check if $raw_keys is not null before using it!

Ejemplo práctico de respuesta aceptada. diferencias notables:

  • Probado y funcionando

  • funciona en entornos que no son de clase

  • Más código que muestra cómo usarlo para Firebase (simple, de una sola línea para enviar el código para verificación)

  • UnexpectedValueException cubre todo tipo de errores que puede ver (como caducados / no válidos keys)

  • Bien comentado y fácil de seguir

  • devuelve un array de datos VERIFICADOS del Firebase Token (puede usar estos datos de forma segura para cualquier cosa que necesite)

Se trata básicamente de una versión PHP desglosada y fácil de leer / entender de https://firebase.google.com/docs/auth/admin/verify-id-tokens

NOTA: Puede usar las funciones getKeys (), refreshKeys (), checkKeys () para generar keys para usar en cualquier situación de API segura (imitando las características de la función ‘verify_firebase_token’ con la suya).

USAR:

$verified_array = verify_firebase_token()

EL CÓDIGO:

$keys_file = "securetoken.json"; // the file for the downloaded public keys
$cache_file = "pkeys.cache"; // this file contains the next time the system has to revalidate the keys
//////////  MUST REPLACE  with your own!
$fbProjectId = ;

/////// FROM THIS POINT, YOU CAN COPY/PASTE - NO CHANGES REQUIRED
///  (though read through for various comments!)
function verify_firebase_token($token = '')

    global $fbProjectId;
    $return = array();
    $userId = $deviceId = "";
    checkKeys();
    $pkeys_raw = getKeys();
    if (!empty($pkeys_raw)) 
        $pkeys = json_decode($pkeys_raw, true);
        try 
            $decoded = FirebaseJWTJWT::decode($token, $pkeys, ["RS256"]);
            if (!empty($_GET['debug'])) 
                echo "
BOTTOM LINE - the decoded data
"; print_r($decoded); echo "
"; if (!empty($decoded)) // do all the verifications Firebase says to do as per 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 your Firebase project ID $aud = $decoded->aud == $fbProjectId; // iss must be "https://securetoken.google.com/" $iss = $decoded->iss == "https://securetoken.google.com/$fbProjectId"; // sub must be non-empty and is the UID of the user or device $sub = $decoded->sub; if ($exp && $iat && $aud && $iss && !empty($sub)) // we have a confirmed Firebase user! // build an array with data we need for further processing $return['UID'] = $sub; $return['email'] = $decoded->email; $return['email_verified'] = $decoded->email_verified; $return['name'] = $decoded->name; $return['picture'] = $decoded->photo; else if (!empty($_GET['debug'])) echo "NOT ALL THE THINGS WERE TRUE!
"; echo "exp is $exp
ist is $iat
aud is $aud
iss is $iss
sub is $sub
"; /////// DO FURTHER PROCESSING IF YOU NEED TO // (if $sub is false you may want to still return the data or even enter the verified user into the database at this point.) catch (UnexpectedValueException $unexpectedValueException) $return['error'] = $unexpectedValueException->getMessage(); if (!empty($_GET['debug'])) echo "
ERROR! " . $unexpectedValueException->getMessage() . "
"; return $return; /** * Checks whether new keys should be downloaded, and retrieves them, if needed. */ function checkKeys() global $cache_file; if (file_exists($cache_file)) $fp = fopen($cache_file, "r+"); if (flock($fp, LOCK_SH)) $contents = fread($fp, filesize($cache_file)); if ($contents > time()) flock($fp, LOCK_UN); elseif (flock($fp, LOCK_EX)) // upgrading the lock to exclusive (write) // here we need to revalidate since another process could've got to the LOCK_EX part before this if (fread($fp, filesize($cache_file)) <= time()) refreshKeys($fp); flock($fp, LOCK_UN); else throw new RuntimeException('Cannot refresh keys: file lock upgrade error.'); else // you need to handle this by signaling error throw new RuntimeException('Cannot refresh keys: file lock error.'); fclose($fp); else refreshKeys(); /** * Downloads the public keys and writes them in a file. This also sets the new cache revalidation time. * @param null $fp the file pointer of the cache time file */ function refreshKeys($fp = null) global $keys_file; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]"); 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('/age:[ ]+?(d+)/i', $headers, $age_matches) === 1) $age = $age_matches[1]; if (preg_match('/cache-control:.+?max-age=(d+)/i', $headers, $max_age_matches) === 1) $valid_for = $max_age_matches[1] - $age; $fp = fopen($keys_file, "w"); ftruncate($fp, 0); fwrite($fp, "" . (time() + $valid_for)); fflush($fp); // $fp will be closed outside, we don't have to $fp_keys = fopen($keys_file, "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). * @return null|string */ function getKeys() global $keys_file; $fp = fopen($keys_file, "r"); $keys = null; if (flock($fp, LOCK_SH)) $keys = fread($fp, filesize($keys_file)); flock($fp, LOCK_UN); fclose($fp); return $keys;

En lugar de hacerlo todo manualmente, puede echar un vistazo a esta biblioteca:
Firebase Tokens o incluso Firebase Admin SDK para PHP. El almacenamiento en caché, etc. ya está implementado, solo eche un vistazo a los documentos.

Básicamente, simplemente haría lo siguiente con la biblioteca de tokens de Firebase:

use FirebaseAuthTokenHttpKeyStore;
use FirebaseAuthTokenVerifier;
use SymfonyComponentCacheSimpleFilesystemCache;

$cache = new FilesystemCache();
$keyStore = new HttpKeyStore(null, $cache);
$verifier = new Verifier($projectId, $keyStore);

    try 
        $verifiedIdToken = $verifier->verifyIdToken($idToken);

        // "If all the above verifications are successful, you can use the subject 
        // (sub) of the ID token as the uid of the corresponding user or device. (see https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library)
        echo $verifiedIdToken->getClaim('sub'); // "a-uid"
     catch (FirebaseAuthTokenExceptionExpiredToken $e) 
        echo $e->getMessage();
     catch (FirebaseAuthTokenExceptionIssuedInTheFuture $e) 
        echo $e->getMessage();
     catch (FirebaseAuthTokenExceptionInvalidToken $e) 
        echo $e->getMessage();
    

Te mostramos reseñas y calificaciones

Recuerda algo, que tienes la capacidad de valorar este escrito si diste con el hallazgo.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *