# OAuth 2.0 & OpenID Connect

> EntryBit is a full OAuth 2.0 authorization server and OpenID Connect provider — authorization-code flow with PKCE (S256) required for every client. The model, the flow, and the tokens you get.

EntryBit is a full **OAuth 2.0 authorization server** and **OpenID Connect provider**. Apps that sign users in — mobile apps, SPAs, server-rendered web apps — use the **authorization-code flow with PKCE**. There is no implicit grant, no password grant, and no `plain` PKCE: **PKCE S256 is mandatory for every client**, public or confidential.

- **Issuer:** `https://entrybit.net`
- **Discovery:** `GET /.well-known/openid-configuration` — most OIDC libraries configure themselves from this one URL
- **Signing:** RS256; public keys at [`/.well-known/jwks.json`](/docs/oauth/discovery-jwks/)

## Why authorization-code + PKCE

The authorization code flow keeps tokens off the front channel: the browser only ever carries a short-lived, single-use **code**, which your app exchanges for tokens over a direct back-channel call. **PKCE** ([RFC 7636](https://www.rfc-editor.org/rfc/rfc7636)) binds that code to a per-request secret (`code_verifier`) that only your app holds — so even if a hostile app on the device intercepts the redirected code, it cannot exchange it. This is why a public client (a mobile app or SPA with no secret) is safe: **PKCE replaces the client secret.**

## The flow, step by step

1. **Discover.** Fetch `/.well-known/openid-configuration` to find the authorize, token, userinfo, and logout endpoints.
2. **Generate PKCE.** Create a random `code_verifier`, then `code_challenge = BASE64URL(SHA-256(code_verifier))`.
3. **Authorize.** Open [`GET /api/oauth/authorize`](/docs/oauth/authorize/) in the **system browser** with `response_type=code`, your `client_id`, `redirect_uri`, `scope`, `state`, `code_challenge`, and `code_challenge_method=S256`.
4. **User signs in.** EntryBit runs login (password, passkey, Google, 2FA) and the consent screen — all inside the browser. Your app never sees the password.
5. **Redirect back.** On success the browser is 302-redirected to your `redirect_uri` with `code`, `state`, and **`iss`** ([RFC 9207](https://www.rfc-editor.org/rfc/rfc9207)).
6. **Exchange the code.** Call [`POST /api/oauth/token`](/docs/oauth/token/) with `grant_type=authorization_code`, the `code`, the same `redirect_uri`, your `client_id`, and the `code_verifier`.
7. **Get tokens.** Receive an access token (always), plus an `id_token` (with `openid`) and a `refresh_token` (with `offline_access`).
8. **Call the API.** Send `Authorization: Bearer <access_token>` to [`/api/v1/*`](/docs/api-reference/passes/).
9. **Refresh.** When the access token nears expiry, exchange the rotating refresh token for a fresh pair.

```
 App                     System Browser                EntryBit
  │  authorize (S256) ──────►│───────────────────────────►│  login + 2FA + consent
  │                          │◄───────────────────────────│  302 → redirect_uri?code&state&iss
  │◄─────────────────────────│  deep link back
  │  token (code + verifier) ───────────────────────────► │
  │◄──────────────────  { access_token, refresh_token?, id_token? }
  │  GET /api/v1/*  (Bearer access_token) ──────────────► │
```

## What you get back

| Token | Format | Lifetime | Purpose |
|---|---|---|---|
| **access_token** | RS256 JWT (`typ: at+jwt`) | ~15 min | Call `/api/v1/*` and `/api/oauth/userinfo`. `aud` = your `client_id`. |
| **id_token** | RS256 JWT (`typ: JWT`) | — | *Who* signed in and *how* (`sub`, `amr`, `auth_time`, identity claims). Issued with `openid`. |
| **refresh_token** | Opaque, rotating | 60 days | Obtain fresh access tokens without re-prompting. Issued with `offline_access`. |

Access tokens and id_tokens verify **offline** against the [JWKS](/docs/oauth/discovery-jwks/); refresh tokens are opaque and single-use — they **rotate** on every exchange, so always persist the newest.

## Client types

- **Public** (mobile apps, SPAs) — no secret. PKCE is the proof. This is the common case.
- **Confidential** (web apps with a secure backend) — a `client_secret` in addition to PKCE, enabling [Introspection](/docs/oauth/introspection-revocation/).

Register either in **Settings → Team → OAuth apps**. See [Register an app](/docs/oauth/register-an-app/).

## Endpoint reference

| Page | Endpoint |
|---|---|
| [Register an app](/docs/oauth/register-an-app/) | Self-service client registration |
| [Scopes](/docs/oauth/scopes/) | What you can request at consent |
| [Authorize endpoint](/docs/oauth/authorize/) | `GET /api/oauth/authorize` |
| [Token endpoint](/docs/oauth/token/) | `POST /api/oauth/token` |
| [ID token & UserInfo](/docs/oauth/id-token-userinfo/) | `id_token` claims + `GET\|POST /api/oauth/userinfo` |
| [Introspection & Revocation](/docs/oauth/introspection-revocation/) | `POST /api/oauth/introspect` · `POST /api/oauth/revoke` |
| [Logout](/docs/oauth/logout/) | `GET\|POST /api/oauth/logout` |
| [Discovery & JWKS](/docs/oauth/discovery-jwks/) | `/.well-known/*` |

Building a mobile app? Jump straight to the [Quickstart](/docs/get-started/quickstart-react-native/).