# Logout

> RP-initiated logout (GET|POST /api/oauth/logout) — end the user's EntryBit browser session with a verified id_token_hint, redirect to a registered post_logout_redirect_uri, and the forced-logout-CSRF defense.

RP-initiated logout ends the user's **EntryBit browser session** — the cookie the system browser holds after signing in. It is the OIDC `end_session_endpoint` ([OIDC RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)), advertised in [discovery](/docs/oauth/discovery-jwks/).

```http
GET  /api/oauth/logout
POST /api/oauth/logout
```

> **Two different logouts.** [Revoking the refresh token](/docs/oauth/introspection-revocation/) signs *your app* out (kills its tokens). This endpoint signs the *browser session* out, so the next `/authorize` re-prompts for a password instead of silently reusing the cookie. On shared devices you want both.

## Parameters

GET takes query parameters; POST takes the same fields in an `application/x-www-form-urlencoded` body.

| Parameter | Notes |
|---|---|
| `id_token_hint` | The `id_token` previously issued to the client. Its signature must verify (it **may** be expired). Required to actually terminate the session. |
| `post_logout_redirect_uri` | Where to send the browser afterward — must **exactly** match the client's registered post-logout list. |
| `state` | Opaque value echoed to the post-logout redirect. |
| `client_id` | Optional; when present it must match the hint's audience. |

## Behavior

The session is terminated **only** when the `id_token_hint`:

1. **verifies** — valid signature and issuer (expiry is *not* required); and
2. **matches** — its `sub` equals the currently signed-in user.

Then the browser is redirected to `post_logout_redirect_uri` **only if** it exactly matches one of the client's registered `post_logout_redirect_uris` (a separate allowlist from login redirect URIs — register them in [Settings → Team → OAuth apps](/docs/oauth/register-an-app/)). When it matches, `state` is appended. With no hint, an invalid hint, or an unregistered URI, the browser simply lands on the EntryBit home page.

```http
HTTP/1.1 302 Found
Location: entrybitresident://oauth/callback?state=<same>
```

## The forced-logout-CSRF defense

The `id_token_hint` requirement is a security control, not a formality. Without it, a bare cross-site `GET /api/oauth/logout` embedded in any web page could **force-log-out** a signed-in user (a logout CSRF). By requiring a **verified `id_token_hint` whose `sub` matches the current user**, EntryBit ensures only a party that already holds the user's own id_token can end their session — an attacker cannot.

## In a native app

Take the endpoint from [discovery](/docs/oauth/discovery-jwks/) (`end_session_endpoint`) rather than hardcoding the path:

```ts
import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';

const { endSessionEndpoint } = await AuthSession.fetchDiscoveryAsync('https://entrybit.net');
if (!endSessionEndpoint) throw new Error('end_session_endpoint not advertised');

const logoutUrl = `${endSessionEndpoint}?${new URLSearchParams({
  id_token_hint: idToken,                // the id_token from sign-in
  post_logout_redirect_uri: redirectUri, // must be REGISTERED as a post-logout URI
})}`;

await WebBrowser.openAuthSessionAsync(logoutUrl, redirectUri);
```

The simplest alternative on shared devices is to sign in with an **ephemeral browser session** in the first place, so no cookie ever persists:

```ts
promptAsync({ preferEphemeralSession: true }); // iOS: no shared Safari cookies
```

A complete sign-out does both: [revoke the refresh token](/docs/oauth/introspection-revocation/), clear local storage, then hit this endpoint (or use an ephemeral session). See the [quickstart](/docs/get-started/quickstart-react-native/).