
# OAuth 2.0 and OpenID Connect endpoints

> **Status: `planned`.** This page describes the protocol surface of every KeenAuth environment as specified in the plan (§13). The endpoints are being built in phase 2 and are not yet available in a hosted environment. Details can still change before general availability.

Every environment is its own OAuth 2.0 authorization server and OpenID Connect provider. The endpoints are not part of the management API: they live on the environment host, `https://<environment-slug>.<region>.keenauth.app`, or on your verified custom domain. In the table, `<issuer>` is the environment's issuer, the value of `issuer` in the discovery document.

## Endpoints

| Endpoint | Method and path | Client authentication |
|---|---|---|
| Discovery | `GET <issuer>/.well-known/openid-configuration` and `GET <issuer>/.well-known/oauth-authorization-server` (RFC 8414, same content) | None |
| Keys | `GET <issuer>/.well-known/jwks.json` | None |
| Authorization | `GET` or `POST <login host>/oauth2/authorize` | None (browser) |
| Pushed authorization request (RFC 9126) | `POST <issuer>/oauth2/par` | Required for confidential clients; `client_id` for public clients |
| Token | `POST <issuer>/oauth2/token` | Required for confidential clients; `client_id` for public clients (DPoP is optional) |
| Device authorization (RFC 8628) | `POST <issuer>/oauth2/device_authorization` | Same as the token endpoint |
| Revocation (RFC 7009) | `POST <issuer>/oauth2/revoke` | Same as the token endpoint |
| Introspection (RFC 7662) | `POST <issuer>/oauth2/introspect` | Confidential client only |
| UserInfo | `GET` or `POST <issuer>/oauth2/userinfo` | Bearer or DPoP access token |
| End session | `GET` or `POST <login host>/oauth2/logout` | None (`id_token_hint` or `client_id`) |

`<login host>` is any sign-in domain of the environment: the default host or an added custom domain. The discovery document is served only on the issuer host. On another sign-in host, `/.well-known/openid-configuration` returns `404` with the issuer URL in the body.

## Grant types

| Grant | Use |
|---|---|
| `authorization_code` | Browser and native sign-in. PKCE with `S256` is required. |
| `refresh_token` | Renews tokens. Every use rotates the refresh token, and reusing an old one revokes the whole token family. |
| `client_credentials` | Machine-to-machine access. |
| `urn:ietf:params:oauth:grant-type:device_code` | Devices without a keyboard, such as a TV or a command-line tool. |
| `urn:ietf:params:oauth:grant-type:token-exchange` | Delegation and impersonation between services. |

Implicit flows (`response_type=token` and anything that contains `id_token`), the resource owner password credentials grant and hybrid response types are not supported, now or later. RFC 9700 forbids the first two.

## Device authorization

A device without a browser starts the flow with `POST <issuer>/oauth2/device_authorization`. Only applications of type `device` or `native` that list the device grant explicitly can use it. The response carries a `device_code`, a `user_code` and a verification address. The user opens the address on another device, signs in and approves. Meanwhile, the device polls the token endpoint with `grant_type=urn:ietf:params:oauth:grant-type:device_code`. The `user_code` has 8 characters and is shown as `XXXX-XXXX`. A device code is valid for 10 minutes and works once. The device polls every 5 seconds at most; faster polling is answered with `slow_down`, which adds 5 seconds to the interval. The `verification_uri` is `https://<login host>/u/device`, and the user is shown the application, the requested scopes and the country of the device that started the request before approving.

## Security rules

The environment follows the OAuth 2.0 Security Best Current Practice (RFC 9700) without exception:

- The redirect URI must match a registered one byte for byte. There are no wildcards.
- PKCE accepts only `S256`; `plain` is always rejected. PKCE is required for public clients and is on by default for every application. A confidential `web` application can switch it off in its settings, and only if the environment still requires `nonce` in that case.
- The official SDKs send a 128-bit `state` and a `nonce` and check both on return.
- An authorization code is valid for 60 seconds and works once. Presenting a used code returns `invalid_grant` and revokes the refresh tokens and the access token that the first use produced.
- Pushed authorization requests keep the request parameters out of the browser's address bar. They are available on every plan, and the `require_par` setting makes them mandatory for an application or the whole environment.
- Refresh tokens rotate on every use, and reuse detection revokes the family.
- DPoP (RFC 9449) binds an access token to a key held by the client. To verify bound tokens in your API, see [Verifying tokens in your API](/docs/sdk/token-verification).

## Sessions and sign-out

After a successful sign-in the environment keeps one single sign-on session in an `HttpOnly` cookie on the sign-in host. Applications of the same environment reuse it, so a second application does not ask for a password again unless its policy needs a stronger or newer authentication. Your code never sees the cookie: it only sees the authorization response.

The end-session endpoint (OpenID Connect RP-Initiated Logout 1.0) ends that session, revokes the refresh tokens that belong to it and notifies the applications that registered a back-channel logout address. To list or revoke the sessions of a user from your backend, use the [session endpoints of the management API](/docs/api/management/users/list-sessions).

If the user signed in through an enterprise connection, add `federated=true` to the end-session request to sign them out at their identity provider too. The session here ends first; the browser then goes to the identity provider's logout (SAML single logout, or the OpenID provider's end-session endpoint) and comes back to the `post_logout_redirect_uri`. This happens only when the connection has identity-provider logout turned on and the provider publishes a logout address. Otherwise, or if the provider cannot be reached, the request works as it does without the parameter. Signing out at the identity provider can also end the user's other applications that use it.
