> Markdown version of https://authpi.com/docs/reference/oidc/ — fetch the complete AuthPI docs index at https://authpi.com/llms.txt to discover all available pages.

# OIDC & OAuth 2.0 Compliance

Complete reference of OAuth 2.0 and OpenID Connect standards implemented by AuthPI.

AuthPI implements OAuth 2.0 and OpenID Connect (OIDC) following the relevant RFCs and specifications. This document details which standards are supported, how they're implemented, and any deviations or limitations.

## Standards Overview

| Standard | Status | Notes |
|----------|--------|-------|
| OAuth 2.0 (RFC 6749) | Partial | Authorization code and refresh token grants |
| OAuth 2.0 Bearer Tokens (RFC 6750) | Full | |
| PKCE (RFC 7636) | Full | S256 only, required for public clients |
| Token Introspection (RFC 7662) | Full | |
| Token Revocation (RFC 7009) | Full | |
| JWT Access Tokens (RFC 9068) | Full | |
| Resource Indicators (RFC 8707) | Full | Audience parameter support |
| OpenID Connect Core 1.0 | Full | |
| OpenID Connect Discovery 1.0 | Full | |
| OpenID Connect Session Management 1.0 | Partial | Front-channel and back-channel logout |

## OAuth 2.0 (RFC 6749)

### Supported Grant Types

**Authorization Code Grant** (RFC 6749 Section 4.1)

The primary grant type for web applications and native apps. The flow works as follows:

1. Client redirects user to `/authorize` with `response_type=code`
2. User authenticates and consents
3. AuthPI redirects back with an authorization code
4. Client exchanges code for tokens at `/token`

```
GET /authorize?
  response_type=code&
  client_id=cli_xxx&
  redirect_uri=https://app.example.com/callback&
  scope=openid%20profile%20email&
  state=abc123
```

**Refresh Token Grant** (RFC 6749 Section 6)

Exchange a refresh token for new access and refresh tokens:

```
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=eyJ...&
client_id=cli_xxx&
scope=openid profile
```

The optional `scope` parameter may **narrow** the granted scope for the issued access token (RFC 6749 §6) — it must be a subset of the original grant, or the request fails with `invalid_scope`. Omit it to receive the full grant. The token response includes the effective `scope`, and the rotated refresh token always retains the complete original grant, so narrowing one refresh does not shrink later ones.

AuthPI implements **refresh token rotation**—each refresh returns a new refresh token, and the old one is invalidated. This improves security by limiting the window for token theft. Granted scopes and organization memberships are re-applied on every refresh: scopes from the grant of record, memberships fresh from the directory.

If the original authorization request used `org=org_...`, refresh keeps that same selected-organization restriction. The refresh request itself cannot add or change `org`.

**Client Credentials Grant** (RFC 6749 Section 4.4)

Machine-to-machine authentication with no user involved:

```
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
scope=read:data
```

The client authenticates via HTTP Basic (`client_secret_basic`) or `client_id` + `client_secret` form parameters (`client_secret_post`).

- Available to **M2M clients** (`application_type: "m2m"`, confidential, with `client_credentials` in their `grant_types`) and to **agents** (`agt_...` identities authenticating with a secret verifier).
- The response contains an access token only — **no refresh token and no ID token**. The `openid` scope is ignored for this grant.
- If no `scope` is requested, the token carries the client's (or agent's) full allowed scope set. Requesting a scope outside the allowed set fails with `invalid_scope`.
- The optional `resource` parameter sets the token's `aud` claim; otherwise the audience defaults to the client id.
- Token lifetime: the client's `default_access_token_age` (default 30 minutes). Agent tokens are fixed at 5 minutes so scope changes propagate quickly.

Note: the client credentials grant issues OAuth access tokens for calling *your own* APIs. To call the AuthPI Core (admin) API from a backend, use [org API keys](/docs/reference/) instead.

### Not Supported

The following grant types are not implemented:

- **Implicit Grant** (RFC 6749 Section 4.2): Deprecated; use authorization code with PKCE
- **Resource Owner Password** (RFC 6749 Section 4.3): Not recommended due to security concerns
- **Device Authorization** (RFC 8628): Not currently supported

## PKCE (RFC 7636)

Proof Key for Code Exchange prevents authorization code interception attacks. AuthPI fully implements PKCE with the following behavior:

### Requirements

| Client Type | PKCE Requirement |
|-------------|------------------|
| Public clients (SPAs, native apps) | **Required** |
| Confidential clients | Optional but recommended |

### Supported Methods

Only `S256` is supported. The `plain` method is rejected for security reasons.

