如何从react-firebase获取有效令牌以进行nodesjs服务器验证

Mar*_*son 3 node.js firebase google-oauth react-redux-firebase

我有一个reactJS Web应用程序,我正在使用react-redux-firebase进行身份验证,它工作正常。

现在我想添加一个自己的nodejs服务器(后面有一个小数据库),我读到我可以使用react webapp登录中的firebase令牌来对nodeJs服务器上的用户进行身份验证,然后再让他对数据库进行更改。

但我得到了错误

No pem found for envelope: {"alg":"RS256","kid":"f5c9aebe234da6016bd7b949168b8cd5b4ec9eeb","typ":"JWT"}
Run Code Online (Sandbox Code Playgroud)

这就是我在客户端获取令牌并将其发送到服务器的方式


async function updateDataInDatabase2(data, dispatch, getState) {
    try {
        await axios.post(`http://localhost:5000/app/todo/data`, JSON.stringify(data), {
            headers: { 'Content-Type': 'application/json', 'firebase-idToken': getState().firebase.auth.stsTokenManager.accessToken },
        });
    } catch (err) {
        console.log(err.message);
    }
}
Run Code Online (Sandbox Code Playgroud)

我发送的令牌如下所示:

'eyJhbGciOiJSUzI1NiIsImtpZCI6ImY1YzlhZWJlMjM0ZGE2MDE2YmQ3Yjk0OTE2OGI4Y2Q1YjRlYzllZWIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vam9ycm9jaC1jb25zdWx0aW5nIiwiYXVkIjoiam9ycm9jaC1jb25zdWx0aW5nIiwiYXV0aF90aW1lIjoxNTkwMDU5Njk5LCJ1c2VyX2lkIjoiVkFXSEFZVXdXS2g5akJkbE82QWFOc0ZUVFJwMSIsInN1YiI6IlZBV0hBWVV3V0toOWpCZGxPNkFhTnNGVFRScDEiLCJpYXQiOjE1OTAwNTk2OTksImV4cCI6MTU5MDA2MzI5OSwiZW1haWwiOiJtYXJrdXNhY3Rpb25qYWNrc29uQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbIm1hcmt1c2FjdGlvbmphY2tzb25AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.KRWI4drcSSU-3YVALR4kJlLdbB9JfhSvbhsa4nR42r41IQkeRj8uIZRkA6-kmprpohlRt8v3ZlM1NsY37eNeHsI1x-KIYlVmcpE4GDETrTMNQ-NdfoyIAFqij79iPi4b7y5uDy6uFLLiKgoQKUJKIT_OFDcM1vUXeLrsA0Jn4eoA7w8wMHTqCA0vYU2YutLuBydpfdElGM0LR3yqWAf6jJFjRu45vyY1JQsI2utTBCv21B4_IDFiN7ov8NqUFjX5CHkRNiipF9P6H9USYvcPTg6AQYfMhMd8V1rtT9_EXPZMNEMnR72sIWG9Y5-Fq4fT7K1mtj7OZlKURGfKSdyybA'
Run Code Online (Sandbox Code Playgroud)

这似乎是一个有效的令牌。

我也尝试这样做:

getFirebase().auth().currentUser.getIdToken()
Run Code Online (Sandbox Code Playgroud)

但这提供了一个像这样的对象:

const token1 = {
    a: 2,
    b: null,
    c: null,
    f: null,
    g: false,
    h: false,
    i:
        'eyJhbGciOiJSUzI1NiIsImtpZCI6ImY1YzlhZWJlMjM0ZGE2MDE2YmQ3Yjk0OTE2OGI4Y2Q1YjRlYzllZWIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vam9ycm9jaC1jb25zdWx0aW5nIiwiYXVkIjoiam9ycm9jaC1jb25zdWx0aW5nIiwiYXV0aF90aW1lIjoxNTkwMDU5Njk5LCJ1c2VyX2lkIjoiVkFXSEFZVXdXS2g5akJkbE82QWFOc0ZUVFJwMSIsInN1YiI6IlZBV0hBWVV3V0toOWpCZGxPNkFhTnNGVFRScDEiLCJpYXQiOjE1OTAwNTk2OTksImV4cCI6MTU5MDA2MzI5OSwiZW1haWwiOiJtYXJrdXNhY3Rpb25qYWNrc29uQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbIm1hcmt1c2FjdGlvbmphY2tzb25AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.KRWI4drcSSU-3YVALR4kJlLdbB9JfhSvbhsa4nR42r41IQkeRj8uIZRkA6-kmprpohlRt8v3ZlM1NsY37eNeHsI1x-KIYlVmcpE4GDETrTMNQ-NdfoyIAFqij79iPi4b7y5uDy6uFLLiKgoQKUJKIT_OFDcM1vUXeLrsA0Jn4eoA7w8wMHTqCA0vYU2YutLuBydpfdElGM0LR3yqWAf6jJFjRu45vyY1JQsI2utTBCv21B4_IDFiN7ov8NqUFjX5CHkRNiipF9P6H9USYvcPTg6AQYfMhMd8V1rtT9_EXPZMNEMnR72sIWG9Y5-Fq4fT7K1mtj7OZlKURGfKSdyybA',
};
Run Code Online (Sandbox Code Playgroud)

