diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 05566205b5..73fc8f678c 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -529,13 +529,19 @@ export class FirebaseTokenVerifier { errorMessage = `${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` + `"${this.issuer}` + projectId + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; - } else if (typeof payload.sub !== 'string') { - errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; - } else if (payload.sub === '') { - errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + verifyJwtTokenDocsMessage; - } else if (payload.sub.length > 128) { - errorMessage = `${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` + - verifyJwtTokenDocsMessage; + } else if (!(payload.event_type !== undefined && + (payload.event_type === 'beforeSendSms' || payload.event_type === 'beforeSendEmail'))) { + // excluding `beforeSendSms` and `beforeSendEmail` from processing `sub` as there is no user record available. + // `sub` is the same as `uid` which is part of the user record. + if (typeof payload.sub !== 'string') { + errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; + } else if (payload.sub === '') { + errorMessage = `${this.tokenInfo.jwtName} has an empty "sub" (subject) claim.` + + verifyJwtTokenDocsMessage; + } else if (payload.sub.length > 128) { + errorMessage = `${this.tokenInfo.jwtName} has a "sub" (subject) claim longer than 128 characters.` + + verifyJwtTokenDocsMessage; + } } if (errorMessage) { throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 7abda8eb08..2d01678ef7 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -341,7 +341,8 @@ describe('FirebaseTokenVerifier', () => { }); return tokenVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('Firebase ID token has "sub" (subject) claim longer than 128 characters'); + .should.eventually.be.rejectedWith('Firebase ID token has a "sub" (subject) claim longer than 128 ' + + 'characters'); }); }); @@ -659,7 +660,7 @@ describe('FirebaseTokenVerifier', () => { return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) .should.eventually.be.rejectedWith( - 'Firebase Auth Blocking token has "sub" (subject) claim longer than 128 characters'); + 'Firebase Auth Blocking token has a "sub" (subject) claim longer than 128 characters'); }); }); @@ -780,5 +781,41 @@ describe('FirebaseTokenVerifier', () => { await authBlockingTokenVerifier._verifyAuthBlockingToken(idTokenNoHeader, false, undefined) .should.eventually.be.rejectedWith('Firebase Auth Blocking token has no "kid" claim.'); }); + + const eventTypesWithoutUid = ['beforeSendSms', 'beforeSendEmail']; + eventTypesWithoutUid.forEach((eventType) => { + it('should not throw error on invalid `sub` when event_type is "' + eventType + '"' , async () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .resolves(); + stubs.push(verifierStub); + + const mockAuthBlockingToken = mocks.generateAuthBlockingToken({ + subject: '' + }, { + event_type: eventType, + }); + return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) + .should.eventually.be.fulfilled; + }); + }); + + const eventTypesWithUid = ['beforeCreate', 'beforeSignIn', undefined]; + eventTypesWithUid.forEach((eventType) => { + it('should not throw error on invalid `sub` when event_type is "' + eventType + '"', async () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .resolves(); + stubs.push(verifierStub); + + const mockAuthBlockingToken = mocks.generateAuthBlockingToken({ + subject: '' + }, { + event_type: eventType, + }); + return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) + .should.eventually.be.rejectedWith('Firebase Auth Blocking token has an empty "sub" (subject) claim.' + + ' See https://cloud.google.com/identity-platform/docs/blocking-functions for details on how to retrieve an' + + ' Auth Blocking token.'); + }); + }); }); });