When You OIDC, use max_age=0 to Force Re-Auth

I was recently doing some work around the max_age=0 prompt, based on a spec update. However, I was curious as to why OpenId Connected needs both max_age=0 and prompt=login. This post outlines the findings, and explains best practices.

The Honor System is for Boy Scouts Link to heading

The sole reason max_age=0 exists (and must be clarified in the spec) is because of a significant OIDC shortcoming: prompt=login does not provide Relying Parties (RPs) a way to positively validate that a re-authentication took place.

Remember, that the prompt parameter is sent via a 302 redirect (through the browser) to the eventual destination. As such, the authn request is subject to tampering by the user agent or a malicious user. While OIDC provides mitigations against user agent tampering for most attack vectors (I.E. whitelisting redirect URI’s associated with individial client_id’s), the prompt paramter does not have a spec-defined validation mechanism.

Whoa, Why Don’t Apps Usually Need to Validate the Prompt Parameter? Link to heading

Good question! Typically, the prompt parameter is used by clients to enhance user experience. The primary place this author has seen it used in the wild is for signaling prompt=none for embedded iframes providing background calls for SPA’s. This was codified (but never moved past draft state) in the Web Message Response Mode spec.

prompt=select_account also falls into a usability category. If an end user wants to remove the parameter signaling the Authorization Server (AS) to present a select account page, he or she is not adversely affecting the authentication operation.

Validate Re-Auth Occurred Link to heading

Instead of using prompt=login, and hoping the request goes from the RP to the AS unmodified, the correct way to ensure users re-authenticated is to use max_age=0. This is because the spec states:

When max_age is used, the ID Token returned MUST include an auth_time Claim Value.

So, this means that clients should get back a claim that looks like this:

{
    auth_time: 29572958,
}

Now RP’s have the power to validate that a re-auth has taken place within a reasonable time period!