@@ -176,6 +176,82 @@ export interface DecodedIdToken {
176176 [ key : string ] : any ;
177177}
178178
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+
179255// Audience to use for Firebase Auth Custom tokens
180256const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit' ;
181257
@@ -201,6 +277,19 @@ export const ID_TOKEN_INFO: FirebaseTokenInfo = {
201277 expiredErrorCode : AuthClientErrorCode . ID_TOKEN_EXPIRED ,
202278} ;
203279
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+
204293/**
205294 * User facing token information related to the Firebase session cookie.
206295 *
@@ -320,6 +409,33 @@ export class FirebaseTokenVerifier {
320409 } ) ;
321410 }
322411
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+
323439 private ensureProjectId ( ) : Promise < string > {
324440 return util . findProjectId ( this . app )
325441 . then ( ( projectId ) => {
@@ -334,10 +450,14 @@ export class FirebaseTokenVerifier {
334450 } )
335451 }
336452
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 > {
338458 return this . safeDecode ( token )
339459 . then ( ( decodedToken ) => {
340- this . verifyContent ( decodedToken , projectId , isEmulator ) ;
460+ this . verifyContent ( decodedToken , projectId , isEmulator , audience ) ;
341461 return this . verifySignature ( token , isEmulator )
342462 . then ( ( ) => decodedToken ) ;
343463 } ) ;
@@ -369,7 +489,8 @@ export class FirebaseTokenVerifier {
369489 private verifyContent (
370490 fullDecodedToken : DecodedToken ,
371491 projectId : string | null ,
372- isEmulator : boolean ) : void {
492+ isEmulator : boolean ,
493+ audience : string | undefined ) : void {
373494 const header = fullDecodedToken && fullDecodedToken . header ;
374495 const payload = fullDecodedToken && fullDecodedToken . payload ;
375496
@@ -390,16 +511,19 @@ export class FirebaseTokenVerifier {
390511 errorMessage = `${ this . tokenInfo . verifyApiName } expects ${ this . shortNameArticle } ` +
391512 `${ this . tokenInfo . shortName } , but was given a legacy custom token.` ;
392513 } else {
393- errorMessage = 'Firebase ID token has no "kid" claim.' ;
514+ errorMessage = ` ${ this . tokenInfo . jwtName } has no "kid" claim.` ;
394515 }
395516
396517 errorMessage += verifyJwtTokenDocsMessage ;
397518 } else if ( ! isEmulator && header . alg !== ALGORITHM_RS256 ) {
398519 errorMessage = `${ this . tokenInfo . jwtName } has incorrect algorithm. Expected "` + ALGORITHM_RS256 + '" but got ' +
399520 '"' + header . alg + '".' + verifyJwtTokenDocsMessage ;
400- } else if ( payload . aud !== projectId ) {
521+ } else if ( typeof audience !== 'undefined' && ! ( payload . aud as string ) . includes ( audience ) ) {
401522 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 +
403527 verifyJwtTokenDocsMessage ;
404528 } else if ( payload . iss !== this . issuer + projectId ) {
405529 errorMessage = `${ this . tokenInfo . jwtName } has incorrect "iss" (issuer) claim. Expected ` +
@@ -470,6 +594,22 @@ export function createIdTokenVerifier(app: App): FirebaseTokenVerifier {
470594 ) ;
471595}
472596
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+
473613/**
474614 * Creates a new FirebaseTokenVerifier to verify Firebase session cookies.
475615 *
0 commit comments