```
// Generate code verifier (43-128 characters, URL-safe)
const verifier = generateRandomString(64);

// Generate code challenge
const challenge = base64url(sha256(verifier));

// Authorization request
GET /authorize?
  ...&
  code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
  code_challenge_method=S256

// Token request includes verifier
POST /token
  ...&
  code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
```

### Error Handling

| Scenario | Error |
|----------|-------|
| Public client without `code_challenge` | `invalid_request` |
| `code_challenge_method=plain` | `invalid_request` |
| Invalid `code_verifier` at token exchange | `invalid_grant` |
| Missing `code_verifier` at token exchange | `invalid_grant` |

## Token Introspection (RFC 7662)

The introspection endpoint allows resource servers to validate tokens and retrieve their metadata.

**Endpoint:** `POST /{issuer_id}/introspect`

### Request

```
POST /introspect
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

token=eyJ...&
token_type_hint=access_token
```

The `token_type_hint` parameter is optional but helps AuthPI validate the token more efficiently. Accepted values: `access_token`, `refresh_token`.

### Response (Active Token)

```json
{
  "active": true,
  "scope": "openid profile email",
  "client_id": "cli_xxx",
  "token_type": "Bearer",
  "sub": "usr_xxx",
  "aud": "cli_xxx",
  "iat": 1705330953,
  "exp": 1705332753,
  "iss": "https://idp.authpi.com/iss_xxx",
  "jti": "tok_xxx",
  "sid": "ses_xxx",
  "username": "user@example.com"
}
```

### Response (Inactive Token)

```json
{
  "active": false
}
```

A token is inactive if it's expired, revoked, malformed, or issued by a different issuer.

### Access vs Refresh Token Introspection

Access tokens are validated by checking:
1. JWT signature validity
2. Expiration time
3. Revocation status in the revocation list

Refresh tokens are validated by checking:
1. JWT signature validity
2. Session binding (must have valid `sid`)
3. Token rotation (JTI must match current token)

## Token Revocation (RFC 7009)

Revoke tokens when users log out or when tokens may be compromised.

**Endpoint:** `POST /{issuer_id}/revoke`

### Request

```
POST /revoke
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

token=eyJ...&
token_type_hint=refresh_token
```

### Response

The endpoint always returns `200 OK` regardless of whether the token was valid or already revoked. This prevents information leakage about token validity.

### Revocation Behavior

**Refresh tokens:** Revoked via the session manager. The token's JTI is marked as revoked, and subsequent uses of the refresh token fail.

**Access tokens:** Added to a revocation list with a TTL matching the token's remaining lifetime. Resource servers should introspect tokens to check revocation status.

## JWT Access Tokens (RFC 9068)

AuthPI issues access tokens as JWTs following RFC 9068.

### Header

```json
{
  "alg": "ES256",
  "typ": "at+jwt",
  "kid": "key_xxx"
}
```

### Payload

```json
{
  "iss": "https://idp.authpi.com/iss_xxx",
  "sub": "usr_xxx",
  "aud": "cli_xxx",
  "exp": 1705332753,
  "iat": 1705330953,
  "nbf": 1705330953,
  "jti": "tok_xxx",
  "client_id": "cli_xxx",
  "sid": "ses_xxx",
  "scope": "openid profile email",
  "auth_time": 1705330900
}
```

### Supported Signing Algorithms

| Algorithm | Description |
|-----------|-------------|
| ES256 | ECDSA with P-256 curve (default) |
| RS256 | RSA with SHA-256 |
| EdDSA | Edwards-curve DSA |

The algorithm is configurable per client via `settings.openid.response_signature_alg`.

## Resource Indicators (RFC 8707)

AuthPI supports the `audience` parameter to request tokens for specific resource servers.

### Request

```
GET /authorize?
  ...&
  audience=https://api.example.com
```

### Validation

The requested audience must be either:
- The client's own `client_id`
- Listed in the client's `allowed_audiences` configuration

### Token Audience

When a custom audience is requested:
- Access tokens have `aud` set to the requested audience
- Refresh tokens always have `aud` set to the client_id

## Selected Organizations

AuthPI supports an optional `org` parameter on the authorization request to issue tokens restricted to one organization:

```
GET /authorize?
  ...&
  org=org_0gw3hcq8r2kfn7xj9tzm4be5a
```

The selected organization is stored with the authorization code and resulting session. `/token` does not accept `org`; token exchange and refresh use the trusted value from the authorization flow.

Validation happens before tokens are issued:

