diff --git a/src/auth.ts b/src/auth.ts index a191722..6fb94d5 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -54,14 +54,15 @@ export class BaseAuth { * @param env - An optional parameter specifying the environment in which the function is running. * If the function is running in an emulator environment, this should be set to `EmulatorEnv`. * If not specified, the function will assume it is running in a production environment. - * + * @param clockSkewSeconds - The number of seconds to tolerate when checking the `iat`. + * This is to deal with small clock differences among different servers. * @returns A promise fulfilled with the * token's decoded claims if the ID token is valid; otherwise, a rejected * promise. */ - public async verifyIdToken(idToken: string, checkRevoked = false, env?: EmulatorEnv): Promise { + public async verifyIdToken(idToken: string, checkRevoked = false, env?: EmulatorEnv, clockSkewSeconds?: number): Promise { const isEmulator = useEmulator(env); - const decodedIdToken = await this.idTokenVerifier.verifyJWT(idToken, isEmulator); + const decodedIdToken = await this.idTokenVerifier.verifyJWT(idToken, isEmulator, clockSkewSeconds); // Whether to check if the token was revoked. if (checkRevoked) { return await this.verifyDecodedJWTNotRevokedOrDisabled(decodedIdToken, AuthClientErrorCode.ID_TOKEN_REVOKED, env); diff --git a/src/token-verifier.ts b/src/token-verifier.ts index 87ee96a..83e0ee6 100644 --- a/src/token-verifier.ts +++ b/src/token-verifier.ts @@ -226,23 +226,31 @@ export class FirebaseTokenVerifier { * * @param jwtToken - The Firebase Auth JWT token to verify. * @param isEmulator - Whether to accept Auth Emulator tokens. + * @param clockSkewSeconds - The number of seconds to tolerate when checking the token's iat. Must be between 0-60, and an integer. Defualts to 0. * @returns A promise fulfilled with the decoded claims of the Firebase Auth ID token. */ - public verifyJWT(jwtToken: string, isEmulator = false): Promise { + public verifyJWT(jwtToken: string, isEmulator = false, clockSkewSeconds: number = 5): Promise { if (!isString(jwtToken)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, `First argument to ${this.tokenInfo.verifyApiName} must be a ${this.tokenInfo.jwtName} string.` ); } - return this.decodeAndVerify(jwtToken, isEmulator).then(payload => { + + if (clockSkewSeconds < 0 || clockSkewSeconds > 60 || !Number.isInteger(clockSkewSeconds)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + 'clockSkewSeconds must be an integer between 0 and 60.' + ) + } + return this.decodeAndVerify(jwtToken, isEmulator, clockSkewSeconds).then(payload => { payload.uid = payload.sub; return payload; }); } - private async decodeAndVerify(token: string, isEmulator: boolean): Promise { - const currentTimestamp = Math.floor(Date.now() / 1000); + private async decodeAndVerify(token: string, isEmulator: boolean, clockSkewSeconds: number = 5): Promise { + const currentTimestamp = Math.floor(Date.now() / 1000) + clockSkewSeconds; try { const rs256Token = this.safeDecode(token, isEmulator, currentTimestamp); const { payload } = rs256Token.decodedToken;