@@ -79,7 +79,7 @@ export class FirebaseTokenVerifier {
79
79
constructor ( private clientCertUrl : string , private algorithm : jwt . Algorithm ,
80
80
private issuer : string , private tokenInfo : FirebaseTokenInfo ,
81
81
private readonly app : FirebaseApp ) {
82
-
82
+
83
83
if ( ! validator . isURL ( clientCertUrl ) ) {
84
84
throw new FirebaseAuthError (
85
85
AuthClientErrorCode . INVALID_ARGUMENT ,
@@ -135,10 +135,11 @@ export class FirebaseTokenVerifier {
135
135
* Verifies the format and signature of a Firebase Auth JWT token.
136
136
*
137
137
* @param {string } jwtToken The Firebase Auth JWT token to verify.
138
+ * @param {boolean= } isEmulator Whether to accept Auth Emulator tokens.
138
139
* @return {Promise<DecodedIdToken> } A promise fulfilled with the decoded claims of the Firebase Auth ID
139
140
* token.
140
141
*/
141
- public verifyJWT ( jwtToken : string ) : Promise < DecodedIdToken > {
142
+ public verifyJWT ( jwtToken : string , isEmulator = false ) : Promise < DecodedIdToken > {
142
143
if ( ! validator . isString ( jwtToken ) ) {
143
144
throw new FirebaseAuthError (
144
145
AuthClientErrorCode . INVALID_ARGUMENT ,
@@ -148,19 +149,15 @@ export class FirebaseTokenVerifier {
148
149
149
150
return util . findProjectId ( this . app )
150
151
. then ( ( projectId ) => {
151
- return this . verifyJWTWithProjectId ( jwtToken , projectId ) ;
152
+ return this . verifyJWTWithProjectId ( jwtToken , projectId , isEmulator ) ;
152
153
} ) ;
153
154
}
154
155
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 > {
164
161
if ( ! validator . isNonEmptyString ( projectId ) ) {
165
162
throw new FirebaseAuthError (
166
163
AuthClientErrorCode . INVALID_CREDENTIAL ,
@@ -185,7 +182,7 @@ export class FirebaseTokenVerifier {
185
182
if ( ! fullDecodedToken ) {
186
183
errorMessage = `Decoding ${ this . tokenInfo . jwtName } failed. Make sure you passed the entire string JWT ` +
187
184
`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' ) {
189
186
const isCustomToken = ( payload . aud === FIREBASE_AUDIENCE ) ;
190
187
const isLegacyCustomToken = ( header . alg === 'HS256' && payload . v === 0 && 'd' in payload && 'uid' in payload . d ) ;
191
188
@@ -200,7 +197,7 @@ export class FirebaseTokenVerifier {
200
197
}
201
198
202
199
errorMessage += verifyJwtTokenDocsMessage ;
203
- } else if ( header . alg !== this . algorithm ) {
200
+ } else if ( ! isEmulator && header . alg !== this . algorithm ) {
204
201
errorMessage = `${ this . tokenInfo . jwtName } has incorrect algorithm. Expected "` + this . algorithm + '" but got ' +
205
202
'"' + header . alg + '".' + verifyJwtTokenDocsMessage ;
206
203
} else if ( payload . aud !== projectId ) {
@@ -209,7 +206,7 @@ export class FirebaseTokenVerifier {
209
206
verifyJwtTokenDocsMessage ;
210
207
} else if ( payload . iss !== this . issuer + projectId ) {
211
208
errorMessage = `${ this . tokenInfo . jwtName } has incorrect "iss" (issuer) claim. Expected ` +
212
- `"${ this . issuer } " ` + projectId + '" but got "' +
209
+ `"${ this . issuer } ` + projectId + '" but got "' +
213
210
payload . iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage ;
214
211
} else if ( typeof payload . sub !== 'string' ) {
215
212
errorMessage = `${ this . tokenInfo . jwtName } has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage ;
@@ -223,9 +220,8 @@ export class FirebaseTokenVerifier {
223
220
return Promise . reject ( new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , errorMessage ) ) ;
224
221
}
225
222
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.
229
225
return this . verifyJwtSignatureWithKey ( jwtToken , null ) ;
230
226
}
231
227
@@ -257,26 +253,29 @@ export class FirebaseTokenVerifier {
257
253
const verifyJwtTokenDocsMessage = ` See ${ this . tokenInfo . url } ` +
258
254
`for details on how to retrieve ${ this . shortNameArticle } ${ this . tokenInfo . shortName } .` ;
259
255
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 ) ;
272
277
}
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
+ } ) ;
280
279
} ) ;
281
280
}
282
281
0 commit comments