- If the client has `settings.restrictions.organizations.policy: "none"`, selected-org requests fail with `invalid_request`.
- If the client has `policy: "allowlist"`, the requested org must appear in `allowed_org_ids`.
- If the user is not an active member of the selected org, the authorization-code exchange fails with `invalid_grant`.

## OpenID Connect Core 1.0

AuthPI is a fully compliant OpenID Connect Provider (OP).

### ID Token

ID tokens are JWTs containing identity claims about the authenticated user.

**Required claims:**

| Claim | Description |
|-------|-------------|
| `iss` | Issuer identifier URL |
| `sub` | Subject identifier (user ID) |
| `aud` | Audience (client ID) |
| `exp` | Expiration time |
| `iat` | Issued at time |

**Authentication claims:**

| Claim | Description |
|-------|-------------|
| `auth_time` | Time of authentication |
| `nonce` | Client-provided nonce (if sent in request) |
| `acr` | Authentication context class reference |
| `amr` | Authentication methods used |
| `at_hash` | Access token hash |

**Profile claims** (when requested via scopes):

| Scope | Claims |
|-------|--------|
| `profile` | `name`, `given_name`, `family_name`, `picture`, `locale` |
| `email` | `email`, `email_verified` |
| `phone` | `phone_number`, `phone_number_verified` |
| `address` | `address` |

### UserInfo Endpoint

**Endpoint:** `GET|POST /{issuer_id}/userinfo`

Returns claims about the authenticated user. Requires a valid access token with the `openid` scope.

```
GET /userinfo
Authorization: Bearer eyJ...
```

Response:

```json
{
  "sub": "usr_xxx",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "email_verified": true,
  "picture": "https://example.com/photo.jpg"
}
```

### Scopes

| Scope | Description |
|-------|-------------|
| `openid` | Required for OIDC flows; triggers ID token issuance |
| `profile` | Basic profile information |
| `email` | Email address and verification status |
| `phone` | Phone number and verification status |
| `address` | Physical address |

### Nonce

The `nonce` parameter prevents replay attacks. When included in the authorization request, it's embedded in the ID token for client verification.

```
GET /authorize?
  ...&
  nonce=n-0S6_WzA2Mj
```

The client must verify that the `nonce` claim in the ID token matches the value sent in the request.

### Authentication Context

**ACR (Authentication Context Class Reference):**

AuthPI returns ACR values indicating the authentication assurance level:
- `urn:authpi:assurance:loa1` - Standard authentication
- `urn:authpi:method:{method}` - Method-specific (e.g., `urn:authpi:method:passkey`)

**AMR (Authentication Methods References):**

The `amr` claim contains an array of authentication methods used:
- `pwd` - Password
- `otp` - One-time password
- `mfa` - Multi-factor authentication
- `hwk` - Hardware key (passkey)

## OpenID Connect Discovery 1.0

AuthPI publishes its configuration at the standard discovery endpoint.

**Endpoint:** `GET /{issuer_id}/.well-known/openid-configuration`

### Response

```json
{
  "issuer": "https://idp.authpi.com/iss_xxx",
  "authorization_endpoint": "https://idp.authpi.com/iss_xxx/authorize",
  "token_endpoint": "https://idp.authpi.com/iss_xxx/token",
  "userinfo_endpoint": "https://idp.authpi.com/iss_xxx/userinfo",
  "jwks_uri": "https://idp.authpi.com/iss_xxx/jwks.json",
  "introspection_endpoint": "https://idp.authpi.com/iss_xxx/introspect",
  "revocation_endpoint": "https://idp.authpi.com/iss_xxx/revoke",
  "end_session_endpoint": "https://idp.authpi.com/iss_xxx/logout",

  "scopes_supported": ["openid", "email", "profile", "address", "phone"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "subject_types_supported": ["public"],

  "id_token_signing_alg_values_supported": ["ES256", "RS256", "EdDSA"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
  "code_challenge_methods_supported": ["S256"],

  "claims_supported": [
    "sub", "iss", "aud", "exp", "iat",
    "name", "given_name", "family_name",
    "email", "email_verified",
    "picture", "locale",
    "acr", "amr", "auth_time"
  ],

  "frontchannel_logout_supported": false,
  "frontchannel_logout_session_supported": false,
  "backchannel_logout_supported": true,
  "backchannel_logout_session_supported": true
}
```

### JWKS Endpoint

**Endpoint:** `GET /{issuer_id}/jwks.json`

Returns the JSON Web Key Set containing public keys for verifying tokens.

```json
{
  "keys": [
    {
      "kty": "EC",
      "use": "sig",
      "kid": "key_xxx",
      "alg": "ES256",
      "crv": "P-256",
      "x": "...",
      "y": "..."
    }
  ]
}
```

