Logout
This page helps you sign a user out of the auth server from your app, with the logout endpoint, /auth/logout.
It’s OpenID Connect’s RP-Initiated Logout 1.0 endpoint, the end_session_endpoint in the discovery document. What a sign-out ends, and what it leaves alone, is on Ending sessions.
Sign a user out
Section titled “Sign a user out”-
Keep the ID token the user signed in with. You send it back as the
id_token_hint, which tells the auth server who’s asking, so the user isn’t asked to confirm. -
Send the browser to
/auth/logoutwith the hint, where to come back to, and astate:GET /auth/logout?id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9...&post_logout_redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fsigned-out&state=af0ifjsldkj HTTP/1.1Host: auth.example.comThe return address must be one of the client’s registered redirect URIs, exactly.
-
The auth server signs the user out and sends the browser back, with your
state:HTTP/1.1 302 FoundLocation: https://myapp.example.com/signed-out?state=af0ifjsldkj -
Check that
stateis the one you sent, and end your app’s own session.
The request
Section titled “The request”/auth/logout takes a GET and a POST, with the same parameters. On a GET they go in the query string. On a POST they go in an application/x-www-form-urlencoded body, and the query string is read too, so send each parameter once.
| Parameter | Required | What it does |
|---|---|---|
id_token_hint |
Recommended | An ID token the auth server issued to your app, signed or encrypted. One it can confirm signs the user out with no question asked. It may have expired. |
post_logout_redirect_uri |
No | Where to send the browser afterwards. It must exactly match one of the client’s redirect URIs, and it’s honoured only on the terms in Where the user lands. |
client_id |
Sometimes | The client’s identifier. Required with an encrypted hint, to say whose secret decrypts it, and with a post_logout_redirect_uri sent without a hint. With a hint, it must match the hint’s aud. |
state |
No | Any value. It comes back on the redirect exactly as you sent it. |
ui_locales |
No | The languages for the pages the user sees, such as pt-BR en. |
There’s no separate list of post-logout redirect URIs: the client’s redirect URIs are the list.
Whether the user is asked
Section titled “Whether the user is asked”A sign-out the auth server can tie to your app happens at once. Any other asks the user first, with “Are you sure you want to sign out?”, as RP-Initiated Logout requires, and happens when they click Yes:
| Request | What happens |
|---|---|
| A GET or a POST with a hint the auth server confirms | The user is signed out at once |
| A GET with no hint, or with a hint it can’t confirm | The user is asked, then signed out |
| A POST with a hint it can’t confirm | 303 See Other to GET /auth/logout, which asks the user |
| A POST from another origin with no hint, another site or another host of the same domain | 403 Forbidden. See Sign-out answers 403. |
A hint that can’t be confirmed isn’t an error your app sees: it only means the user is asked. The 303 keeps post_logout_redirect_uri, state and ui_locales, and drops id_token_hint and client_id.
A POST is worth it when you send a hint, because it keeps the ID token out of the address bar, the browser’s history and the Referer header. A self-submitting form on your app’s page does it.
What a hint must pass
Section titled “What a hint must pass”The auth server confirms a hint when all of these hold:
- It isn’t empty.
- An encrypted hint comes with a
client_id, and decrypts with that client’s secret. - Its signature is the auth server’s, and it’s an ID token: an access token or a refresh token isn’t one.
- It has
subandiat,issis the auth server’s issuer, andaudis the identifier of a client that exists. - A
client_idsent beside it equals itsaud. An emptyclient_id=doesn’t. - Its
nbf, when there is one, has passed. - It has an
expand asid. Anexpthat has passed is fine while the sessionsidnames is still there, as RP-Initiated Logout recommends: a user who pauses before signing out shouldn’t be refused. - When the browser holds a session,
sidnames that one. - When the session
sidnames is still there, it belongs to the usersubnames.
The ID token from a user’s sign-in to your app passes all of these while the browser holds that session or none, until it has both expired and lost its session.
What it ends
Section titled “What it ends”With a confirmed hint, the auth server removes your app from the session the hint’s sid names. The session ends when your app was the last client on it, so the user stays signed in to the other apps sharing it. Without one, once the user clicks Yes, the browser’s whole session ends.
Either way, the browser’s session cookie is cleared, so its next sign-in starts with the password.
Where the user lands
Section titled “Where the user lands”The browser goes to your post_logout_redirect_uri with a 302 Found when:
- the request carries a hint the auth server confirmed, and the URI is a redirect URI of the client the hint’s
audnames; or - the request carries no hint, and the URI is a redirect URI of the client
client_idnames.
The auth server adds state and nothing else. It comes back byte for byte, +, /, =, # and & included; sent empty, it comes back empty, and not sent, there’s none. A query the registered URI already has is kept.
Otherwise the user lands on the auth server’s “Signed out” page, “You have been signed out.” When you sent a post_logout_redirect_uri that wasn’t honoured, it adds “We could not return you to the application that signed you out.” That’s the case for a URI that isn’t registered, a client_id that’s missing or names no client, and a hint that couldn’t be confirmed, even beside a valid client_id.
The sign-out has already happened by then: a request the auth server can’t honour in full still signs the user out.
Encrypting the hint
Section titled “Encrypting the hint”An encrypted hint keeps the ID token unreadable in logs and the browser’s history. It’s a JWE, the signed ID token encrypted as OpenID Connect Core 1.0 section 2 describes a nested JWT, with this protected header:
| Member | Value | What it means |
|---|---|---|
alg |
dir |
The key below is the content encryption key. There’s no encrypted key. |
enc |
A256GCM |
AES-256 in GCM mode, with a 96-bit IV and a 128-bit tag |
cty |
JWT |
The plaintext is a JWT. Optional: a header without it is accepted too. |
The key is the SHA-256 of the client’s secret, as UTF-8, which is 32 bytes. The JWE is in compact serialization, five parts with the second empty. A header with zip or crit is refused. Use a JOSE library for your platform rather than writing it yourself, and send client_id beside the hint.
Only a confidential client can encrypt a hint, since a public client has no secret.
Errors
Section titled “Errors”/auth/logout never sends your app an error. Apart from the 403 above, the one failure is the auth server’s own: when its database or session store fails, it shows an error page with 500 Internal Server Error, and the sign-out may not have happened.
Calling it from a browser
Section titled “Calling it from a browser”JavaScript can call /auth/logout across origins when its origin is registered as a web origin on a client. Sending the browser there with a link or a form needs nothing more.
The audit log
Section titled “The audit log”A sign-out with a confirmed hint leaves deleted_user_session_client in the audit log, for your app leaving the session, and also logout when yours was the session’s last client and the session ended. One without a hint leaves logout, even when there was no session, and deleted_user_session when there was one.