Vercel边缘函数不支持Firebase管理员SDK,因此如何验证客户端请求并从id令牌获取Firebase用户对象(uid、电子邮件、名称、自定义声明)?
有一个介绍(https://firebase.google.com/docs/auth/admin/verify-id-tokens)来使用第三方库验证id令牌,但仅此而已。
下面的库模块利用jose
来处理令牌的验证。它已经徒手编码,所以预计错别字。该模块公开了两种方法:verfyIdToken(req: NextRequest,fire baseIdToken:string)
(错误时拒绝)和verfyIdTokenWellInlineError(req:NextRequest,fire baseIdToken:string)
(从不拒绝,而是在返回的对象中返回错误)。使用适合您风格的方法。
注意:此代码当前不会与Firebase Auth签入以查看令牌是否已被撤销。
// lib/verify-jwt.ts
import * as jose from 'jose';
// Builds a callback function that makes API calls to the given endpoint
// only once the last response expires, otherwise a fresh call is made.
const buildCachedFetch = (src: string) => {
let _cache = false,
expiresAt = 0,
init = async () => {
const nowMS = Date.now(),
res = await fetch(src);
if (!res.ok) {
const err = new Error("Unexpected response status code: " + res.status);
err.res = res;
throw err;
}
expiresAt = nowMS + (Number(/max-age=(\d+)/.exec(res.headers.get('cache-control'))?.[1] || 0) * 1000);
return res.json();
},
refresh = () => {
expiresAt = 0;
_cache = init();
_cache.catch(() => _cache = null);
return _cache
};
return (forceRefresh?: boolean) => {
return !forceRefresh && _cache && (expiresAt === 0 || expiresAt > Date.now())
? _cache
: refresh()
}
}
// build the callback to fetch the authentication certificates
const fetchAuthKeyStore = buildCachedFetch("https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com");
// Defines a callback to pass to the JWT verifier to resolve the appropriate public key
const authKeyResolver = async ({ alg: string, kid: string }) => {
const keyStore = await fetchAuthKeyStore();
if (!(kid in keyStore)) throw new Error("unexpected kid in auth token")
return jose.importX509(keyStore[kid], alg);
};
// Verifies the Authorization header and returns the decoded ID token. Errors
// will reject the Promise chain.
export const verifyIdToken = async (req: NextRequest, firebaseProjectId: string) => {
const jwt = /^bearer (.*)$/i.exec(req.headers.get('authorization'));
if (!jwt) throw new Error("Unauthorised");
const result = jose.jwtVerify(jwt, authKeyResolver, {
audience: firebaseProjectId,
issuer: `https://securetoken.google.com/${firebaseProjectId}`
});
result.payload.uid = result.payload.sub; // see https://firebase.google.com/docs/reference/admin/node/firebase-admin.auth.decodedidtoken.md#decodedidtokenuid
return result;
}
// Verifies the Authorization header and returns the decoded ID token. Errors
// are returned in the return object instead of rejecting the Promise chain.
export const verifyIdTokenWithInlineError = (req: NextRequest, firebaseProjectId: string) => {
return verifyIdToken(req, firebaseProjectId)
.catch((error) => ({ error, payload: null, protectedHeader: null }));
}
使用Vercel的hello. ts
示例作为基础,您可以这样使用它:
// pages/api/hello.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyIdTokenWithInlineError } from '../../lib/verify-jwt';
export const config = {
runtime: 'edge', // this is a pre-requisite
regions: ['iad1'], // only execute this function on iad1
};
const FIREBASE_PROJECT_ID = "<your-project-id>";
export default async (req: NextRequest) => {
const { error, payload } = await verifyIdTokenWithInlineError(req, FIREBASE_PROJECT_ID);
if (error) {
return NextResponse.status(403).json({
error: "You must be authenticated.",
});
}
// if here, a valid auth token was provided. `payload` contains some
// user info.
return NextResponse.json({
name: `Hello ${payload.name}! I'm an Edge Function!`,
});
};