使用该对象可能不正确。JSON.stringyfy 给我一个解析错误。所以我决定将String发送到“i”属性后面,这与上面相同。

在服务器端,我尝试像这样验证它:

router.post('/data', async (req, res) => {
    const idToken = req.header('firebase-idToken');
    const userid = await verifyFirebaseIdToken(idToken);
    ...
    // do database modification
});



const { OAuth2Client } = require('google-auth-library');

const CLIENT_ID = 'Jorroch-Consulting-Web-App';
const client = new OAuth2Client(CLIENT_ID);
const verifyFirebaseIdToken = async (token) => {
    try {
        const ticket = await client.verifyIdToken({
            idToken: token.trim(),
            audience: CLIENT_ID, 
        });
        const payload = ticket.getPayload();
        const userid = payload['sub'];
        console.log('UserID: ', userid);
        return userid;
    } catch (error) {
        console.log('Error validating firebase idToken: ', error.message);
    }
};
Run Code Online (Sandbox Code Playgroud)

但在 verifyIdToken 中,我收到了 oauth2client.js 中抛出的错误:

 if (!Object.prototype.hasOwnProperty.call(certs, envelope.kid)) {
            // If this is not present, then there's no reason to attempt verification
            //throw new Error('No pem found for envelope: ' + JSON.stringify(envelope));
        }
Run Code Online (Sandbox Code Playgroud)

