如何使用PHP(JWT)验证Firebase ID令牌? [英] How to verify firebase ID token with PHP(JWT)?

查看:403
本文介绍了如何使用PHP(JWT)验证Firebase ID令牌?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个只有PHP(没有Java,没有node.js)的共享托管计划。我需要从我的android应用程序发送firebase ID令牌,并通过PHP-JWT验证。



我正在学习以下教程:验证Firebase ID令牌





如果您的后端使用的是没有官方Firebase Admin SDK的语言,您仍然可以验证ID令牌。首先,找到第三方JWT库然后,验证ID标记的头部,有效载荷和签名。



我发现这个库: Firebase-PHP-JWT 。在gitHub的例子中;我无法理解

$ key部分:

  $ key =example_key;`




$ b $ $ p $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ //example.org,
aud=>http://example.com,
iat=> 1356999524,
nbf=> 1357000000
);`

我的问题:


  1. 什么应该是 $ key 变量?

  2. 为什么& token 变量是一个数组?从移动应用程序发送的令牌是一个字符串。
  3. 如果有人可以发布PHP-JWT验证Firebase ID的完整示例,我将不胜感激。

编辑:

< GitHub示例显示了如何生成JWT代码(编码)以及如何对其进行解码。在我的情况下,我只需要解码由firebase编码的jwt。所以,我只需要使用这个代码:

$ $ p $ $ c $解码= JWT :: decode($ jwt,$ key,array( 'HS256'));

在这段代码中, $ jwt 是Firebase ID标记。对于 $ key 变量文档说:


最后,确保ID令牌由相应的私钥签名到令牌的孩子声称。从 https://www.googleapis获取公钥.com / robot / v1 / metadata / x509 / securetoken @ system.gserviceaccount.com 并使用JWT库来验证签名。在该端点的响应的Cache-Control头中使用max-age的值来知道何时刷新公钥。

我不明白如何通过这个公钥来解码函数。钥匙是这样的:


$ b


<----> BEGIN CERTIFICATE ----- \\ \
MIIDHDCCAgSgAwIBAgIIZ36AHgMyvnQwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\\\
AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTcw\\\
MjA4MDA0NTI2WhcNMTcwMjExMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\\\
bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\\\
ggEPADCCAQoCggEBANBNTpiQplOYizNeLbs + r941T392wiuMWr1gSJEVykFyj7fe\\\
CCIhS / zrmG9jxVMK905KwceO / FNB4SK + l8GYLb559xZeJ6MFJ7QmRfL7Fjkq7GHS\\\
0 / sOFpjX7vfKjxH5oT65Fb1 + Hb4RzdoAjx0zRHkDIHIMiRzV0nYleplqLJXOAc6E\\\
5HQros8iLdf + ASdqaN0hS0nU5aa / CPU / EHQwfbEgYraZLyn5NtH8SPKIwZIeM7Fr\\\
nh + SS7JSadsqifrUBRtb // fueZ / FYlWqHEppsuIkbtaQmTjRycg35qpVSEACHkKc\\\
W05rRsSvz7q1Hucw6Kx / dNBBbkyHrR4Mc / wg31kCAwEAAaM4MDYwDAYDVR0TAQH / \\\
BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH / BAwwCgYIKwYBBQUHAwIwDQYJ\\\
KoZIhvcNAQEFBQADggEBAEuYEtvmZ4uReMQhE3P0iI4wkB36kWBe1mZZAwLA5A + U\\\
iEODMVKaaCGqZXrJTRhvEa20KRFrfuG QO7U3FgOMyWmX3drl40cNZNb3Ry8rsuVi\\\
R1dxy6HpC39zba / DsgL07enZPMDksLRNv0dVZ / X / wMrTLrwwrglpCBYUlxGT9RrU\\\
f8nAwLr1E4EpXxOVDXAX8bNBl3TCb2fu6DT62ZSmlJV40K + wTRUlCqIewzJ0wMt6\\\
O8 + 6kVdgZH4iKLi8gVjdcFfNsEpbOBoZqjipJ63l4A3mfxOkma0d2XgKR12KAfYX\\\
cAVPgihAPoNoUPJK0Nj + CmvNlUBXCrl9TtqGjK7AKi8 = \\\
----- END CERTIFICATE ----- \\\


