-
Notifications
You must be signed in to change notification settings - Fork 130
Prompt and Max Age
OIDC Core 1.0 §3.1.2.1 defines two authorization parameters that control when the user must (re)authenticate: prompt and max_age. This page describes how the gem handles them and which configuration each value requires.
Both are handled on the authorization endpoint (GET /oauth/authorize) when the request is a valid OpenID Connect request, i.e. it includes the openid scope. Plain OAuth requests without openid ignore prompt unless you opt in via apply_prompt_to_non_oidc_requests; max_age enforcement is always OIDC-only, since it is defined by OIDC Core.
prompt is a space-delimited list. Multiple values are processed in the priority order none, consent, login, select_account; combining none with any other value is rejected with invalid_request, as required by the spec.
| Value | Behavior | Required configuration |
|---|---|---|
none |
No UI may be shown. Without a signed-in user the request fails with login_required; if the consent screen would be shown it fails with consent_required. When the user already has an active token for this client covering the requested scopes (a non-strict superset counts), authorization is auto-approved instead of rendering the consent form. |
— |
login |
Forces reauthentication of the current user by calling reauthenticate_resource_owner. |
reauthenticate_resource_owner |
consent |
Re-renders the consent screen even when it would normally be skipped (e.g. skip_authorization or an existing matching token). Under Doorkeeper's api_only mode the pre-authorization is answered as JSON instead (doorkeeper-openid_connect >= 2.0; older versions answered with an empty 200, see Troubleshooting). |
— |
select_account |
Triggers account selection by calling select_account_for_resource_owner. |
select_account_for_resource_owner |
create |
Accepted but not supported — treated as a no-op rather than an error. | — |
| anything else | Rejected with invalid_request. |
— |
The block is executed in the controller's scope and receives the resource owner and a return_to URL — a path-relative URL rebuilding the original authorization request (request path plus query string, not an absolute URL) with the just-handled prompt value removed, so that finishing the login doesn't trigger reauthentication again:
# config/initializers/doorkeeper_openid_connect.rb
Doorkeeper::OpenidConnect.configure do
# ...
reauthenticate_resource_owner do |resource_owner, return_to|
store_location_for resource_owner, return_to
sign_out resource_owner
redirect_to new_user_session_url
end
endThe block must render or redirect. If it completes without producing a response, the request fails with login_required — a deliberate backstop, since silently continuing would defeat the reauthentication the client asked for.
Same contract as above: controller scope, (resource_owner, return_to), and it must render or redirect — otherwise the request fails with account_selection_required (OIDC Core §3.1.2.6). Note that since v1.10.2 the block is also called when no user is signed in, with nil as the first argument, so it must handle both cases:
select_account_for_resource_owner do |resource_owner, return_to|
store_location_for(resource_owner, return_to) if resource_owner
redirect_to account_picker_url
endmax_age is the maximum number of seconds since the user last authenticated. When the user's authentication is older (or its time cannot be determined), the gem forces reauthentication through the same reauthenticate_resource_owner block — except under prompt=none, where showing UI is forbidden and the request fails with login_required instead.
The authentication time is resolved in this order:
-
auth_time_from_session— receives(session, request)in the controller's scope and returns the time the current session authenticated (returnnilto force reauthentication). This is the correct source when a user can hold multiple concurrent sessions. -
auth_time_from_resource_owner— legacy fallback receiving the resource owner. Deprecated formax_ageenforcement (it returns the most recent login on any device, so a stale session can inherit a fresh login from another one — see #150); a deprecation warning is emitted once per process. It remains fully supported for populating theauth_timeclaim on the ID Token.
A typical setup captures the time at login and surfaces it in the config:
# app/controllers/users/sessions_controller.rb (or your login handler)
session[:auth_time] = Time.current.to_i# config/initializers/doorkeeper_openid_connect.rb
Doorkeeper::OpenidConnect.configure do
# ...
auth_time_from_session do |session, _request|
session[:auth_time]
end
endDetails worth knowing:
-
max_ageis evaluated beforeprompthandling — so e.g.prompt=consentwith a stale authentication triggers reauthentication first, and the consent re-render happens on the follow-up request after login. -
max_age=0is treated as a floor of one second (clock-skew allowance), i.e. it effectively demands a just-now authentication. - A malformed, non-scalar
max_age(e.g.max_age[]=1) is treated as absent on doorkeeper-openid_connect >= 2.0; older versions raised a 500. -
auth_time_from_access_tokenonly affects theauth_timeclaim on the ID Token; it is never used formax_ageenforcement.
Failures are reported to the client as standard authentication error responses: a redirect to the redirect_uri carrying error (login_required, consent_required, account_selection_required, or invalid_request) and the client's state, in the query or fragment as appropriate for the response type. On doorkeeper-openid_connect >= 2.0, only these protocol errors are redirected to the client — internal problems such as a missing configuration surface as a 500 instead of leaking to the client as a spurious authorization error — and the RFC 9207 iss parameter is included on error redirects when Doorkeeper (6.0+) is configured with an issuer.
- Configuration — all settings mentioned on this page
- Troubleshooting — common integration pitfalls