UNPKG

next-firebase-auth-edge

Version:

Next.js Firebase Authentication for Edge and server runtimes. Compatible with latest Next.js features.

96 lines (95 loc) 4.4 kB
import { decodeJwt, decodeProtectedHeader, errors } from 'jose'; import { ALGORITHM_RS256 } from '../auth/jwt/verify.js'; import { JWKSSignatureVerifier } from '../auth/signature-verifier'; import { FirebaseAppCheckError } from './api-client.js'; const APP_CHECK_ISSUER = 'https://firebaseappcheck.googleapis.com/'; const JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1/jwks'; export class AppCheckTokenVerifier { credential; signatureVerifier; constructor(credential) { this.credential = credential; this.signatureVerifier = new JWKSSignatureVerifier(JWKS_URL); } async verifyToken(token, options) { const projectId = await this.credential.getProjectId(); const decoded = await this.decodeAndVerify(token, projectId, options); const decodedAppCheckToken = decoded.payload; decodedAppCheckToken.app_id = decodedAppCheckToken.sub; return decodedAppCheckToken; } async decodeAndVerify(token, projectId, options) { const header = decodeProtectedHeader(token); const payload = decodeJwt(token); this.verifyContent({ header, payload }, projectId); await this.verifySignature(token, options); return { header, payload }; } verifyContent(fullDecodedToken, projectId) { const header = fullDecodedToken.header; const payload = fullDecodedToken.payload; const projectIdMatchMessage = ' Make sure the App Check token comes from the same ' + 'Firebase project as the service account used to authenticate this SDK.'; const scopedProjectId = `projects/${projectId}`; let errorMessage; if (header.alg !== ALGORITHM_RS256) { errorMessage = 'The provided App Check token has incorrect algorithm. Expected "' + ALGORITHM_RS256 + '" but got ' + '"' + header.alg + '".'; } else if (!payload.aud?.includes(scopedProjectId)) { errorMessage = 'The provided App Check token has incorrect "aud" (audience) claim. Expected "' + scopedProjectId + '" but got "' + payload.aud + '".' + projectIdMatchMessage; } else if (typeof payload.iss !== 'string' || !payload.iss.startsWith(APP_CHECK_ISSUER)) { errorMessage = 'The provided App Check token has incorrect "iss" (issuer) claim.'; } else if (typeof payload.sub !== 'string') { errorMessage = 'The provided App Check token has no "sub" (subject) claim.'; } else if (payload.sub === '') { errorMessage = 'The provided App Check token has an empty string "sub" (subject) claim.'; } if (errorMessage) { throw new FirebaseAppCheckError('invalid-argument', errorMessage); } } verifySignature(jwtToken, options) { return this.signatureVerifier .verify(jwtToken, options) .catch((error) => { throw this.mapJwtErrorToAppCheckError(error); }); } mapJwtErrorToAppCheckError(error) { if (error instanceof errors.JWTExpired) { const errorMessage = 'The provided App Check token has expired. Get a fresh App Check token' + ' from your client app and try again.'; return new FirebaseAppCheckError('app-check-token-expired', errorMessage); } else if (error instanceof errors.JWSSignatureVerificationFailed) { const errorMessage = 'The provided App Check token has invalid signature.'; return new FirebaseAppCheckError('invalid-argument', errorMessage); } else if (error instanceof errors.JWKSNoMatchingKey) { const errorMessage = 'The provided App Check token has "kid" claim which does not ' + 'correspond to a known public key. Most likely the provided App Check token ' + 'is expired, so get a fresh token from your client app and try again.'; return new FirebaseAppCheckError('invalid-argument', errorMessage); } return new FirebaseAppCheckError('invalid-argument', error.message); } }