在传递之前,我需要将这个公钥转换成什么吗?我尝试删除所有\\\
----- BEGIN CERTIFICATE ---------- BEGIN CERTIFICATE ----- ...但没有运气。我仍然得到无效的签名错误。任何建议?

解决方案

HS256仅在使用密码签署令牌时使用。 Firebase使用RS256时,它发出一个令牌,因此,您需要从给定的URL的公共密钥,您需要设置算法为RS256



另外请注意您在应用程序中获得的令牌不应该是一个数组,而应该是一个包含3个部分的字符串: body 签名。每个部分由一个分开,因此它给了你一个简单的字符串: header.body.signature



为了验证令牌,你需要做的是从定期给出URL (检查该信息的 Cache-Control 标题)和将它(JSON)保存在一个文件中,所以每次需要检查JWT时都不必检索它。然后你可以读取文件并解码JSON。解码的对象可以传递给 JWT :: decode(...)函数。
这里有一个简短的例子:

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

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

现在 $ encoded 变量包含有效载荷的令牌。一旦你有解码的对象,你仍然需要验证它。根据ID标记验证的指南 ,你必须检查下列事情:




  • exp / li>
  • iat 过去

  • iss https://securetoken.google.com/<firebaseProjectID>

  • aud < firebaseProjectID>

  • sub 非空



例如,您可以检查 iss (其中 FIREBASE_APP_ID 是Firebase控制台中的应用ID):

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






下面是一个完整的示例,检索他们。


免责声明:我没有测试过,基本上仅供参考。 / p>



  $ keys_file =securetoken.json; //下载的公钥文件
$ cache_file =pkeys.cache; //这个文件包含下一次系统必须重新验证密钥

/ **
*检查是否应该下载新密钥,并在需要时检索它们。
$ / b $ b函数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)){//将锁升级为独占(写)
//在这里我们需要重新验证,因为在此之前另一个进程可能已经到达LOCK_EX部分($ fp,filesize($ this-> cache_file))< = time()){
$ this-> refreshKeys($ fp);
}
flock($ fp,LOCK_UN);
} else {
throw new \RuntimeException('无法刷新密钥:文件锁升级错误。

} else {
//你需要通过发信号错误来处理
throw new \RuntimeException('不能刷新密钥:文件锁定错误');
}

fclose($ fp);
} else {
refreshKeys();
}
}

/ **
*下载公钥并将它们写入文件。这也设置了新的缓存重新验证时间。
* @param null $ fp缓存时间文件的文件指针
* /
函数refreshKeys($ fp = null)
{
$ 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('/ age:[] +?(\d +)/ i',$ headers,$ age_matches)=== 1){
$ age = $ age_matches [1];
$ b $ 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将被关闭,我们不需要
$ b $ 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);
}
}
}

/ **
*检索下载的密钥。
*只要您需要密钥(即解码/验证),就应该调用它。
* @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);

返回$ keys;



$ b

最好的办法是安排一个cronjob来调用 checkKeys()在需要时,但我不知道你的提供者是否允许。而不是这样,你可以为每个请求做到这一点:

  checkKeys(); 
$ pkeys_raw = getKeys(); //在使用它之前检查$ raw_keys是不是null!


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 the tutorial: Verify Firebase ID tokens

It says:

"If your backend is in a language that doesn't have an official Firebase Admin SDK, you can still verify ID tokens. First, find a third-party JWT library for your language. Then, verify the header, payload, and signature of the ID token."

I found that library: Firebase-PHP-JWT. In gitHub example; i couldn't understand the

$key part:

`$key = "example_key";` 

and

$token part:

`$token = array(
    "iss" => "http://example.org",
    "aud" => "http://example.com",
    "iat" => 1356999524,
    "nbf" => 1357000000
);`

My questions:

  1. What should be the $key variable?
  2. Why the &token variable is an array? Token which will be sent from mobile app is a String.
  3. If somebody could post a full example of verifying firebase ID with PHP-JWT, i would appreciate it.

