Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve token verification logic with Auth Emulator. #1148

Merged
merged 11 commits into from
Feb 4, 2021
Prev Previous commit
Next Next commit
Call useEmulator() only once.
yuchenshi committed Feb 1, 2021
commit 159e0b4943d05e56617b7ae726486fd8cc992dbe
10 changes: 6 additions & 4 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -115,10 +115,11 @@ export class BaseAuth<T extends AbstractAuthRequestHandler> implements BaseAuthI
* verification.
*/
public verifyIdToken(idToken: string, checkRevoked = false): Promise<DecodedIdToken> {
return this.idTokenVerifier.verifyJWT(idToken)
const isEmulator = useEmulator();
return this.idTokenVerifier.verifyJWT(idToken, isEmulator)
.then((decodedIdToken: DecodedIdToken) => {
// Whether to check if the token was revoked.
if (checkRevoked || useEmulator()) {
if (checkRevoked || isEmulator) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuchenshi can you give any context around why the verifyIdToken method always calls verifyDecodedJWTNotRevokedOrDisabled if using emulator please?

I'm in the position where I'm working with uid's that may/may not exist in Firebase.

I want to verify a token (which succeeds in prod) but the emulator checks whether there's a user and whether its been disabled regardless of the value of checkRevoked .

If this isn't intended I can create a PR.

TIA

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lprhodes Tokens from the Auth Emulator aren't really "verifiable" since they are not signed (compared to production), so the closest approximation we can do is to ask the Auth Emulator if they exist. It sounds like you have a specific workflow in mind that you want to emulate and please open a new issue with more context (especially why you'd like to verify tokens from non-existent users) and we'll see what we can do.

return this.verifyDecodedJWTNotRevoked(
decodedIdToken,
AuthClientErrorCode.ID_TOKEN_REVOKED);
@@ -443,10 +444,11 @@ export class BaseAuth<T extends AbstractAuthRequestHandler> implements BaseAuthI
*/
public verifySessionCookie(
sessionCookie: string, checkRevoked = false): Promise<DecodedIdToken> {
return this.sessionCookieVerifier.verifyJWT(sessionCookie)
const isEmulator = useEmulator();
return this.sessionCookieVerifier.verifyJWT(sessionCookie, isEmulator)
.then((decodedIdToken: DecodedIdToken) => {
// Whether to check if the token was revoked.
if (checkRevoked || useEmulator()) {
if (checkRevoked || isEmulator) {
return this.verifyDecodedJWTNotRevoked(
decodedIdToken,
AuthClientErrorCode.SESSION_COOKIE_REVOKED);
20 changes: 12 additions & 8 deletions src/auth/token-verifier.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,6 @@ import { FirebaseApp } from '../firebase-app';
import { auth } from './index';

import DecodedIdToken = auth.DecodedIdToken;
import { useEmulator } from './auth-api-request';

// Audience to use for Firebase Auth Custom tokens
const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
@@ -136,10 +135,11 @@ export class FirebaseTokenVerifier {
* Verifies the format and signature of a Firebase Auth JWT token.
*
* @param {string} jwtToken The Firebase Auth JWT token to verify.
* @param {boolean} isEmulator Whether to accept Auth Emulator tokens.
* @return {Promise<DecodedIdToken>} A promise fulfilled with the decoded claims of the Firebase Auth ID
* token.
*/
public verifyJWT(jwtToken: string): Promise<DecodedIdToken> {
public verifyJWT(jwtToken: string, isEmulator: boolean): Promise<DecodedIdToken> {
if (!validator.isString(jwtToken)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
@@ -149,11 +149,15 @@ export class FirebaseTokenVerifier {

return util.findProjectId(this.app)
.then((projectId) => {
return this.verifyJWTWithProjectId(jwtToken, projectId);
return this.verifyJWTWithProjectId(jwtToken, projectId, isEmulator);
});
}

private verifyJWTWithProjectId(jwtToken: string, projectId: string | null): Promise<DecodedIdToken> {
private verifyJWTWithProjectId(
jwtToken: string,
projectId: string | null,
isEmulator: boolean
): Promise<DecodedIdToken> {
if (!validator.isNonEmptyString(projectId)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_CREDENTIAL,
@@ -178,7 +182,7 @@ export class FirebaseTokenVerifier {
if (!fullDecodedToken) {
errorMessage = `Decoding ${this.tokenInfo.jwtName} failed. Make sure you passed the entire string JWT ` +
`which represents ${this.shortNameArticle} ${this.tokenInfo.shortName}.` + verifyJwtTokenDocsMessage;
} else if (!useEmulator() && typeof header.kid === 'undefined') {
} else if (!isEmulator && typeof header.kid === 'undefined') {
const isCustomToken = (payload.aud === FIREBASE_AUDIENCE);
const isLegacyCustomToken = (header.alg === 'HS256' && payload.v === 0 && 'd' in payload && 'uid' in payload.d);

@@ -193,7 +197,7 @@ export class FirebaseTokenVerifier {
}

errorMessage += verifyJwtTokenDocsMessage;
} else if (!useEmulator() && header.alg !== this.algorithm) {
} else if (!isEmulator && header.alg !== this.algorithm) {
errorMessage = `${this.tokenInfo.jwtName} has incorrect algorithm. Expected "` + this.algorithm + '" but got ' +
'"' + header.alg + '".' + verifyJwtTokenDocsMessage;
} else if (payload.aud !== projectId) {
@@ -216,7 +220,7 @@ export class FirebaseTokenVerifier {
return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage));
}

if (useEmulator()) {
if (isEmulator) {
// Signature checks skipped for emulator; no need to fetch public keys.
return this.verifyJwtSignatureWithKey(jwtToken, null);
}
@@ -250,7 +254,7 @@ export class FirebaseTokenVerifier {
`for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`;
return new Promise((resolve, reject) => {
const verifyOptions: jwt.VerifyOptions = {};
if (!useEmulator()) {
if (publicKey !== null) {
verifyOptions.algorithms = [this.algorithm];
}
jwt.verify(jwtToken, publicKey || '', verifyOptions,