Skip to main content

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:

  1. Revokes the user's refresh-token chain.
  2. Adds the active access token's jti (and its session group) to a Redis-backed blacklist so it can no longer access protected APIs.
  3. 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

FieldInRequiredDescription
AuthorizationheaderBearer <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_idbodyOAuth client ID. Must match the client that issued the tokens being revoked.
refresh_tokenbodyRefresh token to revoke (along with its rotation chain). Strongly recommended whenever available.
id_token_hintbodyThe 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_uribodyOptional 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"
}
FieldTypeDescription
successbooleantrue when the session was successfully ended (or already ended — RFC-style: revoking an unknown token returns 200).
messagestringHuman-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 presentEffect
refresh_tokenThe 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: BearerThe 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_hintThe 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_uriIf 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

HTTPCodeReason
400invalid_requestclient_id missing.
401unauthorized_clientSupplied token does not belong to the calling client_id.
403invalid_redirect_uripost_logout_redirect_uri is not registered for the client.
200success: falseLogout 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 only refresh_token will revoke the refresh chain but the access token remains usable until its natural exp.
  • 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_uri is not an open redirect: only pre-registered URIs are honoured.

Next steps