RP-Initiated Logout
ChainIT implements the OpenID Connect RP-Initiated Logout 1.0 specification combined with RFC 7009 revocation. A single call to the logout endpoint:
- Revokes the user's refresh-token chain.
- Adds the active access token's
jti(and its session group) to a Redis-backed blacklist so it can no longer access protected APIs. - Optionally redirects the user to a registered
post_logout_redirect_uri.
This is the recommended way to end an interactive session — it is symmetric with the Hosted UI SDK flow, where useLogout() / HostedAuth.logout() wrap this exact endpoint.
Endpoint
POST https://api.chainit.online/oauth/logout
Content-Type: application/json.
The URL is advertised by the server metadata document as end_session_endpoint.
Request
| Field | In | Required | Description |
|---|---|---|---|
Authorization | header | – | Bearer <access_token>. When supplied, the server blacklists this access token's jti (and session group) so it cannot be re-used until its natural exp. |
client_id | body | ✅ | OAuth client ID. Must match the client that issued the tokens being revoked. |
refresh_token | body | – | Refresh token to revoke (along with its rotation chain). Strongly recommended whenever available. |
id_token_hint | body | – | The ID token previously issued to the user. Helps the server identify the session and enforce post_logout_redirect_uri against the originating client. |
post_logout_redirect_uri | body | – | Optional URL the IdP may redirect to after logout. Must be pre-registered on the application; otherwise it is ignored. |
Example
POST /oauth/logout
Authorization: Bearer eyJhbGciOiJSUzI1NiI...
Content-Type: application/json
{
"client_id": "<<your_client_id>>",
"refresh_token": "1f2c....b1ad.s3cret-base64url",
"id_token_hint": "eyJhbGciOiJSUzI1NiI...",
"post_logout_redirect_uri": "https://app.example.com/signed-out"
}
Response
{
"success": true,
"message": "Logged out successfully"
}
| Field | Type | Description |
|---|---|---|
success | boolean | true when the session was successfully ended (or already ended — RFC-style: revoking an unknown token returns 200). |
message | string | Human-readable status suitable for logging. |
Even when the server-side outcome is success: false (for example, the supplied refresh token had already been rotated out), clients should still clear local token storage so the user is signed out from the device.
What actually happens server-side
| Input present | Effect |
|---|---|
refresh_token | The presented refresh token plus its full rotation chain is marked revoked. Subsequent refresh attempts using any chain member return invalid_grant and the chain stays blacklisted. |
Authorization: Bearer | The access token's jti and groupId are added to the Redis blacklist until the token's natural exp. The guard rejects every subsequent request that presents the same token. |
id_token_hint | The server validates the ID token signature, extracts aud / sid, and uses them to scope the logout to the right session. It is also used to enforce post_logout_redirect_uri against the originating client's registered list. |
post_logout_redirect_uri | If pre-registered for the client, the IdP may return a 302 to that URL. Otherwise the field is ignored and a JSON response is returned. |
The endpoint is idempotent — calling it again after a successful logout is safe and returns success: true.
Sample integrations
Hosted UI SDK (preferred)
import { useLogout } from "@chainitservices/hosted-ui";
function SignOutButton() {
const logout = useLogout();
return <button onClick={() => logout()}>Sign out</button>;
}
The SDK reads clientId from HostedAuthProvider config, automatically attaches the stored access token as Authorization: Bearer, sends refresh_token + id_token_hint from local storage, and clears local storage afterwards. See the Hosted UI guide.
curl
curl -X POST "https://api.chainit.online/oauth/logout" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"client_id": "<<your_client_id>>",
"refresh_token": "<<your_refresh_token>>",
"id_token_hint": "<<your_id_token>>"
}'
Node.js
import axios from "axios";
await axios.post(
"https://api.chainit.online/oauth/logout",
{
client_id: process.env.CLIENT_ID,
refresh_token: refreshToken,
id_token_hint: idToken,
},
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
Python
import requests
requests.post(
"https://api.chainit.online/oauth/logout",
headers={"Authorization": f"Bearer {access_token}"},
json={
"client_id": CLIENT_ID,
"refresh_token": refresh_token,
"id_token_hint": id_token,
},
)
Errors
| HTTP | Code | Reason |
|---|---|---|
| 400 | invalid_request | client_id missing. |
| 401 | unauthorized_client | Supplied token does not belong to the calling client_id. |
| 403 | invalid_redirect_uri | post_logout_redirect_uri is not registered for the client. |
| 200 | success: false | Logout completed but one or more tokens were already revoked/unknown. |
Security considerations
- Always call logout on the server side when ending a user session whose tokens are held by a backend (e.g. server-rendered apps, mobile-native back-ends). Public-client logout is best-effort.
- Always send
Authorization: Bearer <access_token>if you have it — this is what blacklists the live access token. Sending onlyrefresh_tokenwill revoke the refresh chain but the access token remains usable until its naturalexp. - After logout, redirect the user to a public route. Any client-side router guard that reads
tokenStorage(or its equivalent) should observe the cleared state and rerender the signed-out experience. post_logout_redirect_uriis not an open redirect: only pre-registered URIs are honoured.