EDIT:

Okey i got the point. GitHub example shows how to generate JWT code(encode) and how to decode it. In my case i need only decode the jwt which encoded by firebase. So, i need to use only this code:

$decoded = JWT::decode($jwt, $key, array('HS256'));

In this code part $jwt is the firebase ID token. For $key variable documentation says:

Finally, ensure that the ID token was signed by the private key corresponding to the token's kid claim. Grab the public key from https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com and use a JWT library to verify the signature. Use the value of max-age in the Cache-Control header of the response from that endpoint to know when to refresh the public keys.

I didn't understand how to pass this public keys to decode function. Keys are something like this:

"-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIZ36AHgMyvnQwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTcw\nMjA4MDA0NTI2WhcNMTcwMjExMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBANBNTpiQplOYizNeLbs+r941T392wiuMWr1gSJEVykFyj7fe\nCCIhS/zrmG9jxVMK905KwceO/FNB4SK+l8GYLb559xZeJ6MFJ7QmRfL7Fjkq7GHS\n0/sOFpjX7vfKjxH5oT65Fb1+Hb4RzdoAjx0zRHkDIHIMiRzV0nYleplqLJXOAc6E\n5HQros8iLdf+ASdqaN0hS0nU5aa/cPu/EHQwfbEgYraZLyn5NtH8SPKIwZIeM7Fr\nnh+SS7JSadsqifrUBRtb//fueZ/FYlWqHEppsuIkbtaQmTjRycg35qpVSEACHkKc\nW05rRsSvz7q1Hucw6Kx/dNBBbkyHrR4Mc/wg31kCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAEuYEtvmZ4uReMQhE3P0iI4wkB36kWBe1mZZAwLA5A+U\niEODMVKaaCGqZXrJTRhvEa20KRFrfuGQO7U3FgOMyWmX3drl40cNZNb3Ry8rsuVi\nR1dxy6HpC39zba/DsgL07enZPMDksLRNv0dVZ/X/wMrTLrwwrglpCBYUlxGT9RrU\nf8nAwLr1E4EpXxOVDXAX8bNBl3TCb2fu6DT62ZSmlJV40K+wTRUlCqIewzJ0wMt6\nO8+6kVdgZH4iKLi8gVjdcFfNsEpbOBoZqjipJ63l4A3mfxOkma0d2XgKR12KAfYX\ncAVPgihAPoNoUPJK0Nj+CmvNlUBXCrl9TtqGjK7AKi8=\n-----END CERTIFICATE-----\n"

Do i need to convert this public key to something before pass it? I tried to remove all "\n" and "-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----"...But no luck. Still i get invalid signature error. Any advice?

解决方案

HS256 is used only if you use a password to sign the token. Firebase uses RS256 when it issues a token, thus, you need the public keys from the given URL, and you need to set the algorithm to RS256.

Also note that the token you get in your application should not be an array but a string that has 3 parts: header, body, and signature. Each part is separated by a ., thus it gives you a simple string: header.body.signature

What you need to do in order to verify the tokens is downloading the public keys from the given URL regularly (check the Cache-Control header for that info) and saving it (the JSON) in a file, so you won't have to retrieve it every time you need to check the JWT. Then you can read in the file and decode the JSON. The decoded object can be passed to the JWT::decode(...) function. Here's a short sample:

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

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

Now the $decoded variable contains the payload of the token. Once you have the decoded object, you still need to verify it. According to the guide on ID token verification, you have to check the following things:

  • exp is in the future
  • iat is in the past
  • iss: https://securetoken.google.com/<firebaseProjectID>
  • aud: <firebaseProjectID>
  • sub is non-empty

So, for example, you can check iss like this (where FIREBASE_APP_ID is the app ID from the firebase console):

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


Here is a complete sample for refreshing the keys and retrieving them.

Disclaimer: I haven't tested it and this is basically for informational purposes only.

$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/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('/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;
}

The best thing would be scheduling a cronjob to call checkKeys() whenever needed, but I don't know if your provider allows that. Instead of that, you can do this for every request:

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

这篇关于如何使用PHP(JWT)验证Firebase ID令牌?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