Skip to main content

Consent flow

OAuth2 and OpenID Connect require an authenticated End-User session for all OAuth2 / OpenID Connect flows except the client_credentials flow which does not involve End-Users.

Ory OAuth2 & OpenID Connect doesn't contain a database with End-Users but instead uses HTTP Redirection to "delegate" the login and consent flow to another app - we call this the Login & Consent App.

The following short video shows the flow from an End-User's perspective - it includes both login and consent.

info

Please read Login Flow first, as the Login Flow happens before the consent flow.

The following sequence diagram describes the different API calls and HTTP Redirects when performing the OAuth2 flow:

Once the login challenge is accepted, Ory OAuth2 & OpenID Connect will ask the user for consent:

OAuth 2.0 Asking for User Consent

The UI is fully under your control, because Ory OAuth2 & OpenID Connect redirects the End-User's browser to the "Consent Endpoint" established in your config:

hydra serve all -c path/to/hydra/config.yml
# Can also be set using the environment variable:
# URLS_CONSENT=https://consent-app/consent
urls:
consent: https://consent-app/consent
note

You can implement the Login and Consent endpoints in the same code base / application. Check out our Login, Consent & Logout Node.js Reference implementation!

Ory OAuth2 & OpenID Connect appends a consent_challenge query parameter to the url. The value is a ID which should later be used by the Consent Endpoint to fetch important information about the request.

https://consent-app/consent?consent_challenge=7bb518c4eec2454dbb289f5fdb4c0ee2

A consent has four distinctive attributes:

  • The requested scope (/oauth2/auth?...&...&scope=email+profile+offline_access);
  • The OAuth2 Client (/oauth2/auth?client_id=abcd);
  • The End-User (this is the subject set in the Login Flow);
  • Whether or not a previous consent exists, the previous consent has remember: true, and the scope granted by the user (did the user accept all of email, profile, offline_access?)

There are three possible states:

  • The user has never before authorized ("consent") the OAuth2 Client before.
  • The user has authorized ("consent") the OAuth2 Client before and chose to remember the "consent".
  • The user has authorized ("consent") the OAuth2 Client before, and chose to remember the "consent", but the OAuth2 Client now also wants additional permissions ("has changed the token scope" in /oauth2/auth?scope=...).

Regardless of which of these three states we're in, the End-User's browser is always redirected to the consent endpoint. What changes is the skip value, as explained a bit later.

note

In certain scenarios (for example a special OAuth2 Client) you might not want to show the consent screen at all. In those cases you can choose to skip showing the UI and just accept the consent. Please keep in mind that OAuth2 is a delegation protocol and that it makes most sense for third-party access. Not showing the consent screen will break OpenID Connect Certification.

The Consent Endpoint (set by urls.consent) is an application written by you. You can find an exemplary Node.js reference implementation on our GitHub.

The Consent Endpoint uses the consent_challenge value in the URL to fetch information about the consent request by making a HTTP GET request to:

http(s)://<HYDRA_ADMIN_URL>/oauth2/auth/requests/consent?consent_challenge=<challenge>

The response (see below in "Consent Challenge Response" tab) contains information about the consent request. The body contains a skip value. If the value is false, the user interface must be shown. If skip is true, you shouldn't show the user interface but instead just accept or reject the consent request! For more details about the implementation check the "Implementing the Consent Endpoint" Guide.

Exemplary OAuth 2.0 Consent Screen

The way you collect the consent information from the End-User is up to you. In most cases, you will show an HTML form similar to:

<form action="/consent" method="post">
<input type="hidden" name="csrf_token" value="...." />
<!-- Use CSRF tokens in your HTML forms! -->
<input type="checkbox" name="scope" />
</form>

To accept the Consent Challenge, make an HTTP PUT request with Content-Type: application/json and a JSON payload (see Accept Consent Request HTTP API Reference)

{
// The scope granted by the user.
"grant_scope": ["openid", "offline_access"],

// The following fields are optional

// The Access Token Audience if needed. Typically equals the `requested_access_token_audience` field from
// the consent challenge.
"grant_access_token_audience": ["https://my-audience.com"],

// Remember, if set to true, tells Ory OAuth2 & OpenID Connect to remember this consent authorization and reuse it if the same client
// asks the same user for the same, or a subset of, scope.
"remember": true,
// RememberFor sets how long the consent authorization should be remembered for in seconds. If set to 0, the
// authorization will be remembered indefinitely.
"remember_for": 3600,

// Set the data for this consent "session"
"session": {
"access_token": {
"foo": "This field will be available when introspecting the Access Token"
},
"id_token": {
"bar": "This field will be available as a claim in the ID Token"
}
}
}

With curl this might look like the following request:

curl --location --request PUT 'http://127.0.0.1:4445/oauth2/auth/requests/consent/accept?consent_challenge=7bb518c4eec2454dbb289f5fdb4c0ee2' \
--header 'Content-Type: application/json' \
--data-raw '{
"grant_scope": ["openid", "offline_access"]
}'

The server responds with JSON

{
"redirect_to": "http://127.0.0.1:4445/oauth2/auth..."
}

which is the URL your application must the End-User's browser to.

Check the "Implementing the Consent Endpoint" Guide for examples using the Ory OAuth2 & OpenID Connect SDK in different languages.

To reject the Login Challenge, make a HTTP PUT request with Content-Type: application/json and a JSON payload (see Reject Consent Request HTTP API Reference)

{
// The error should follow the OAuth2 error format (for example `invalid_request`, `login_required`).
"error": "user_banned",

// Description of the error in a human readable format.
"error_description": "You are banned!",

// Hint to help resolve the error.
"error_hint": "Contact the site administrator.",

// Debug contains information to help resolve the problem as a developer. Usually not exposed
// to the public but only in the server logs.
"error_debug": "The user was marked banned in the database.",

// Represents the HTTP status code of the error (for example 401 or 403)
//
// Defaults to 400
"status_code": 403
}

With curl this might look like the following request:

curl --location --request PUT 'http://127.0.0.1:4445/oauth2/auth/requests/consent/reject?consent_challenge=7bb518c4eec2454dbb289f5fdb4c0ee2' \
--header 'Content-Type: application/json' \
--data-raw '{
"error": "user_banned",
"error_debug": "The user was marked banned in the database.",
"error_description": "You are banned!",
"error_hint": "Contact the site administrator.",
"status_code": 403
}'

The server responds with JSON

{
"redirect_to": "http://127.0.0.1:4445/oauth2/auth..."
}

which is the URL your application must the End-User's browser to.

Check the "Implementing the Login Endpoint" Guide for examples using the Ory OAuth2 & OpenID Connect SDK in different languages.

You can revoke a user's consent either on a per application basis or for all applications.

Revoking the consent will automatically revoke all related access and refresh tokens.

danger

Don't use this endpoint to "invalidate" user sessions. Please revise your approach to and usage of OAuth2 if you use access / refresh tokens as user sessions (i.e. instead of browser cookies).

Revoking all consent sessions of a user is as easy as sending DELETE to /oauth2/auth/sessions/consent?subject={subject}.

Revoking the consent sessions of a user for a specific client is as easy as sending DELETE to /oauth2/auth/sessions/consent?subject={subject}&client={client}.