Keys are cached for 1 hour. Clients should cache JWKS responses and refresh periodically.

## Session Management

AuthPI supports OIDC Session Management for detecting session changes.

### Check Session Iframe

**Endpoint:** `GET /{issuer_id}/check-session.html`

Clients can embed this iframe and use `postMessage` to check if the user's session is still valid without making network requests.

### RP-Initiated Logout

**Endpoint:** `GET /{issuer_id}/logout`

```
GET /logout?
  id_token_hint=eyJ...&
  post_logout_redirect_uri=https://app.example.com/logged-out&
  state=xyz
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `id_token_hint` | Recommended | ID token for session identification |
| `post_logout_redirect_uri` | Optional | Where to redirect after logout |
| `state` | Optional | CSRF protection, echoed back |

The `post_logout_redirect_uri` must be registered in the client's configuration.

When AuthPI can identify the underlying OP session, RP-initiated logout terminates that SSO anchor and the linked RP/client sessions created from it. Each affected client session can receive a back-channel logout notification when the client is configured for it.

### Front-Channel Logout

AuthPI does not currently advertise or send OIDC front-channel logout iframe notifications. Use back-channel logout for client session notifications.

### Back-Channel Logout

AuthPI can send logout tokens directly to relying parties' back-channel logout endpoints. This is the supported logout notification mechanism and does not depend on the user's browser.

## Client Authentication

### Supported Methods

| Method | Description |
|--------|-------------|
| `client_secret_basic` | Client ID and secret in HTTP Basic header |
| `client_secret_post` | Client ID and secret in POST body |
| `none` | No authentication (public clients only) |

**Basic Authentication:**

```
POST /token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=xxx&
redirect_uri=https://app.example.com/callback
```

**POST Body Authentication:**

```
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=xxx&
redirect_uri=https://app.example.com/callback&
client_id=cli_xxx&
client_secret=sec_xxx
```

### Not Supported

- `private_key_jwt` - JWT client assertion with private key
- `client_secret_jwt` - JWT client assertion with shared secret
- `tls_client_auth` - Mutual TLS authentication

## Token Lifetimes

Default token lifetimes (configurable per client):

| Token Type | Default Lifetime |
|------------|------------------|
| Authorization code | 10 minutes |
| Access token | 30 minutes |
| Refresh token | 7 days |
| ID token | Same as access token |

## Error Responses

AuthPI returns OAuth 2.0 standard error responses:

```json
{
  "error": "invalid_grant",
  "error_description": "The authorization code has expired"
}
```

Common error codes:

| Error | Description |
|-------|-------------|
| `invalid_request` | Missing or invalid parameter |
| `invalid_client` | Client authentication failed |
| `invalid_grant` | Authorization code or refresh token invalid |
| `unauthorized_client` | Client not authorized for this grant type |
| `unsupported_grant_type` | Grant type not supported |
| `invalid_scope` | Requested scope is invalid or exceeds granted |
| `access_denied` | User denied the authorization request |

## Limitations

### Features Not Implemented

- **Hybrid flows**: `response_type` combinations like `code id_token` are not supported
- **Request objects**: The `request` and `request_uri` parameters are not supported
- **Pushed Authorization Requests** (RFC 9126): Not implemented
- **DPoP** (RFC 9449): Demonstration of Proof of Possession not supported
- **CIBA**: Client-Initiated Backchannel Authentication not supported

### Parameter Limitations

- `prompt` parameter is recognized but not fully enforced
- `max_age` parameter is not validated
- `ui_locales` parameter is not processed
- `login_hint` parameter is not used

## Security Recommendations

### For Confidential Clients

1. Store client secrets securely (environment variables, secret managers)
2. Use HTTPS for all redirect URIs
3. Implement PKCE even though it's optional
4. Rotate client secrets periodically

### For Public Clients

1. PKCE is mandatory—always use `S256`
2. Store tokens securely (avoid localStorage for sensitive data)
3. Use short-lived access tokens
4. Implement token refresh handling

### For All Clients

1. Validate ID token signatures using JWKS
2. Verify `nonce` in ID tokens matches your request
3. Check `aud` claim matches your client ID
4. Implement proper CSRF protection with `state` parameter
5. Use token introspection for high-security operations

## Next Steps

- [Getting Started](/docs/quickstarts/typescript-backend/) with AuthPI
- [Clients](/docs/concepts/clients) configuration guide
- [Sessions](/docs/concepts/users#sessions) and token management
- [Webhooks](/docs/guides/webhooks) for authentication events