@@ -176,6 +176,82 @@ export interface DecodedIdToken {
176
176
[ key : string ] : any ;
177
177
}
178
178
179
+ /** @alpha */
180
+ export interface DecodedAuthBlockingSharedUserInfo {
181
+ uid : string ;
182
+ display_name ?: string ;
183
+ email ?: string ;
184
+ photo_url ?: string ;
185
+ phone_number ?: string ;
186
+ }
187
+
188
+ /** @alpha */
189
+ export interface DecodedAuthBlockingMetadata {
190
+ creation_time ?: number ;
191
+ last_sign_in_time ?: number ;
192
+ }
193
+
194
+ /** @alpha */
195
+ export interface DecodedAuthBlockingUserInfo extends DecodedAuthBlockingSharedUserInfo {
196
+ provider_id : string ;
197
+ }
198
+
199
+ /** @alpha */
200
+ export interface DecodedAuthBlockingMfaInfo {
201
+ uid : string ;
202
+ display_name ?: string ;
203
+ phone_number ?: string ;
204
+ enrollment_time ?: string ;
205
+ factor_id ?: string ;
206
+ }
207
+
208
+ /** @alpha */
209
+ export interface DecodedAuthBlockingEnrolledFactors {
210
+ enrolled_factors ?: DecodedAuthBlockingMfaInfo [ ] ;
211
+ }
212
+
213
+ /** @alpha */
214
+ export interface DecodedAuthBlockingUserRecord extends DecodedAuthBlockingSharedUserInfo {
215
+ email_verified ?: boolean ;
216
+ disabled ?: boolean ;
217
+ metadata ?: DecodedAuthBlockingMetadata ;
218
+ password_hash ?: string ;
219
+ password_salt ?: string ;
220
+ provider_data ?: DecodedAuthBlockingUserInfo [ ] ;
221
+ multi_factor ?: DecodedAuthBlockingEnrolledFactors ;
222
+ custom_claims ?: any ;
223
+ tokens_valid_after_time ?: number ;
224
+ tenant_id ?: string ;
225
+ [ key : string ] : any ;
226
+ }
227
+
228
+ /** @alpha */
229
+ export interface DecodedAuthBlockingToken {
230
+ aud : string ;
231
+ exp : number ;
232
+ iat : number ;
233
+ iss : string ;
234
+ sub : string ;
235
+ event_id : string ;
236
+ event_type : string ;
237
+ ip_address : string ;
238
+ user_agent ?: string ;
239
+ locale ?: string ;
240
+ sign_in_method ?: string ;
241
+ user_record ?: DecodedAuthBlockingUserRecord ;
242
+ tenant_id ?: string ;
243
+ raw_user_info ?: string ;
244
+ sign_in_attributes ?: {
245
+ [ key : string ] : any ;
246
+ } ;
247
+ oauth_id_token ?: string ;
248
+ oauth_access_token ?: string ;
249
+ oauth_refresh_token ?: string ;
250
+ oauth_token_secret ?: string ;
251
+ oauth_expires_in ?: number ;
252
+ [ key : string ] : any ;
253
+ }
254
+
179
255
// Audience to use for Firebase Auth Custom tokens
180
256
const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit' ;
181
257
@@ -201,6 +277,19 @@ export const ID_TOKEN_INFO: FirebaseTokenInfo = {
201
277
expiredErrorCode : AuthClientErrorCode . ID_TOKEN_EXPIRED ,
202
278
} ;
203
279
280
+ /**
281
+ * User facing token information related to the Firebase Auth Blocking token.
282
+ *
283
+ * @internal
284
+ */
285
+ export const AUTH_BLOCKING_TOKEN_INFO : FirebaseTokenInfo = {
286
+ url : 'https://cloud.google.com/identity-platform/docs/blocking-functions' ,
287
+ verifyApiName : '_verifyAuthBlockingToken()' ,
288
+ jwtName : 'Firebase Auth Blocking token' ,
289
+ shortName : 'Auth Blocking token' ,
290
+ expiredErrorCode : AuthClientErrorCode . AUTH_BLOCKING_TOKEN_EXPIRED ,
291
+ } ;
292
+
204
293
/**
205
294
* User facing token information related to the Firebase session cookie.
206
295
*
@@ -320,6 +409,33 @@ export class FirebaseTokenVerifier {
320
409
} ) ;
321
410
}
322
411
412
+ /** @alpha */
413
+ // eslint-disable-next-line @typescript-eslint/naming-convention
414
+ public _verifyAuthBlockingToken (
415
+ jwtToken : string ,
416
+ isEmulator : boolean ,
417
+ audience : string | undefined ) : Promise < DecodedAuthBlockingToken > {
418
+ if ( ! validator . isString ( jwtToken ) ) {
419
+ throw new FirebaseAuthError (
420
+ AuthClientErrorCode . INVALID_ARGUMENT ,
421
+ `First argument to ${ this . tokenInfo . verifyApiName } must be a ${ this . tokenInfo . jwtName } string.` ,
422
+ ) ;
423
+ }
424
+
425
+ return this . ensureProjectId ( )
426
+ . then ( ( projectId ) => {
427
+ if ( typeof audience === 'undefined' ) {
428
+ audience = `${ projectId } .cloudfunctions.net/` ;
429
+ }
430
+ return this . decodeAndVerify ( jwtToken , projectId , isEmulator , audience ) ;
431
+ } )
432
+ . then ( ( decoded ) => {
433
+ const decodedAuthBlockingToken = decoded . payload as DecodedAuthBlockingToken ;
434
+ decodedAuthBlockingToken . uid = decodedAuthBlockingToken . sub ;
435
+ return decodedAuthBlockingToken ;
436
+ } ) ;
437
+ }
438
+
323
439
private ensureProjectId ( ) : Promise < string > {
324
440
return util . findProjectId ( this . app )
325
441
. then ( ( projectId ) => {
@@ -334,10 +450,14 @@ export class FirebaseTokenVerifier {
334
450
} )
335
451
}
336
452
337
- private decodeAndVerify ( token : string , projectId : string , isEmulator : boolean ) : Promise < DecodedToken > {
453
+ private decodeAndVerify (
454
+ token : string ,
455
+ projectId : string ,
456
+ isEmulator : boolean ,
457
+ audience ?: string ) : Promise < DecodedToken > {
338
458
return this . safeDecode ( token )
339
459
. then ( ( decodedToken ) => {
340
- this . verifyContent ( decodedToken , projectId , isEmulator ) ;
460
+ this . verifyContent ( decodedToken , projectId , isEmulator , audience ) ;
341
461
return this . verifySignature ( token , isEmulator )
342
462
. then ( ( ) => decodedToken ) ;
343
463
} ) ;
@@ -369,7 +489,8 @@ export class FirebaseTokenVerifier {
369
489
private verifyContent (
370
490
fullDecodedToken : DecodedToken ,
371
491
projectId : string | null ,
372
- isEmulator : boolean ) : void {
492
+ isEmulator : boolean ,
493
+ audience : string | undefined ) : void {
373
494
const header = fullDecodedToken && fullDecodedToken . header ;
374
495
const payload = fullDecodedToken && fullDecodedToken . payload ;
375
496
@@ -390,16 +511,19 @@ export class FirebaseTokenVerifier {
390
511
errorMessage = `${ this . tokenInfo . verifyApiName } expects ${ this . shortNameArticle } ` +
391
512
`${ this . tokenInfo . shortName } , but was given a legacy custom token.` ;
392
513
} else {
393
- errorMessage = 'Firebase ID token has no "kid" claim.' ;
514
+ errorMessage = ` ${ this . tokenInfo . jwtName } has no "kid" claim.` ;
394
515
}
395
516
396
517
errorMessage += verifyJwtTokenDocsMessage ;
397
518
} else if ( ! isEmulator && header . alg !== ALGORITHM_RS256 ) {
398
519
errorMessage = `${ this . tokenInfo . jwtName } has incorrect algorithm. Expected "` + ALGORITHM_RS256 + '" but got ' +
399
520
'"' + header . alg + '".' + verifyJwtTokenDocsMessage ;
400
- } else if ( payload . aud !== projectId ) {
521
+ } else if ( typeof audience !== 'undefined' && ! ( payload . aud as string ) . includes ( audience ) ) {
401
522
errorMessage = `${ this . tokenInfo . jwtName } has incorrect "aud" (audience) claim. Expected "` +
402
- projectId + '" but got "' + payload . aud + '".' + projectIdMatchMessage +
523
+ audience + '" but got "' + payload . aud + '".' + verifyJwtTokenDocsMessage ;
524
+ } else if ( typeof audience === 'undefined' && payload . aud !== projectId ) {
525
+ errorMessage = `${ this . tokenInfo . jwtName } has incorrect "aud" (audience) claim. Expected "` +
526
+ projectId + '" but got "' + payload . aud + '".' + projectIdMatchMessage +
403
527
verifyJwtTokenDocsMessage ;
404
528
} else if ( payload . iss !== this . issuer + projectId ) {
405
529
errorMessage = `${ this . tokenInfo . jwtName } has incorrect "iss" (issuer) claim. Expected ` +
@@ -470,6 +594,22 @@ export function createIdTokenVerifier(app: App): FirebaseTokenVerifier {
470
594
) ;
471
595
}
472
596
597
+ /**
598
+ * Creates a new FirebaseTokenVerifier to verify Firebase Auth Blocking tokens.
599
+ *
600
+ * @internal
601
+ * @param app - Firebase app instance.
602
+ * @returns FirebaseTokenVerifier
603
+ */
604
+ export function createAuthBlockingTokenVerifier ( app : App ) : FirebaseTokenVerifier {
605
+ return new FirebaseTokenVerifier (
606
+ CLIENT_CERT_URL ,
607
+ 'https://securetoken.google.com/' ,
608
+ AUTH_BLOCKING_TOKEN_INFO ,
609
+ app
610
+ ) ;
611
+ }
612
+
473
613
/**
474
614
* Creates a new FirebaseTokenVerifier to verify Firebase session cookies.
475
615
*
0 commit comments