Agent Wranglers

Recommended

Ask the provider for a fresh sign-in before sensitive changes

When a person who signs in through single sign-on makes a sensitive change, send them back to the provider for a fresh sign-in. Check the sign-in time it returns.

Applies when

The project it has user accounts.

How to do it

These people have no password for you to ask for. The provider may also answer at once from its own session, without asking anything. NIST SP 800-63B-4 makes your app, not the provider, decide whether that sign-in is recent enough. With OpenID Connect, send max_age=0 or prompt=login, which tells the provider to sign the person in again. Then read auth_time in the ID token and refuse it when it is missing or too old. The time window and the list of sensitive changes come from ask again before changing how someone logs in. When an organisation requires a second factor, also check acr or amr, as let organisations require a second factor says.

Examples

Using these examples: they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

JavaScript example

const FRESH_SIGN_IN_SECONDS = 5 * 60;

export const freshSignInUrl = (client, state, nonce) =>
  client.authorizationUrl({ scope: 'openid email', state, nonce, max_age: 0, prompt: 'login' });

export function assertFreshSignIn(claims, nowSeconds = Math.floor(Date.now() / 1000)) {
  if (typeof claims.auth_time !== 'number' || nowSeconds - claims.auth_time > FRESH_SIGN_IN_SECONDS) {
    throw new ForbiddenError('Please sign in again to make this change.');
  }
}

Python example

import time

FRESH_SIGN_IN_SECONDS = 5 * 60

def fresh_sign_in_url(client, state: str, nonce: str) -> str:
    return client.authorization_url(scope="openid email", state=state, nonce=nonce, max_age=0, prompt="login")

def assert_fresh_sign_in(claims: dict, now: float | None = None) -> None:
    auth_time = claims.get("auth_time")
    if not isinstance(auth_time, int) or (now or time.time()) - auth_time > FRESH_SIGN_IN_SECONDS:
        raise Forbidden("Please sign in again to make this change.")

How to check

Point staging at a test provider that ignores max_age and returns an auth_time from an hour ago. The sensitive change is refused. With a fresh auth_time, it goes through.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/accounts/business-sign-on-and-provisioning/sso-reauthentication">Agent Wranglers accounts playbook</a>. In code you write, add a comment next to it with the same link.