Skip to content

Commit

Permalink
authn: Support IdPs that backdate their "iat" claim in id tokens
Browse files Browse the repository at this point in the history
The backdating must be a fixed duration, which is what I've observed
with Azure AD (300s) and other IdPs.  Backdating is sometimes applied to
be more lenient with clients that have a slow clock (i.e. who otherwise
might see a correct iat as in the future and reject the token).

Without accounting for backdating, our staleBefore marker can cause a
temporary deauthentication and repeated renewal attempts as token
renewal requests "work" but produce a token with an iat that's still
older than the staleBefore.
  • Loading branch information
tsibley committed Oct 17, 2023
1 parent 582856c commit e84c672
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 3 deletions.
6 changes: 3 additions & 3 deletions src/authn/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { JOSEError, JWTClaimValidationFailed, JWTExpired } from 'jose/util/error
import partition from 'lodash.partition';
import BearerStrategy from './bearer.js';
import { getTokens, setTokens, deleteTokens } from './session.js';
import { PRODUCTION, OIDC_ISSUER_URL, OIDC_JWKS_URL, OAUTH2_AUTHORIZATION_URL, OAUTH2_TOKEN_URL, OAUTH2_LOGOUT_URL, OAUTH2_SCOPES_SUPPORTED, OIDC_USERNAME_CLAIM, OIDC_GROUPS_CLAIM, OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_CLI_CLIENT_ID } from '../config.js';
import { PRODUCTION, OIDC_ISSUER_URL, OIDC_JWKS_URL, OAUTH2_AUTHORIZATION_URL, OAUTH2_TOKEN_URL, OAUTH2_LOGOUT_URL, OAUTH2_SCOPES_SUPPORTED, OIDC_USERNAME_CLAIM, OIDC_GROUPS_CLAIM, OIDC_IAT_BACKDATED_BY, OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_CLI_CLIENT_ID } from '../config.js';
import { AuthnRefreshTokenInvalid, AuthnTokenTooOld } from '../exceptions.js';
import { fetch } from '../fetch.js';
import { copyCookie } from '../middleware.js';
Expand Down Expand Up @@ -716,8 +716,8 @@ async function verifyIdToken(idToken, client = OAUTH2_CLIENT_ID) {
if (typeof claims.iat !== "number") {
throw new JWTClaimValidationFailed(`"iat" claim must be a number`, "iat", "invalid");
}
if (claims.iat < staleBefore) {
throw new AuthnTokenTooOld(`"iat" claim less than user's staleBefore: ${claims.iat} < ${staleBefore}`);
if (claims.iat + OIDC_IAT_BACKDATED_BY < staleBefore) {
throw new AuthnTokenTooOld(`"iat" claim (plus any backdating) less than user's staleBefore: ${claims.iat} + ${OIDC_IAT_BACKDATED_BY} < ${staleBefore}`);
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,19 @@ export const OIDC_USERNAME_CLAIM = fromEnvOrConfig("OIDC_USERNAME_CLAIM");
export const OIDC_GROUPS_CLAIM = fromEnvOrConfig("OIDC_GROUPS_CLAIM");


/**
* Fixed time (in seconds) by which the IdP backdates the "iat" claim of the ID
* token.
*
* @type {number}
*/
export const OIDC_IAT_BACKDATED_BY = fromEnvOrConfig("OIDC_IAT_BACKDATED_BY", 0);

if (typeof OIDC_IAT_BACKDATED_BY !== 'number') {
throw new Error(`OIDC_IAT_BACKDATED_BY value is not a number; got "${OIDC_IAT_BACKDATED_BY}"`);
}


/**
* Path to a JSON file containing Groups data.
*
Expand Down

0 comments on commit e84c672

Please sign in to comment.