@@ -19,12 +19,9 @@ import * as util from '../utils/index';
1919import * as validator from '../utils/validator' ;
2020import * as jwt from 'jsonwebtoken' ;
2121import {
22- DecodedToken , decodeJwt , JwtDecoderError , JwtDecoderErrorCode
23- } from '../utils/jwt-decoder' ;
24- import {
25- EmulatorSignatureVerifier , NO_MATCHING_KID_ERROR_MESSAGE ,
26- PublicKeySignatureVerifier , SignatureVerifierError , SignatureVerifierErrorCode
27- } from '../utils/jwt-signature-verifier' ;
22+ DecodedToken , decodeJwt , JwtError , JwtErrorCode ,
23+ EmulatorSignatureVerifier , PublicKeySignatureVerifier , UrlKeyFetcher ,
24+ } from '../utils/jwt' ;
2825import { FirebaseApp } from '../firebase-app' ;
2926import { auth } from './index' ;
3027
@@ -33,7 +30,7 @@ import DecodedIdToken = auth.DecodedIdToken;
3330// Audience to use for Firebase Auth Custom tokens
3431const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit' ;
3532
36- export const ALGORITHM_RS256 = 'RS256' ;
33+ const ALGORITHM_RS256 = 'RS256' as const ;
3734
3835// URL containing the public keys for the Google certs (whose private keys are used to sign Firebase
3936// Auth ID tokens)
@@ -80,7 +77,6 @@ export interface FirebaseTokenInfo {
8077export class FirebaseTokenVerifier {
8178 private readonly shortNameArticle : string ;
8279 private readonly signatureVerifier : PublicKeySignatureVerifier ;
83- private readonly emulatorSignatureVerifier : EmulatorSignatureVerifier ;
8480
8581 constructor ( clientCertUrl : string , private algorithm : jwt . Algorithm ,
8682 private issuer : string , private tokenInfo : FirebaseTokenInfo ,
@@ -134,8 +130,8 @@ export class FirebaseTokenVerifier {
134130 }
135131 this . shortNameArticle = tokenInfo . shortName . charAt ( 0 ) . match ( / [ a e i o u ] / i) ? 'an' : 'a' ;
136132
137- this . signatureVerifier = new PublicKeySignatureVerifier ( clientCertUrl , algorithm , app ) ;
138- this . emulatorSignatureVerifier = new EmulatorSignatureVerifier ( ) ;
133+ this . signatureVerifier = new PublicKeySignatureVerifier (
134+ new UrlKeyFetcher ( clientCertUrl , app . options . httpAgent ) ) ;
139135
140136 // For backward compatibility, the project ID is validated in the verification call.
141137 }
@@ -156,55 +152,69 @@ export class FirebaseTokenVerifier {
156152 ) ;
157153 }
158154
159- return util . findProjectId ( this . app )
155+ return this . ensureProjectId ( )
160156 . then ( ( projectId ) => {
161- return Promise . all ( [ this . safeDecode ( jwtToken ) , projectId ] ) ;
162- } )
163- . then ( ( [ fullDecodedToken , projectId ] ) => {
164- this . validateToken ( fullDecodedToken , projectId , isEmulator ) ;
165- return Promise . all ( [
166- fullDecodedToken ,
167- this . verifySignature ( jwtToken , isEmulator )
168- ] ) ;
157+ return this . decodeAndVerify ( jwtToken , projectId , isEmulator ) ;
169158 } )
170- . then ( ( [ fullDecodedToken ] ) => {
171- const decodedIdToken = fullDecodedToken . payload as DecodedIdToken ;
159+ . then ( ( decoded ) => {
160+ const decodedIdToken = decoded . payload as DecodedIdToken ;
172161 decodedIdToken . uid = decodedIdToken . sub ;
173162 return decodedIdToken ;
174163 } ) ;
175164 }
176165
166+ private ensureProjectId ( ) : Promise < string > {
167+ return util . findProjectId ( this . app )
168+ . then ( ( projectId ) => {
169+ if ( ! validator . isNonEmptyString ( projectId ) ) {
170+ throw new FirebaseAuthError (
171+ AuthClientErrorCode . INVALID_CREDENTIAL ,
172+ 'Must initialize app with a cert credential or set your Firebase project ID as the ' +
173+ `GOOGLE_CLOUD_PROJECT environment variable to call ${ this . tokenInfo . verifyApiName } .` ,
174+ ) ;
175+ }
176+ return Promise . resolve ( projectId ) ;
177+ } )
178+ }
179+
180+ private decodeAndVerify ( token : string , projectId : string , isEmulator : boolean ) : Promise < DecodedToken > {
181+ return this . verifyContent ( token , projectId , isEmulator )
182+ . then ( ( decoded ) => {
183+ return this . verifySignature ( token , isEmulator )
184+ . then ( ( ) => decoded ) ;
185+ } ) ;
186+ }
187+
188+ private verifyContent ( token : string , projectId : string , isEmulator : boolean ) : Promise < DecodedToken > {
189+ return this . safeDecode ( token ) . then ( ( decodedToken ) => {
190+ this . validateTokenContent ( decodedToken , projectId , isEmulator ) ;
191+ return Promise . resolve ( decodedToken ) ;
192+ } ) ;
193+ }
194+
177195 private safeDecode ( jwtToken : string ) : Promise < DecodedToken > {
178196 return decodeJwt ( jwtToken )
179197 . catch ( ( err ) => {
180- if ( ! ( err instanceof JwtDecoderError ) ) {
181- return Promise . reject ( err ) ;
198+ if ( ! ( err instanceof JwtError ) ) {
199+ throw err ;
182200 }
183- if ( err . code == JwtDecoderErrorCode . INVALID_ARGUMENT ) {
201+ if ( err . code == JwtErrorCode . INVALID_ARGUMENT ) {
184202 const verifyJwtTokenDocsMessage = ` See ${ this . tokenInfo . url } ` +
185203 `for details on how to retrieve ${ this . shortNameArticle } ${ this . tokenInfo . shortName } .` ;
186204 const errorMessage = `Decoding ${ this . tokenInfo . jwtName } failed. Make sure you passed ` +
187205 `the entire string JWT which represents ${ this . shortNameArticle } ` +
188206 `${ this . tokenInfo . shortName } .` + verifyJwtTokenDocsMessage ;
189- return Promise . reject (
190- new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , errorMessage ) ) ;
207+ throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT ,
208+ errorMessage ) ;
191209 }
192- return Promise . reject (
193- new FirebaseAuthError ( AuthClientErrorCode . INTERNAL_ERROR , err . message ) ) ;
210+ throw new FirebaseAuthError ( AuthClientErrorCode . INTERNAL_ERROR , err . message ) ;
194211 } ) ;
195212 }
196213
197- private validateToken (
214+ private validateTokenContent (
198215 fullDecodedToken : DecodedToken ,
199216 projectId : string | null ,
200217 isEmulator : boolean ) : void {
201- if ( ! validator . isNonEmptyString ( projectId ) ) {
202- throw new FirebaseAuthError (
203- AuthClientErrorCode . INVALID_CREDENTIAL ,
204- 'Must initialize app with a cert credential or set your Firebase project ID as the ' +
205- `GOOGLE_CLOUD_PROJECT environment variable to call ${ this . tokenInfo . verifyApiName } .` ,
206- ) ;
207- }
208218
209219 const header = fullDecodedToken && fullDecodedToken . header ;
210220 const payload = fullDecodedToken && fullDecodedToken . payload ;
@@ -256,37 +266,30 @@ export class FirebaseTokenVerifier {
256266
257267 private verifySignature ( jwtToken : string , isEmulator : boolean ) :
258268 Promise < void > {
259- if ( isEmulator ) {
260- return this . emulatorSignatureVerifier . verify ( jwtToken )
261- . catch ( ( error ) => {
262- return Promise . reject ( this . mapSignatureVerifierErrorToAuthError ( error ) ) ;
263- } ) ;
264- }
265-
266- return this . signatureVerifier . verify ( jwtToken )
269+ const verifier = isEmulator ? new EmulatorSignatureVerifier ( ) : this . signatureVerifier ;
270+ return verifier . verify ( jwtToken )
267271 . catch ( ( error ) => {
268- return Promise . reject ( this . mapSignatureVerifierErrorToAuthError ( error ) ) ;
272+ throw this . mapJwtErrorToAuthError ( error ) ;
269273 } ) ;
270274 }
271275
272- private mapSignatureVerifierErrorToAuthError ( error : SignatureVerifierError ) : Error {
276+ private mapJwtErrorToAuthError ( error : JwtError ) : Error {
273277 const verifyJwtTokenDocsMessage = ` See ${ this . tokenInfo . url } ` +
274278 `for details on how to retrieve ${ this . shortNameArticle } ${ this . tokenInfo . shortName } .` ;
275- if ( ! ( error instanceof SignatureVerifierError ) ) {
279+ if ( ! ( error instanceof JwtError ) ) {
276280 return ( error ) ;
277281 }
278- if ( error . code === SignatureVerifierErrorCode . TOKEN_EXPIRED ) {
282+ if ( error . code === JwtErrorCode . TOKEN_EXPIRED ) {
279283 const errorMessage = `${ this . tokenInfo . jwtName } has expired. Get a fresh ${ this . tokenInfo . shortName } ` +
280284 ` from your client app and try again (auth/${ this . tokenInfo . expiredErrorCode . code } ).` +
281285 verifyJwtTokenDocsMessage ;
282286 return new FirebaseAuthError ( this . tokenInfo . expiredErrorCode , errorMessage ) ;
283287 }
284- else if ( error . code === SignatureVerifierErrorCode . INVALID_TOKEN ) {
288+ else if ( error . code === JwtErrorCode . INVALID_TOKEN ) {
285289 const errorMessage = `${ this . tokenInfo . jwtName } has invalid signature.` + verifyJwtTokenDocsMessage ;
286290 return new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , errorMessage ) ;
287291 }
288- else if ( error . code === SignatureVerifierErrorCode . INVALID_ARGUMENT &&
289- error . message === NO_MATCHING_KID_ERROR_MESSAGE ) {
292+ else if ( error . code === JwtErrorCode . NO_MATCHING_KID ) {
290293 const errorMessage = `${ this . tokenInfo . jwtName } has "kid" claim which does not ` +
291294 `correspond to a known public key. Most likely the ${ this . tokenInfo . shortName } ` +
292295 'is expired, so get a fresh token from your client app and try again.' ;
0 commit comments