@@ -19,12 +19,9 @@ import * as util from '../utils/index';
19
19
import * as validator from '../utils/validator' ;
20
20
import * as jwt from 'jsonwebtoken' ;
21
21
import {
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' ;
28
25
import { FirebaseApp } from '../firebase-app' ;
29
26
import { auth } from './index' ;
30
27
@@ -33,7 +30,7 @@ import DecodedIdToken = auth.DecodedIdToken;
33
30
// Audience to use for Firebase Auth Custom tokens
34
31
const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit' ;
35
32
36
- export const ALGORITHM_RS256 = 'RS256' ;
33
+ const ALGORITHM_RS256 = 'RS256' as const ;
37
34
38
35
// URL containing the public keys for the Google certs (whose private keys are used to sign Firebase
39
36
// Auth ID tokens)
@@ -80,7 +77,6 @@ export interface FirebaseTokenInfo {
80
77
export class FirebaseTokenVerifier {
81
78
private readonly shortNameArticle : string ;
82
79
private readonly signatureVerifier : PublicKeySignatureVerifier ;
83
- private readonly emulatorSignatureVerifier : EmulatorSignatureVerifier ;
84
80
85
81
constructor ( clientCertUrl : string , private algorithm : jwt . Algorithm ,
86
82
private issuer : string , private tokenInfo : FirebaseTokenInfo ,
@@ -134,8 +130,8 @@ export class FirebaseTokenVerifier {
134
130
}
135
131
this . shortNameArticle = tokenInfo . shortName . charAt ( 0 ) . match ( / [ a e i o u ] / i) ? 'an' : 'a' ;
136
132
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 ) ) ;
139
135
140
136
// For backward compatibility, the project ID is validated in the verification call.
141
137
}
@@ -156,55 +152,69 @@ export class FirebaseTokenVerifier {
156
152
) ;
157
153
}
158
154
159
- return util . findProjectId ( this . app )
155
+ return this . ensureProjectId ( )
160
156
. 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 ) ;
169
158
} )
170
- . then ( ( [ fullDecodedToken ] ) => {
171
- const decodedIdToken = fullDecodedToken . payload as DecodedIdToken ;
159
+ . then ( ( decoded ) => {
160
+ const decodedIdToken = decoded . payload as DecodedIdToken ;
172
161
decodedIdToken . uid = decodedIdToken . sub ;
173
162
return decodedIdToken ;
174
163
} ) ;
175
164
}
176
165
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
+
177
195
private safeDecode ( jwtToken : string ) : Promise < DecodedToken > {
178
196
return decodeJwt ( jwtToken )
179
197
. catch ( ( err ) => {
180
- if ( ! ( err instanceof JwtDecoderError ) ) {
181
- return Promise . reject ( err ) ;
198
+ if ( ! ( err instanceof JwtError ) ) {
199
+ throw err ;
182
200
}
183
- if ( err . code == JwtDecoderErrorCode . INVALID_ARGUMENT ) {
201
+ if ( err . code == JwtErrorCode . INVALID_ARGUMENT ) {
184
202
const verifyJwtTokenDocsMessage = ` See ${ this . tokenInfo . url } ` +
185
203
`for details on how to retrieve ${ this . shortNameArticle } ${ this . tokenInfo . shortName } .` ;
186
204
const errorMessage = `Decoding ${ this . tokenInfo . jwtName } failed. Make sure you passed ` +
187
205
`the entire string JWT which represents ${ this . shortNameArticle } ` +
188
206
`${ this . tokenInfo . shortName } .` + verifyJwtTokenDocsMessage ;
189
- return Promise . reject (
190
- new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , errorMessage ) ) ;
207
+ throw new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT ,
208
+ errorMessage ) ;
191
209
}
192
- return Promise . reject (
193
- new FirebaseAuthError ( AuthClientErrorCode . INTERNAL_ERROR , err . message ) ) ;
210
+ throw new FirebaseAuthError ( AuthClientErrorCode . INTERNAL_ERROR , err . message ) ;
194
211
} ) ;
195
212
}
196
213
197
- private validateToken (
214
+ private validateTokenContent (
198
215
fullDecodedToken : DecodedToken ,
199
216
projectId : string | null ,
200
217
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
- }
208
218
209
219
const header = fullDecodedToken && fullDecodedToken . header ;
210
220
const payload = fullDecodedToken && fullDecodedToken . payload ;
@@ -256,37 +266,30 @@ export class FirebaseTokenVerifier {
256
266
257
267
private verifySignature ( jwtToken : string , isEmulator : boolean ) :
258
268
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 )
267
271
. catch ( ( error ) => {
268
- return Promise . reject ( this . mapSignatureVerifierErrorToAuthError ( error ) ) ;
272
+ throw this . mapSignatureVerifierErrorToAuthError ( error ) ;
269
273
} ) ;
270
274
}
271
275
272
- private mapSignatureVerifierErrorToAuthError ( error : SignatureVerifierError ) : Error {
276
+ private mapSignatureVerifierErrorToAuthError ( error : JwtError ) : Error {
273
277
const verifyJwtTokenDocsMessage = ` See ${ this . tokenInfo . url } ` +
274
278
`for details on how to retrieve ${ this . shortNameArticle } ${ this . tokenInfo . shortName } .` ;
275
- if ( ! ( error instanceof SignatureVerifierError ) ) {
279
+ if ( ! ( error instanceof JwtError ) ) {
276
280
return ( error ) ;
277
281
}
278
- if ( error . code === SignatureVerifierErrorCode . TOKEN_EXPIRED ) {
282
+ if ( error . code === JwtErrorCode . TOKEN_EXPIRED ) {
279
283
const errorMessage = `${ this . tokenInfo . jwtName } has expired. Get a fresh ${ this . tokenInfo . shortName } ` +
280
284
` from your client app and try again (auth/${ this . tokenInfo . expiredErrorCode . code } ).` +
281
285
verifyJwtTokenDocsMessage ;
282
286
return new FirebaseAuthError ( this . tokenInfo . expiredErrorCode , errorMessage ) ;
283
287
}
284
- else if ( error . code === SignatureVerifierErrorCode . INVALID_TOKEN ) {
288
+ else if ( error . code === JwtErrorCode . INVALID_TOKEN ) {
285
289
const errorMessage = `${ this . tokenInfo . jwtName } has invalid signature.` + verifyJwtTokenDocsMessage ;
286
290
return new FirebaseAuthError ( AuthClientErrorCode . INVALID_ARGUMENT , errorMessage ) ;
287
291
}
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 ) {
290
293
const errorMessage = `${ this . tokenInfo . jwtName } has "kid" claim which does not ` +
291
294
`correspond to a known public key. Most likely the ${ this . tokenInfo . shortName } ` +
292
295
'is expired, so get a fresh token from your client app and try again.' ;
0 commit comments