@@ -79,7 +79,7 @@ export class FirebaseTokenVerifier {
7979 constructor ( private clientCertUrl : string , private algorithm : jwt . Algorithm ,
8080 private issuer : string , private tokenInfo : FirebaseTokenInfo ,
8181 private readonly app : FirebaseApp ) {
82-
82+
8383 if ( ! validator . isURL ( clientCertUrl ) ) {
8484 throw new FirebaseAuthError (
8585 AuthClientErrorCode . INVALID_ARGUMENT ,
@@ -135,10 +135,11 @@ export class FirebaseTokenVerifier {
135135 * Verifies the format and signature of a Firebase Auth JWT token.
136136 *
137137 * @param {string } jwtToken The Firebase Auth JWT token to verify.
138+ * @param {boolean= } isEmulator Whether to accept Auth Emulator tokens.
138139 * @return {Promise<DecodedIdToken> } A promise fulfilled with the decoded claims of the Firebase Auth ID
139140 * token.
140141 */
141- public verifyJWT ( jwtToken : string ) : Promise < DecodedIdToken > {
142+ public verifyJWT ( jwtToken : string , isEmulator = false ) : Promise < DecodedIdToken > {
142143 if ( ! validator . isString ( jwtToken ) ) {
143144 throw new FirebaseAuthError (
144145 AuthClientErrorCode . INVALID_ARGUMENT ,
@@ -148,19 +149,15 @@ export class FirebaseTokenVerifier {
148149
149150 return util . findProjectId ( this . app )
150151 . then ( ( projectId ) => {
151- return this . verifyJWTWithProjectId ( jwtToken , projectId ) ;
152+ return this . verifyJWTWithProjectId ( jwtToken , projectId , isEmulator ) ;
152153 } ) ;
153154 }
154155
155- /**
156- * Override the JWT signing algorithm.
157- * @param algorithm the new signing algorithm.
158- */
159- public setAlgorithm ( algorithm : jwt . Algorithm ) : void {
160- this . algorithm = algorithm ;
161- }
162-
163- private verifyJWTWithProjectId ( jwtToken : string , projectId : string | null ) : Promise < DecodedIdToken > {
156+ private verifyJWTWithProjectId (
157+ jwtToken : string ,
158+ projectId : string | null ,
159+ isEmulator : boolean
160+ ) : Promise < DecodedIdToken > {
164161 if ( ! validator . isNonEmptyString ( projectId ) ) {
165162 throw new FirebaseAuthError (
166163 AuthClientErrorCode . INVALID_CREDENTIAL ,
@@ -185,7 +182,7 @@ export class FirebaseTokenVerifier {
185182 if ( ! fullDecodedToken ) {
186183 errorMessage = `Decoding ${ this . tokenInfo . jwtName } failed. Make sure you passed the entire string JWT ` +
187184 `which represents ${ this . shortNameArticle } ${ this . tokenInfo . shortName } .` + verifyJwtTokenDocsMessage ;
188- } else if ( typeof header . kid === 'undefined' && this . algorithm !== 'none ') {
185+ } else if ( ! isEmulator && typeof header . kid === 'undefined' ) {
189186 const isCustomToken = ( payload . aud === FIREBASE_AUDIENCE ) ;
190187 const isLegacyCustomToken = ( header . alg === 'HS256' && payload . v === 0 && 'd' in payload && 'uid' in payload . d ) ;
191188
@@ -200,7 +197,7 @@ export class FirebaseTokenVerifier {
200197 }
201198
202199 errorMessage += verifyJwtTokenDocsMessage ;
203- } else if ( header . alg !== this . algorithm ) {
200+ } else if ( ! isEmulator && header . alg !== this . algorithm ) {
204201 errorMessage = `${ this . tokenInfo . jwtName } has incorrect algorithm. Expected "` + this . algorithm + '" but got ' +
205202 '"' + header . alg + '".' + verifyJwtTokenDocsMessage ;
206203 } else if ( payload . aud !== projectId ) {
@@ -209,7 +206,7 @@ export class FirebaseTokenVerifier {
209206 verifyJwtTokenDocsMessage ;
210207 } else if ( payload . iss !== this . issuer + projectId ) {
211208 errorMessage = `${ this . tokenInfo . jwtName } has incorrect "iss" (issuer) claim. Expected ` +
212- `"${ this . issuer } " ` + projectId + '" but got "' +
209+ `"${ this . issuer } ` + projectId + '" but got "' +
213210 payload . iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage ;
214211 } else if ( typeof payload . sub !== 'string' ) {
215212 errorMessage = `${ this . tokenInfo . jwtName } has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage ;
@@ -223,9 +220,8 @@ export class FirebaseTokenVerifier {
223220 return Promise . reject ( new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , errorMessage ) ) ;
224221 }
225222
226- // When the algorithm is set to 'none' there will be no signature and therefore we don't check
227- // the public keys.
228- if ( this . algorithm === 'none' ) {
223+ if ( isEmulator ) {
224+ // Signature checks skipped for emulator; no need to fetch public keys.
229225 return this . verifyJwtSignatureWithKey ( jwtToken , null ) ;
230226 }
231227
@@ -257,26 +253,29 @@ export class FirebaseTokenVerifier {
257253 const verifyJwtTokenDocsMessage = ` See ${ this . tokenInfo . url } ` +
258254 `for details on how to retrieve ${ this . shortNameArticle } ${ this . tokenInfo . shortName } .` ;
259255 return new Promise ( ( resolve , reject ) => {
260- jwt . verify ( jwtToken , publicKey || '' , {
261- algorithms : [ this . algorithm ] ,
262- } , ( error : jwt . VerifyErrors | null , decodedToken : object | undefined ) => {
263- if ( error ) {
264- if ( error . name === 'TokenExpiredError' ) {
265- const errorMessage = `${ this . tokenInfo . jwtName } has expired. Get a fresh ${ this . tokenInfo . shortName } ` +
266- ` from your client app and try again (auth/${ this . tokenInfo . expiredErrorCode . code } ).` +
267- verifyJwtTokenDocsMessage ;
268- return reject ( new FirebaseAuthError ( this . tokenInfo . expiredErrorCode , errorMessage ) ) ;
269- } else if ( error . name === 'JsonWebTokenError' ) {
270- const errorMessage = `${ this . tokenInfo . jwtName } has invalid signature.` + verifyJwtTokenDocsMessage ;
271- return reject ( new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , errorMessage ) ) ;
256+ const verifyOptions : jwt . VerifyOptions = { } ;
257+ if ( publicKey !== null ) {
258+ verifyOptions . algorithms = [ this . algorithm ] ;
259+ }
260+ jwt . verify ( jwtToken , publicKey || '' , verifyOptions ,
261+ ( error : jwt . VerifyErrors | null , decodedToken : object | undefined ) => {
262+ if ( error ) {
263+ if ( error . name === 'TokenExpiredError' ) {
264+ const errorMessage = `${ this . tokenInfo . jwtName } has expired. Get a fresh ${ this . tokenInfo . shortName } ` +
265+ ` from your client app and try again (auth/${ this . tokenInfo . expiredErrorCode . code } ).` +
266+ verifyJwtTokenDocsMessage ;
267+ return reject ( new FirebaseAuthError ( this . tokenInfo . expiredErrorCode , errorMessage ) ) ;
268+ } else if ( error . name === 'JsonWebTokenError' ) {
269+ const errorMessage = `${ this . tokenInfo . jwtName } has invalid signature.` + verifyJwtTokenDocsMessage ;
270+ return reject ( new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , errorMessage ) ) ;
271+ }
272+ return reject ( new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , error . message ) ) ;
273+ } else {
274+ const decodedIdToken = ( decodedToken as DecodedIdToken ) ;
275+ decodedIdToken . uid = decodedIdToken . sub ;
276+ resolve ( decodedIdToken ) ;
272277 }
273- return reject ( new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , error . message ) ) ;
274- } else {
275- const decodedIdToken = ( decodedToken as DecodedIdToken ) ;
276- decodedIdToken . uid = decodedIdToken . sub ;
277- resolve ( decodedIdToken ) ;
278- }
279- } ) ;
278+ } ) ;
280279 } ) ;
281280 }
282281
0 commit comments