这是引发错误的函数的完整代码:

   /**
     * Verify the id token is signed with the correct certificate
     * and is from the correct audience.
     * @param jwt The jwt to verify (The ID Token in this case).
     * @param certs The array of certs to test the jwt against.
     * @param requiredAudience The audience to test the jwt against.
     * @param issuers The allowed issuers of the jwt (Optional).
     * @param maxExpiry The max expiry the certificate can be (Optional).
     * @return Returns a promise resolving to LoginTicket on verification.
     */
    async verifySignedJwtWithCertsAsync(jwt, certs, requiredAudience, issuers, maxExpiry) {
        const crypto = crypto_1.createCrypto();
        if (!maxExpiry) {
            maxExpiry = OAuth2Client.MAX_TOKEN_LIFETIME_SECS_;
        }
        const segments = jwt.split('.');
        if (segments.length !== 3) {
            throw new Error('Wrong number of segments in token: ' + jwt);
        }
        const signed = segments[0] + '.' + segments[1];
        let signature = segments[2];
        let envelope;
        let payload;
        try {
            envelope = JSON.parse(crypto.decodeBase64StringUtf8(segments[0]));
        }
        catch (err) {
            err.message = `Can't parse token envelope: ${segments[0]}': ${err.message}`;
            throw err;
        }
        if (!envelope) {
            throw new Error("Can't parse token envelope: " + segments[0]);
        }
        try {
            payload = JSON.parse(crypto.decodeBase64StringUtf8(segments[1]));
        }
        catch (err) {
            err.message = `Can't parse token payload '${segments[0]}`;
            throw err;
        }
        if (!payload) {
            throw new Error("Can't parse token payload: " + segments[1]);
        }
        if (!Object.prototype.hasOwnProperty.call(certs, envelope.kid)) {
            // If this is not present, then there's no reason to attempt verification
            //throw new Error('No pem found for envelope: ' + JSON.stringify(envelope));
        }
        const cert = certs[envelope.kid];
        if (envelope.alg === 'ES256') {
            signature = formatEcdsa.joseToDer(signature, 'ES256').toString('base64');
        }
        const verified = await crypto.verify(cert, signed, signature);
        if (!verified) {
            throw new Error('Invalid token signature: ' + jwt);
        }
        if (!payload.iat) {
            throw new Error('No issue time in token: ' + JSON.stringify(payload));
        }
        if (!payload.exp) {
            throw new Error('No expiration time in token: ' + JSON.stringify(payload));
        }
        const iat = Number(payload.iat);
        if (isNaN(iat))
            throw new Error('iat field using invalid format');
        const exp = Number(payload.exp);
        if (isNaN(exp))
            throw new Error('exp field using invalid format');
        const now = new Date().getTime() / 1000;
        if (exp >= now + maxExpiry) {
            throw new Error('Expiration time too far in future: ' + JSON.stringify(payload));
        }
        const earliest = iat - OAuth2Client.CLOCK_SKEW_SECS_;
        const latest = exp + OAuth2Client.CLOCK_SKEW_SECS_;
        if (now < earliest) {
            throw new Error('Token used too early, ' +
                now +
                ' < ' +
                earliest +
                ': ' +
                JSON.stringify(payload));
        }
        if (now > latest) {
            throw new Error('Token used too late, ' +
                now +
                ' > ' +
                latest +
                ': ' +
                JSON.stringify(payload));
        }
        if (issuers && issuers.indexOf(payload.iss) < 0) {
            throw new Error('Invalid issuer, expected one of [' +
                issuers +
                '], but got ' +
                payload.iss);
        }
        // Check the audience matches if we have one
        if (typeof requiredAudience !== 'undefined' && requiredAudience !== null) {
            const aud = payload.aud;
            let audVerified = false;
            // If the requiredAudience is an array, check if it contains token
            // audience
            if (requiredAudience.constructor === Array) {
                audVerified = requiredAudience.indexOf(aud) > -1;
            }
            else {
                audVerified = aud === requiredAudience;
            }
            if (!audVerified) {
                throw new Error('Wrong recipient, payload audience != requiredAudience');
            }
        }
        return new loginticket_1.LoginTicket(envelope, payload);
    }
Run Code Online (Sandbox Code Playgroud)

所以令牌中一定缺少一些东西

!Object.prototype.hasOwnProperty.call(certs, envelope.kid)) 
Run Code Online (Sandbox Code Playgroud)

是真的。

在调试模式下,我看到 certs 是一个具有两个属性的对象(960a7e8e8341ed752f12b186fa129731fe0b04c0 和 c1771814ba6a70693fb9412da3c6e90c2bf5b927),信封中的 kids 属性是 f5c9aebe234da6016bd7b94916 8b8cd5b4ec9eeb。

所以孩子的长度是一样的,这让我认为这并不是完全错误的。似乎验证尝试根据 Kid 属性中指定的证书进行验证,但 certs 对象中只有两个不同的证书。

有谁知道令牌有什么问题吗?

我现在一整天都在搜索,并考虑踢出我的项目的 firebase 。

小智 6

使用 oauth2client.js 等外部库似乎会很复杂,您也可以考虑在后端使用 Firebase Admin SDK 来尝试验证令牌。这样你就可以发送从承诺返回的令牌

firebase.auth().currentUser.getIdToken(true).then(idToken => axios.post...)

在您的 Node.js 服务器上,您可以按照以下说明设置 Admin SDK: https: //firebase.google.com/docs/admin/setup

然后在收到请求时调用authenticate方法

admin.auth().verifyIdToken(idToken)
    .then(function(decodedToken) {
        let uid = decodedToken.uid;
    }
Run Code Online (Sandbox Code Playgroud)

  • 天哪,我真的花了几个小时尝试在 Firebase Functions 中使用“google-auth-library”,却遇到了 OP 的错误(“找不到信封的 pem”)。终于找到了答案,并将所有内容替换为“admin.auth().verifyIdToken”,这就像一个魅力。你应该得到一块饼干 威尔,谢谢你! (3认同)
  • 我是从 David 的文章中找到的,该文章也值得从此处链接 https://itnext.io/firebase-cloud-functions-verify-users-tokens-d4e60e314d1a (2认同)