# OneiD documentation (all pages) > The complete OneiD documentation from https://oltinid.com/docs/ as Markdown. Index: https://oltinid.com/llms.txt ## Key facts for agents - OneiD is an OAuth 2.0 authorization server and OpenID Connect provider. Configure libraries from discovery: `https://YOUR_ONEID/.well-known/openid-configuration`. - The issuer ends with a slash: `https://YOUR_ONEID/`. Compare `iss` with the discovery value exactly. - Use the authorization code flow with PKCE (`S256` only) for every application with a user; `client_credentials` for services. Implicit, hybrid, password (ROPC) and device flows are not supported. - Client authentication: `client_secret_basic` (preferred) or `client_secret_post`. `private_key_jwt` and mTLS cannot be used. - Access tokens are RS256 JWTs with `iss`, `sub`, `exp`, `scope`, `client_id` and no `aud`. APIs check signature (JWKS), issuer, expiry and the required scope; they must not require an audience. - Refresh tokens (scope `offline_access`) rotate on every use; store the new one. Reuse after a short grace period revokes the chain. - Sign-out: RP-initiated logout at `/connect/logout` with `id_token_hint` and a registered `post_logout_redirect_uri`. No front-channel or back-channel logout. - Clients are registered by an administrator (no dynamic registration). Browser clients need their origin in the client's allowed CORS origins. - Key users by `iss` + `sub`, never by email. Roles are in the `role` claim (scope `roles`). - Not supported: SAML, SCIM, PAR, JAR, DPoP, token exchange, CIBA, `login_hint`, `ui_locales`, enforcement of `acr_values` (check `acr`/`amr` in the ID token instead). --- # How OneiD works > Learn the parts of OneiD you work with when you connect an application or API, and how a sign-in moves between them. Source: https://oltinid.com/docs/get-started/overview/ · Section: Get started · All OneiD documentation: https://oltinid.com/llms.txt OneiD is an OAuth 2.0 authorization server and OpenID Connect provider with an admin console. Your applications send users to OneiD to sign in, and receive tokens that say who the user is and what the application may do. This page explains the parts you work with and how a sign-in moves between them. ## Your OneiD address Every OneiD deployment has one address, for example `https://YOUR_ONEID`. All endpoints live under it, and the discovery document at `https://YOUR_ONEID/.well-known/openid-configuration` lists them. The address also identifies OneiD in tokens. This value is called the issuer, and it ends with a slash: `https://YOUR_ONEID/`. The `iss` claim in every token carries the same value. If your library compares issuers exactly, use the value from the discovery document, including the slash. One deployment has one issuer, one database and one source of users. There are no tenants or organisations inside a deployment. Each customer gets its own deployment, either hosted by OneiD or run in the customer's own environment. ## Applications are clients Every application that signs users in or calls an API with OneiD tokens is registered as a client. An administrator registers clients in the admin console or through the Admin API. Developers cannot register clients themselves, and there is no dynamic client registration. A client is one of two types: | Client type | Used by | Secret | PKCE | |---|---|---|---| | `public` | Browser applications, mobile and desktop apps | None | Always required | | `confidential` | Server-side web applications and services | One client secret | Required by default | A public client cannot keep a secret, because its code runs on the user's device. A confidential client runs on a server you control and proves its identity with its client secret. For confidential clients an administrator can turn PKCE off. Keep it on. ## Users and sign-in sources Each deployment takes its users from one sign-in source: - **OneiD accounts.** Administrators create users in the admin console. Users sign in with a password and, if enabled, an authenticator-app code. - **LDAP or Active Directory.** Users sign in with their directory user name and password. OneiD checks the password against the directory and does not store it. - **An OpenID Connect provider**, such as Okta. OneiD sends users to that provider to sign in. Your application does not need to know which source is in use. It always talks to OneiD in the same way. The `idp` claim tells you where the user signed in. See [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/). ## Scopes A scope names something the application asks for. The application lists scopes in the `scope` parameter when it sends the user to sign in. | Scope | What it gives | |---|---| | `openid` | Required for OpenID Connect. Returns an ID token with the user's `sub`. | | `profile` | Name claims, `preferred_username`, `updated_at` and `idp`. | | `email` | `email` and `email_verified`. | | `phone` | `phone_number` and `phone_number_verified`. | | `roles` | One `role` claim per role the user holds. | | `offline_access` | A refresh token, if the client also has the refresh token grant. | An administrator can also create API scopes, such as `orders.read`. None exist by default. API scopes appear in the access token's `scope` claim and add no other claims. A client may only request the scopes it is allowed. Any other scope returns `invalid_scope`. See [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/). ## Tokens OneiD issues three kinds of token to applications. | Token | Who reads it | What it is for | Lifetime by default | |---|---|---|---| | ID token | Your application | Says who signed in, when and how. Never send it to an API. | 20 minutes | | Access token | Your API | Lets the application call an API on the user's behalf, or on its own behalf. | 1 hour | | Refresh token | OneiD only | Gets new access tokens without asking the user to sign in again. | 14 days | ID tokens and access tokens are signed JWTs. Applications and APIs check the signature with the public keys OneiD publishes at `/.well-known/jwks`. Refresh tokens and authorisation codes are opaque. Do not try to read them. An administrator can change the access, ID and refresh token lifetimes for each client. See [Tokens](https://oltinid.com/docs/reference/tokens/). ## Sessions and single sign-on When a user signs in, OneiD keeps a browser session for 8 hours, extended while the user is active. While that session lasts, other applications that send the user to OneiD get a sign-in without a password prompt. This is single sign-on. Your application keeps its own session too. OneiD does not tell other applications when a user signs out of one of them. See [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/) and [Sign-out](https://oltinid.com/docs/guides/logout/). ## The admin console Administrators manage OneiD in the admin console. They can: - register applications, manage client secrets and choose allowed scopes - create users, assign roles, unlock accounts, reset MFA and passwords - manage roles, role claims and API scopes - list sessions and revoke tokens - read the audit log - rotate signing keys - import and export clients As a developer you usually do not use the console yourself. You send your administrator the values described in [Register an application](https://oltinid.com/docs/get-started/register-an-application/). ## How a sign-in works This is the authorization code flow with PKCE, which every application that signs users in uses. ```text 1. The user opens your application and chooses Sign in. 2. Your application creates a random code_verifier, a state and a nonce, and redirects the browser to https://YOUR_ONEID/connect/authorize with client_id, redirect_uri, scope, state, nonce and the code_challenge. 3. OneiD checks the client and the redirect URI. 4. If the user has no OneiD session, OneiD shows its sign-in page. The user signs in (and enters an authenticator code if MFA applies). 5. If the client asks for consent, OneiD shows the consent page. 6. OneiD redirects the browser back to your redirect_uri with a one-time code, your state and iss. 7. Your application checks state and iss, then sends the code and the code_verifier to https://YOUR_ONEID/connect/token. 8. OneiD returns an ID token, an access token and, if requested and allowed, a refresh token. 9. Your application validates the ID token, starts its own session and calls your API with the access token. 10. Your API validates the access token and checks its scope. ``` The details, with every request, are in [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/). ## Glossary | Term | Meaning | |---|---| | OneiD address | The base URL of your OneiD deployment, written here as `https://YOUR_ONEID`. | | Issuer | The value that identifies OneiD in tokens and discovery: `https://YOUR_ONEID/`, with a trailing slash. | | Client | An application registered with OneiD. Identified by its client ID. | | Public client | A client without a secret, such as a browser, mobile or desktop app. | | Confidential client | A server-side client that authenticates with a client secret. | | Redirect URI | The address in your application where OneiD sends the user back after sign-in. Must match a registered value exactly. | | Scope | A named permission or set of claims the application asks for. | | Claim | A piece of information in a token, such as `sub` or `email`. | | `sub` | The user's stable, opaque identifier. Key users by `iss` and `sub`, never by email. | | ID token | A JWT for your application that says who signed in. | | Access token | A JWT your application sends to an API. | | Refresh token | An opaque token that gets new access tokens. | | PKCE | Proof Key for Code Exchange. Ties the code to the application that started the sign-in. | | Consent | The page where a user agrees to share the requested scopes with an application. | | Session | The period during which OneiD remembers that a user has signed in. | | JWKS | The set of public keys OneiD signs tokens with, published at `/.well-known/jwks`. | ## Learn more - [Your first sign-in](https://oltinid.com/docs/get-started/first-sign-in/) - [Choose a flow](https://oltinid.com/docs/get-started/choose-a-flow/) - [Register an application](https://oltinid.com/docs/get-started/register-an-application/) - [Endpoints](https://oltinid.com/docs/reference/endpoints/) --- # Your first sign-in > Sign in to the OneiD demo application, read the discovery document of the demonstration instance and pick a quickstart. Source: https://oltinid.com/docs/get-started/first-sign-in/ · Section: Get started · All OneiD documentation: https://oltinid.com/llms.txt Before you connect your own application, see a OneiD sign-in from the user's side and look at what OneiD publishes about itself. This page uses the demonstration instance that we run at `https://auth.oltinid.com`. ## What you need - A web browser. - A terminal with `curl`. `jq` is optional and makes the JSON easier to read. - A demo account. [Ask for one](https://oltinid.com/contact/?topic=demo) through the contact page. ## Step 1: Sign in to the demo application `https://demo.oltinid.com` is a small application that signs in through the demonstration instance and shows you the tokens it receives. 1. Open `https://demo.oltinid.com` in your browser. 2. Choose to sign in. The browser moves to `https://auth.oltinid.com`, which shows the OneiD sign-in page. 3. Sign in with your demo account. If the account has MFA set up, enter the code from your authenticator app. 4. If OneiD shows a consent page, review the scopes and continue. 5. The browser returns to the demo application, which shows your tokens and their claims. Look at the requests during step 2, for example in the network tab of your browser's developer tools. The first request to OneiD goes to `https://auth.oltinid.com/connect/authorize` and carries parameters such as `client_id`, `redirect_uri`, `scope`, `state` and `code_challenge`. This is the authorisation request every application sends. > **Checkpoint:** The demo application shows an ID token whose `iss` is `https://auth.oltinid.com/`, with the trailing slash, and whose `sub` identifies your demo account. ### What to look for in the tokens | Claim | Where | What it tells you | |---|---|---| | `iss` | ID token and access token | Which OneiD issued the token. Ends with a slash. | | `sub` | ID token and access token | The user's stable, opaque identifier. | | `aud` | ID token | The client ID of the demo application. | | `auth_time` | ID token | When you signed in, in seconds since 1970. | | `amr` | ID token | How you signed in, for example `["pwd"]` or `["pwd","mfa"]`. | | `acr` | ID token | The same information as one value, for example `urn:oltin:ac:pwd`. | | `scope` | Access token | The scopes the access token carries, separated by spaces. | | `idp` | ID token and access token, with `profile` | Where you signed in. On the demonstration instance this is `local`. | The access token has no `aud` claim. APIs check its issuer, signature, expiry and scope instead. See [Tokens](https://oltinid.com/docs/reference/tokens/). ### Single sign-on After you sign in, OneiD keeps a session in your browser for 8 hours, extended while you are active. While it lasts, any other application that sends you to the same OneiD signs you in without asking for your password again. ## Step 2: Read the discovery document Every OneiD instance publishes a discovery document that lists its endpoints and capabilities. OpenID Connect libraries read it to configure themselves. ```bash curl -s https://auth.oltinid.com/.well-known/openid-configuration | jq ``` The response is a JSON object. These are some of the fields you will see (abridged): ```json { "issuer": "https://auth.oltinid.com/", "authorization_endpoint": "https://auth.oltinid.com/connect/authorize", "token_endpoint": "https://auth.oltinid.com/connect/token", "userinfo_endpoint": "https://auth.oltinid.com/connect/userinfo", "end_session_endpoint": "https://auth.oltinid.com/connect/logout", "jwks_uri": "https://auth.oltinid.com/.well-known/jwks", "response_types_supported": ["code"], "response_modes_supported": ["form_post", "fragment", "query"], "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["RS256"], "claims_parameter_supported": true, "request_parameter_supported": false, "request_uri_parameter_supported": false, "authorization_response_iss_parameter_supported": true } ``` What this tells you: - **`issuer`** ends with a slash. Tokens carry exactly this value in `iss`. - **`response_types_supported`** is `code` only. Applications use the authorization code flow. - **`grant_types_supported`** lists the three grants OneiD supports. There is no password grant and no device flow. - **`authorization_response_iss_parameter_supported`** is `true`. OneiD adds `iss` to the redirect back to your application, so you can check that the response came from the OneiD you called. - **`request_parameter_supported`** is `false`. OneiD does not accept request objects. Now fetch the public signing keys: ```bash curl -s https://auth.oltinid.com/.well-known/jwks | jq ``` > **Checkpoint:** The discovery document's `issuer` is `https://auth.oltinid.com/`, and the JWKS response contains at least one RSA key with a `kid`. The `kid` in the header of the tokens you saw in step 1 matches one of these keys. For every field and what OneiD supports, see [Discovery document](https://oltinid.com/docs/reference/discovery/). ## Step 3: Pick a quickstart You have seen the flow from the user's side. Next, connect your own application. 1. Decide which flow fits your application. [Choose a flow](https://oltinid.com/docs/get-started/choose-a-flow/) walks you through it. 2. Ask your OneiD administrator to register your application. [Register an application](https://oltinid.com/docs/get-started/register-an-application/) lists what to send them. 3. Follow the quickstart for your stack. The [quickstarts](https://oltinid.com/docs/quickstarts/) cover browser applications, server-side web applications, APIs and machine-to-machine calls. > **Note:** The demonstration instance is for trying OneiD. Build your application against your own OneiD address, written in these docs as `https://YOUR_ONEID`. ## Learn more - [How OneiD works](https://oltinid.com/docs/get-started/overview/) - [Quickstarts](https://oltinid.com/docs/quickstarts/) - [Discovery document](https://oltinid.com/docs/reference/discovery/) --- # Choose a flow > Answer two questions to find the OAuth 2.0 grant and client type your application needs with OneiD. Source: https://oltinid.com/docs/get-started/choose-a-flow/ · Section: Get started · All OneiD documentation: https://oltinid.com/llms.txt OneiD supports two ways for an application to get tokens: the authorization code flow with PKCE when a user signs in, and client credentials when a service calls an API on its own behalf. Refresh tokens keep a user sign-in going without asking the user again. Answer the questions below to find the right one. ## Question 1: Is a user present? **Yes, a person signs in.** Use the authorization code flow with PKCE. The application sends the user's browser to OneiD, the user signs in there, and the application receives a code that it exchanges for tokens. Your application never sees the user's password. **No, a program calls an API on its own behalf.** Use client credentials. A background job, a scheduled task or one service calling another authenticates with its client ID and secret and receives an access token. There is no user, so there is no ID token and no refresh token. ## Question 2: Where does your code run? The answer decides the client type and whether the application can hold a secret. ### In the browser A single-page application (React, Angular, Vue or plain JavaScript) runs entirely on the user's device. Anyone can read its code, so it cannot keep a secret. - Client type: `public` - Grant: authorization code with PKCE - Your administrator also registers your application's origin as an allowed CORS origin, because the browser calls the token endpoint directly. See [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/). ### On a server A server-side web application (Node.js, ASP.NET Core, Go, Python, Java) handles the sign-in on the server. The browser only carries redirects; the server talks to the token endpoint and can keep a secret. - Client type: `confidential` - Grant: authorization code with PKCE, plus the client secret at the token endpoint PKCE is required for confidential clients by default. Keep it on. ### On a phone or a desktop A mobile or desktop application is installed on the user's device. Like a browser application, it cannot keep a secret: anyone can extract one from the installed app. - Client type: `public` - Grant: authorization code with PKCE, using the system browser and a private-use URI scheme or a loopback redirect See [Mobile and desktop applications](https://oltinid.com/docs/guides/native-applications/). ### On a machine, without a user A service, daemon or scheduled job runs on infrastructure you control. - Client type: `confidential` - Grant: client credentials Public clients cannot use client credentials. See [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/). ### An API that receives tokens An API does not sign anyone in. It receives access tokens from the applications above and validates them. Most APIs do not need their own client. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/). ## Question 3: Must the user stay signed in? Access tokens last 1 hour by default. If your application calls an API for longer than that without bringing the user back to OneiD, ask for a refresh token: - request the `offline_access` scope, and - ask your administrator to give the client the refresh token grant. OneiD rotates refresh tokens: every refresh returns a new refresh token, and you must store it. A chain of refreshes ends when the original refresh token's lifetime runs out, 14 days by default. The user then signs in again. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/). Client credentials do not use refresh tokens. A service requests a new access token when the old one is about to expire. ## Summary | Application | Client type | Grant | Start with | |---|---|---|---| | Single-page application | `public` | Authorization code with PKCE | [JavaScript](https://oltinid.com/docs/quickstarts/javascript/), [React](https://oltinid.com/docs/quickstarts/react/), [Angular](https://oltinid.com/docs/quickstarts/angular/) | | Server-side web application | `confidential` | Authorization code with PKCE and client secret | [Node.js](https://oltinid.com/docs/quickstarts/node/), [.NET](https://oltinid.com/docs/quickstarts/dotnet/), [Go](https://oltinid.com/docs/quickstarts/go/), [Python](https://oltinid.com/docs/quickstarts/python/), [Java](https://oltinid.com/docs/quickstarts/java/) | | Mobile or desktop application | `public` | Authorization code with PKCE | [Mobile and desktop applications](https://oltinid.com/docs/guides/native-applications/) | | Service, daemon or job | `confidential` | Client credentials | [curl](https://oltinid.com/docs/quickstarts/curl/), [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) | | API | Usually none | Validates access tokens | [Node.js](https://oltinid.com/docs/quickstarts/api-node/), [.NET](https://oltinid.com/docs/quickstarts/api-dotnet/), [Go](https://oltinid.com/docs/quickstarts/api-go/), [Python](https://oltinid.com/docs/quickstarts/api-python/), [Java](https://oltinid.com/docs/quickstarts/api-java/) | Add `offline_access` and the refresh token grant to any user-facing row when the user must stay signed in beyond the access token's lifetime. ## Flows OneiD does not support > **Not supported:** OneiD does not support the implicit flow, the hybrid flow, the resource owner password credentials (ROPC) grant, the device authorization flow, token exchange or CIBA. What to use instead: | You were going to use | Use instead | |---|---| | Implicit flow (`response_type=token` or `id_token`) | Authorization code with PKCE as a public client. Only `response_type=code` is accepted. | | Hybrid flow (`code id_token`) | Authorization code with PKCE. | | Password grant, where your application collects the user's password | Authorization code with PKCE, so the user signs in on OneiD's page. For a program without a user, client credentials. | | Device flow, for a device without a usable browser | OneiD has no replacement for this. If the device has a system browser, use authorization code with PKCE as a native application. | | `private_key_jwt` or mTLS client authentication | A client secret, sent with `client_secret_basic`. | ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) - [Register an application](https://oltinid.com/docs/get-started/register-an-application/) - [Standards support](https://oltinid.com/docs/reference/standards-support/) --- # Register an application > Know what your OneiD administrator enters when registering your application, and send them the values they need. Source: https://oltinid.com/docs/get-started/register-an-application/ · Section: Get started · All OneiD documentation: https://oltinid.com/llms.txt Every application that uses OneiD is registered as a client by an administrator, in the admin console or through the Admin API. Developers cannot register clients themselves. This page describes the settings your administrator fills in, the rules your URIs must follow, and a template you can send them. ## What the administrator enters | Setting | What it is | What you decide | |---|---|---| | Client ID | The identifier your application sends as `client_id`. | Suggest a short, readable value, for example `orders-web`. | | Display name | The application's display name. For client credentials, it is also the `name` claim in the access token. | The name your users recognise. | | Client type | `public` or `confidential`. | `public` for browser, mobile and desktop apps. `confidential` for server-side applications and services. | | Grant types | Authorization code, refresh token, client credentials. | See [Choose a flow](https://oltinid.com/docs/get-started/choose-a-flow/). Public clients cannot have client credentials. | | Redirect URIs | Where OneiD may send the user back after sign-in. | Every callback URL, for every environment. | | Post-logout redirect URIs | Where OneiD may send the user after sign-out. | The pages your application shows after sign-out. | | Allowed scopes | The scopes the client may request. | `openid` and the identity and API scopes you need. | | Allowed CORS origins | Origins whose browsers may call the token, userinfo and revocation endpoints. | Browser applications only: the origin your app is served from. | | PKCE | Whether PKCE is required. | Always required for public clients. Required by default for confidential clients; keep it on. | | Consent | Whether users are asked to agree before the first sign-in. | Usually not asked for your own applications, asked for third-party ones. | | Token lifetimes | Access token, ID token and refresh token lifetimes for this client. | Leave the defaults unless you have a reason. | | Client secret | Confidential clients only. One secret per client. | Store it in a secret store, never in source code. | > **Warning:** The administrator sees the client secret only once, when it is created. Ask them to pass it to you through a secure channel, not by email or chat. If it is lost, the administrator must issue a new one. ### Default token lifetimes If the administrator leaves the lifetimes unchanged, OneiD uses these by default: | Token | Default lifetime | |---|---| | Authorisation code | 5 minutes (cannot be changed) | | Access token | 1 hour | | ID token | 20 minutes | | Refresh token | 14 days | ### Consent When consent is asked, OneiD shows a consent page the first time a user signs in to your application with a given set of scopes. The user can untick optional scopes, and OneiD remembers the decision if the user ticks "remember". If the user refuses, your application receives `access_denied`. When consent is not asked, OneiD shows the consent page only if your application sends `prompt=consent`. ### Allowed scopes Request only what you need. Identity scopes are `openid`, `profile`, `email`, `phone`, `roles` and `offline_access`. API scopes, such as `orders.read`, exist only after an administrator creates them. If your application requests a scope that the client is not allowed, OneiD returns `invalid_scope`. Do not ask for `admin_api`, `admin_api_readonly` or `admin_console_webhooks`. These scopes are for administering OneiD itself and are not for applications. ## URI rules OneiD compares redirect URIs character by character. Your application must send exactly a registered value. - **Exact match.** No wildcards, no prefix matching. `https://app.example.com/callback` and `https://app.example.com/callback/` are different URIs. - **https.** Every redirect URI uses https, except loopback addresses. - **http only for loopback.** `http://localhost`, `http://127.0.0.1` and `http://[::1]`, for development and for desktop applications. The port is part of the URI and must match too. - **Private-use schemes for public clients only.** Mobile and desktop apps may use a reverse-domain scheme, such as `com.example.app:/callback`. - **No fragments.** A redirect URI cannot contain `#`. Post-logout redirect URIs follow the same exact-match rule. Allowed CORS origins are written as `scheme://host[:port]`, with no path and no trailing slash, for example `https://app.example.com` or `http://localhost:4200`. After the administrator saves a change, it takes effect within about 15 seconds. > **Tip:** Register one redirect URI for each environment, such as local development, test and production, rather than reusing one value. If you need separate settings per environment, ask for separate clients. If your application sends a `redirect_uri` that is not registered, or a `client_id` that OneiD does not know, OneiD does not redirect back. It shows its own error page with `invalid_request`. See [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Request to your administrator Copy this template, fill it in, and send it to your OneiD administrator. ```text Please register a OneiD client for this application. Application name (shown to users): Orders Suggested client ID: orders-web Client type: confidential | public Grant types: authorization_code refresh_token (only if we request offline_access) client_credentials (services only, confidential only) Redirect URIs (exact): https://app.example.com/callback http://localhost:3000/callback Post-logout redirect URIs (exact): https://app.example.com/signed-out http://localhost:3000/signed-out Scopes we need: openid profile email offline_access (only with the refresh_token grant) API scopes: orders.read orders.write Allowed CORS origins (browser applications only): https://app.example.com http://localhost:3000 Consent: ask users | do not ask (first-party application) PKCE: required (keep the default) Token lifetimes: defaults For confidential clients: please send the client secret through our secret store, not by email or chat. ``` Once the client is registered, you receive the client ID, and for a confidential client the client secret. Your OneiD address is the base URL that your application uses as its authority, `https://YOUR_ONEID`. > **Checkpoint:** Open `https://YOUR_ONEID/.well-known/openid-configuration` in a browser. You see the discovery document, and its `issuer` is `https://YOUR_ONEID/`. ## When a client is disabled An administrator can disable a client. A disabled client gets `unauthorized_client` at the authorize and end session endpoints and `invalid_client` at the other endpoints, and its tokens are revoked. If a client secret has expired, the token endpoint returns `invalid_client` with the description "The client secret has expired." Ask your administrator for a new secret. For every setting and its effect, see [Client settings](https://oltinid.com/docs/reference/client-settings/). ## Learn more - [Client settings](https://oltinid.com/docs/reference/client-settings/) - [Choose a flow](https://oltinid.com/docs/get-started/choose-a-flow/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/) --- # Quickstarts > Complete, runnable examples that connect an application or an API to OneiD. Source: https://oltinid.com/docs/quickstarts/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt Pick the kind of software you are connecting. Each quickstart is one complete program with the client settings it needs, the steps to run it, and what you should see. ## Browser applications Single-page applications that run in the browser. Public client, PKCE, no secret. - [JavaScript single-page application](https://oltinid.com/docs/quickstarts/javascript/): oidc-client-ts - [React single-page application](https://oltinid.com/docs/quickstarts/react/): react-oidc-context - [Angular single-page application](https://oltinid.com/docs/quickstarts/angular/): oidc-client-ts ## Server web applications Applications that render pages on a server and keep a session. Usually a confidential client. - [Node.js web application (Express)](https://oltinid.com/docs/quickstarts/node/): openid-client - [ASP.NET Core web application](https://oltinid.com/docs/quickstarts/dotnet/): Microsoft.AspNetCore.Authentication.OpenIdConnect - [Go web application](https://oltinid.com/docs/quickstarts/go/): coreos/go-oidc - [Python web application (Flask)](https://oltinid.com/docs/quickstarts/python/): Authlib with Flask - [Java web application (Spring Security)](https://oltinid.com/docs/quickstarts/java/): Spring Security ## APIs Services that accept OneiD access tokens and check them without calling OneiD. - [Protect a Node.js API](https://oltinid.com/docs/quickstarts/api-node/): jose with Express - [Protect an ASP.NET Core API](https://oltinid.com/docs/quickstarts/api-dotnet/): Microsoft.AspNetCore.Authentication.JwtBearer - [Protect a Go API](https://oltinid.com/docs/quickstarts/api-go/): coreos/go-oidc - [Protect a Python API (Flask)](https://oltinid.com/docs/quickstarts/api-python/): PyJWT with Flask - [Protect a Java API (Spring Security)](https://oltinid.com/docs/quickstarts/api-java/): Spring Security ## Machine to machine Services that call an API with no user present. Client credentials. - [Machine to machine with cURL](https://oltinid.com/docs/quickstarts/curl/): Client credentials grant ## Something else? Any library that supports OpenID Connect discovery and the authorization code flow with PKCE works with OneiD. Give it the OneiD address and your client ID. The [Connector specification](https://oltinid.com/docs/ai/connector-specification/) lists everything a library or product must do. --- # JavaScript single-page application > Add OneiD to a JavaScript application with oidc-client-ts, step by step. Source: https://oltinid.com/docs/quickstarts/javascript/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a browser application with **oidc-client-ts**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. - A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo). ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `public` (no secret) | | Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) | | Redirect URI | `http://localhost:3000/callback` | | Post-logout redirect URI | `http://localhost:3000` | | Allowed scopes | `openid profile email` | | Allowed CORS origins | `http://localhost:3000` | ## 2. Install ```bash npm install oidc-client-ts ``` ## 3. Add the code `auth.js` ```js import { UserManager } from 'oidc-client-ts'; const oneid = new UserManager({ authority: 'https://auth.oltinid.com', client_id: 'quickstart', redirect_uri: 'http://localhost:3000/callback', post_logout_redirect_uri: 'http://localhost:3000', response_type: 'code', // authorization code flow; the library adds PKCE scope: 'openid profile email', }); // Call from your "Sign in" button. export const signIn = () => oneid.signinRedirect(); // Call once on the /callback page. OneiD sends the user back there. export async function completeSignIn() { const user = await oneid.signinRedirectCallback(); console.log(`Hello, ${user.profile.name}`); return user; // user.access_token is the token for your API } // The signed-in user, or null. export const currentUser = () => oneid.getUser(); // Ends the session in your application and in OneiD. export const signOut = () => oneid.signoutRedirect(); ``` ## 4. Run it Serve the application on port 3000; with Vite: npm run dev -- --port 3000. This code runs in the browser. The client must list the address of your application (here http://localhost:3000) in its allowed CORS origins, or the browser blocks the call to the token endpoint. > **Checkpoint:** After you sign in, OneiD sends the browser to http://localhost:3000/callback. When that page calls completeSignIn(), the browser console shows Hello, followed by the user’s name. ## Common issues - **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port. - **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page. - **A CORS error in the browser console.** The origin `http://localhost:3000` is not in the client’s allowed CORS origins. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/) --- # React single-page application > Add OneiD to a React application with react-oidc-context, step by step. Source: https://oltinid.com/docs/quickstarts/react/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a browser application with **react-oidc-context**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. - A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo). ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `public` (no secret) | | Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) | | Redirect URI | `http://localhost:3000/callback` | | Post-logout redirect URI | `http://localhost:3000` | | Allowed scopes | `openid profile email` | | Allowed CORS origins | `http://localhost:3000` | ## 2. Install ```bash npm install react-oidc-context oidc-client-ts ``` ## 3. Add the code `main.jsx` ```jsx import { createRoot } from 'react-dom/client'; import { AuthProvider, useAuth } from 'react-oidc-context'; const oneid = { authority: 'https://auth.oltinid.com', client_id: 'quickstart', redirect_uri: 'http://localhost:3000/callback', post_logout_redirect_uri: 'http://localhost:3000', scope: 'openid profile email', // Remove ?code=...&state=... from the address bar after sign-in. onSigninCallback: () => window.history.replaceState({}, document.title, '/'), }; function App() { const auth = useAuth(); if (auth.isLoading) return

Loading...

; if (auth.error) return

Sign-in failed: {auth.error.message}

; if (!auth.isAuthenticated) { return ; } return ( <>

Hello, {auth.user?.profile.name}

); } createRoot(document.getElementById('root')).render( , ); ``` ## 4. Run it Serve the application on port 3000; with Vite: npm run dev -- --port 3000. This code runs in the browser. The client must list the address of your application (here http://localhost:3000) in its allowed CORS origins, or the browser blocks the call to the token endpoint. > **Checkpoint:** Open http://localhost:3000 and select Sign in with OneiD. After you sign in, the page shows Hello, followed by the user’s name, and a Sign out button. ## Common issues - **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port. - **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page. - **A CORS error in the browser console.** The origin `http://localhost:3000` is not in the client’s allowed CORS origins. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/) --- # Angular single-page application > Add OneiD to a Angular application with oidc-client-ts, step by step. Source: https://oltinid.com/docs/quickstarts/angular/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a browser application with **oidc-client-ts**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. - A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo). ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `public` (no secret) | | Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) | | Redirect URI | `http://localhost:3000/callback` | | Post-logout redirect URI | `http://localhost:3000` | | Allowed scopes | `openid profile email` | | Allowed CORS origins | `http://localhost:3000` | ## 2. Install ```bash npm install oidc-client-ts ``` ## 3. Add the code `auth.service.ts` ```ts import { Injectable, signal } from '@angular/core'; import { User, UserManager } from 'oidc-client-ts'; @Injectable({ providedIn: 'root' }) export class AuthService { private readonly oneid = new UserManager({ authority: 'https://auth.oltinid.com', client_id: 'quickstart', redirect_uri: 'http://localhost:3000/callback', post_logout_redirect_uri: 'http://localhost:3000', response_type: 'code', // authorization code flow; the library adds PKCE scope: 'openid profile email', }); /** The signed-in user, or null. Templates read it as auth.user(). */ readonly user = signal(null); constructor() { this.oneid.getUser().then((user) => this.user.set(user)); } signIn(): Promise { return this.oneid.signinRedirect(); } /** Call from the component on the 'callback' route, then navigate to your start page. */ async completeSignIn(): Promise { this.user.set(await this.oneid.signinRedirectCallback()); } signOut(): Promise { return this.oneid.signoutRedirect(); } } ``` ## 4. Run it Start the application on port 3000: ng serve --port 3000. This code runs in the browser. The client must list the address of your application (here http://localhost:3000) in its allowed CORS origins, or the browser blocks the call to the token endpoint. > **Checkpoint:** The service shows nothing by itself. After the component on the callback route calls completeSignIn(), auth.user() returns the signed-in user, and auth.user()?.profile.name is the user’s name. ## Common issues - **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port. - **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page. - **A CORS error in the browser console.** The origin `http://localhost:3000` is not in the client’s allowed CORS origins. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/) --- # Node.js web application (Express) > Add OneiD to a Node.js application with openid-client, step by step. Source: https://oltinid.com/docs/quickstarts/node/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a server web application with **openid-client**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. - A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo). ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `public` (no secret) | | Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) | | Redirect URI | `http://localhost:3000/callback` | | Post-logout redirect URI | `http://localhost:3000` | | Allowed scopes | `openid profile email` | ## 2. Install ```bash npm install express express-session openid-client ``` ## 3. Add the code `server.js` ```js import express from 'express'; import session from 'express-session'; import * as client from 'openid-client'; const redirectUri = 'http://localhost:3000/callback'; // Reads the endpoints and signing keys from OneiD. `None` = a public client without a secret. const oneid = await client.discovery( new URL('https://auth.oltinid.com'), 'quickstart', undefined, client.None(), ); const app = express(); app.use(session({ secret: 'change-me', resave: false, saveUninitialized: false })); app.get('/', (req, res) => { const user = req.session.user; res.send(user ? `Hello, ${user.name}. Sign out` : 'Sign in with OneiD'); }); app.get('/login', async (req, res) => { const codeVerifier = client.randomPKCECodeVerifier(); const state = client.randomState(); req.session.oidc = { codeVerifier, state }; res.redirect( client.buildAuthorizationUrl(oneid, { redirect_uri: redirectUri, scope: 'openid profile email', code_challenge: await client.calculatePKCECodeChallenge(codeVerifier), code_challenge_method: 'S256', state, }).href, ); }); app.get('/callback', async (req, res) => { const { codeVerifier, state } = req.session.oidc ?? {}; const tokens = await client.authorizationCodeGrant(oneid, new URL(req.originalUrl, redirectUri), { pkceCodeVerifier: codeVerifier, expectedState: state, }); req.session.user = tokens.claims(); // the checked ID token: sub, name, email req.session.idToken = tokens.id_token; res.redirect('/'); }); app.get('/logout', (req, res) => { const idToken = req.session.idToken; req.session.destroy(() => { res.redirect( client.buildEndSessionUrl(oneid, { id_token_hint: idToken, post_logout_redirect_uri: 'http://localhost:3000', }).href, ); }); }); app.listen(3000, () => console.log('http://localhost:3000')); ``` ## 4. Run it Set "type": "module" in package.json, then run: node server.js. The example uses a public client so that it runs without a secret. For your own server application, use a confidential client and pass its secret to the library. > **Checkpoint:** Open http://localhost:3000 and select Sign in with OneiD. After you sign in, the page shows Hello, followed by the user’s name, and a Sign out link. ## Common issues - **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port. - **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) --- # ASP.NET Core web application > Add OneiD to a .NET application with Microsoft.AspNetCore.Authentication.OpenIdConnect, step by step. Source: https://oltinid.com/docs/quickstarts/dotnet/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a server web application with **Microsoft.AspNetCore.Authentication.OpenIdConnect**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. - A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo). ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `public` (no secret) | | Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) | | Redirect URI | `https://localhost:3000/callback` | | Post-logout redirect URI | `https://localhost:3000/signout-callback-oidc` | | Allowed scopes | `openid profile email` | ## 2. Install ```bash dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect ``` ## 3. Add the code `Program.cs` ```csharp using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; var builder = WebApplication.CreateBuilder(args); builder.Services .AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(options => { options.Authority = "https://auth.oltinid.com"; options.ClientId = "quickstart"; options.ResponseType = "code"; // authorization code flow; PKCE is on by default options.CallbackPath = "/callback"; options.Scope.Add("email"); // openid and profile are requested by default options.SaveTokens = true; options.MapInboundClaims = false; // keep the claim names that OneiD sends options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapGet("/", (HttpContext context) => Results.Content( context.User.Identity?.IsAuthenticated == true ? $"Hello, {context.User.Identity.Name}. Sign out" : "Sign in with OneiD", "text/html")); app.MapGet("/login", () => Results.Challenge(new AuthenticationProperties { RedirectUri = "/" })); // Ends the session in this application and in OneiD. app.MapGet("/logout", () => Results.SignOut( new AuthenticationProperties { RedirectUri = "/" }, [CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme])); // HTTPS: the sign-in cookies of this library need it. Run `dotnet dev-certs https --trust` once. app.Run("https://localhost:3000"); ``` ## 4. Run it Run: dotnet run. The example listens on https://localhost:3000. The example uses a public client so that it runs without a secret. For your own application, use a confidential client and set options.ClientSecret from a secret store. > **Checkpoint:** Open https://localhost:3000 and select Sign in with OneiD. After you sign in, the page shows Hello, followed by the user’s name, and a Sign out link. ## Common issues - **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port. - **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page. - **`Correlation failed`.** The application runs on plain http. Run it on https, as the example does. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) --- # Go web application > Add OneiD to a Go application with coreos/go-oidc, step by step. Source: https://oltinid.com/docs/quickstarts/go/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a server web application with **coreos/go-oidc**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. - A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo). ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `public` (no secret) | | Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) | | Redirect URI | `http://localhost:3000/callback` | | Post-logout redirect URI | `http://localhost:3000` | | Allowed scopes | `openid profile email` | ## 2. Install ```bash go get github.com/coreos/go-oidc/v3/oidc golang.org/x/oauth2 ``` ## 3. Add the code `main.go` ```go package main import ( "context" "fmt" "log" "net/http" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" ) func main() { ctx := context.Background() // Reads the endpoints and signing keys from OneiD. The issuer name ends with a slash. provider, err := oidc.NewProvider(ctx, "https://auth.oltinid.com/") if err != nil { log.Fatal(err) } verifier := provider.Verifier(&oidc.Config{ClientID: "quickstart"}) endpoint := provider.Endpoint() endpoint.AuthStyle = oauth2.AuthStyleInParams // a public client: no secret, no Basic header config := oauth2.Config{ ClientID: "quickstart", Endpoint: endpoint, RedirectURL: "http://localhost:3000/callback", Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, } http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { state, pkce := oauth2.GenerateVerifier(), oauth2.GenerateVerifier() setCookie(w, "state", state) setCookie(w, "pkce", pkce) http.Redirect(w, r, config.AuthCodeURL(state, oauth2.S256ChallengeOption(pkce)), http.StatusFound) }) http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { state, _ := r.Cookie("state") pkce, _ := r.Cookie("pkce") if state == nil || pkce == nil || r.URL.Query().Get("state") != state.Value { http.Error(w, "state does not match", http.StatusBadRequest) return } token, err := config.Exchange(ctx, r.URL.Query().Get("code"), oauth2.VerifierOption(pkce.Value)) if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return } // Check the signature, issuer, audience and expiry of the ID token. rawIDToken, _ := token.Extra("id_token").(string) idToken, err := verifier.Verify(ctx, rawIDToken) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } var claims struct { Name string `json:"name"` Email string `json:"email"` } if err := idToken.Claims(&claims); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } fmt.Fprintf(w, "Hello, %s (%s)", claims.Name, claims.Email) }) log.Println("http://localhost:3000/login") log.Fatal(http.ListenAndServe("localhost:3000", nil)) } func setCookie(w http.ResponseWriter, name, value string) { http.SetCookie(w, &http.Cookie{Name: name, Value: value, Path: "/", HttpOnly: true, MaxAge: 600, SameSite: http.SameSiteLaxMode}) } ``` ## 4. Run it Run: go run . Then open http://localhost:3000/login. The example uses a public client so that it runs without a secret. For your own application, use a confidential client and set ClientSecret in the oauth2.Config. > **Checkpoint:** Open http://localhost:3000/login and the browser goes to OneiD. After you sign in, the page shows Hello, followed by the user’s name and, in parentheses, the email address. ## Common issues - **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port. - **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) --- # Python web application (Flask) > Add OneiD to a Python application with Authlib with Flask, step by step. Source: https://oltinid.com/docs/quickstarts/python/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a server web application with **Authlib with Flask**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. - A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo). ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `public` (no secret) | | Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) | | Redirect URI | `http://localhost:3000/callback` | | Post-logout redirect URI | `http://localhost:3000` | | Allowed scopes | `openid profile email` | ## 2. Install ```bash pip install flask authlib requests ``` ## 3. Add the code `app.py` ```python from authlib.integrations.flask_client import OAuth from flask import Flask, redirect, session, url_for app = Flask(__name__) app.secret_key = "change-me" # signs the session cookie oauth = OAuth(app) oauth.register( name="oneid", # Authlib reads the endpoints and signing keys from OneiD. server_metadata_url="https://auth.oltinid.com/.well-known/openid-configuration", client_id="quickstart", client_kwargs={ "scope": "openid profile email", "code_challenge_method": "S256", # PKCE "token_endpoint_auth_method": "none", # a public client without a secret }, ) @app.route("/") def home(): user = session.get("user") if user is None: return 'Sign in with OneiD' return f'Hello, {user["name"]}. Sign out' @app.route("/login") def login(): return oauth.oneid.authorize_redirect(url_for("callback", _external=True)) @app.route("/callback") def callback(): token = oauth.oneid.authorize_access_token() # exchanges the code and checks the ID token session["user"] = token["userinfo"] # sub, name, email return redirect("/") @app.route("/logout") def logout(): session.clear() return redirect("/") if __name__ == "__main__": app.run(host="localhost", port=3000) ``` ## 4. Run it Run: python app.py. The example uses a public client so that it runs without a secret. Replace the session secret key "change-me" with a random value before you use the code anywhere else. > **Checkpoint:** Open http://localhost:3000 and select Sign in with OneiD. After you sign in, the page shows Hello, followed by the user’s name, and a Sign out link. ## Common issues - **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port. - **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) --- # Java web application (Spring Security) > Add OneiD to a Java application with Spring Security, step by step. Source: https://oltinid.com/docs/quickstarts/java/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a server web application with **Spring Security**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. - A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo). ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `public` (no secret) | | Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) | | Redirect URI | `http://localhost:3000/login/oauth2/code/oneid` | | Post-logout redirect URI | `http://localhost:3000` | | Allowed scopes | `openid profile email` | ## 2. Install Add the dependency org.springframework.boot:spring-boot-starter-oauth2-client. ## 3. Add the code `application.yml` ```yaml server: port: 3000 spring: security: oauth2: client: registration: oneid: client-id: quickstart client-authentication-method: none # a public client; Spring Security adds PKCE authorization-grant-type: authorization_code scope: openid, profile, email redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" provider: oneid: # Spring reads the endpoints and signing keys from OneiD. The issuer name ends with a slash. issuer-uri: https://auth.oltinid.com/ ``` `HomeController.java` ```java package com.example.demo; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; // With spring-boot-starter-oauth2-client on the classpath, every page needs a signed-in user: // Spring Security sends the browser to OneiD and handles the callback. @RestController public class HomeController { @GetMapping("/") public String home(@AuthenticationPrincipal OidcUser user) { return "Hello, " + user.getFullName() + " (" + user.getEmail() + ")"; } } ``` ## 4. Run it Create a Spring Boot project with the Spring Web and OAuth2 Client starters, add these two files, and run it. Spring Security uses the redirect address http://localhost:3000/login/oauth2/code/oneid. > **Checkpoint:** Open http://localhost:3000 and Spring Security sends the browser to OneiD. After you sign in, the page shows Hello, followed by the user’s name and, in parentheses, the email address. ## Common issues - **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port. - **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) --- # Machine to machine with cURL > Add OneiD to a cURL service with Client credentials grant, step by step. Source: https://oltinid.com/docs/quickstarts/curl/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a machine to machine with **Client credentials grant**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours. ## 1. Register the application Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). | Setting | Value | |---|---| | Client type | `confidential` (OneiD generates a secret and shows it once) | | Grant types | `client_credentials` | | Allowed scopes | the API scopes the service needs, for example `orders.read` | ## 2. Add the code `token.sh` ```bash # A service that calls an API with its own identity (no user). An administrator registers # a confidential client with the client credentials grant and gives you its ID and secret. # 1. Read the endpoints. curl https://auth.oltinid.com/.well-known/openid-configuration # 2. Get an access token. curl https://auth.oltinid.com/connect/token \ --user "$CLIENT_ID:$CLIENT_SECRET" \ --data grant_type=client_credentials \ --data scope="$API_SCOPE" # 3. Call your API with the token. curl https://api.example.com/orders \ --header "Authorization: Bearer $ACCESS_TOKEN" ``` ## 3. Run it Set YOUR_CLIENT_ID and YOUR_CLIENT_SECRET, then run the commands one by one. A client credentials client needs a secret, so it is always a confidential client. > **Checkpoint:** The first command prints the discovery document as JSON. The second command prints a JSON object; its access_token value is the token that the third command sends to your API. ## Common issues - **`invalid_client`.** The client ID or secret is wrong, the secret expired, or the client is disabled. - **`invalid_scope`.** The client is not allowed the scope it asks for. - **HTTP 429.** Too many token requests. Cache the token until shortly before it expires and wait for the `Retry-After` time. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) - [Rate limits](https://oltinid.com/docs/reference/rate-limits/) - [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) --- # Protect a Node.js API > Add OneiD to a Node.js API API with jose with Express, step by step. Source: https://oltinid.com/docs/quickstarts/api-node/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a api with **jose with Express**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - An access token with the scope `orders.read` to test with. ## 1. Register the application An API is not a client: it does not sign in. It needs an **API scope** that clients can request. 1. Ask your OneiD administrator to create the API scope `orders.read` (or a scope named after your API) in the admin console. 2. Ask for the scope to be allowed on the client that will call the API. 3. Get an access token with that scope: from a [server or browser application](https://oltinid.com/docs/quickstarts/), or with the [client credentials grant](https://oltinid.com/docs/quickstarts/curl/). > **Note:** OneiD access tokens have no `aud` claim. The API accepts a token because it was issued by your OneiD, is unexpired and carries the scope the API requires. Give each API its own scopes. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/). ## 2. Install ```bash npm install express jose ``` ## 3. Add the code `server.js` ```js import express from 'express'; import { createRemoteJWKSet, jwtVerify } from 'jose'; // Reads the issuer name and the address of the signing keys from OneiD. const oneid = await (await fetch('https://auth.oltinid.com/.well-known/openid-configuration')).json(); // jose downloads the public keys on first use and again when OneiD rotates them. const keys = createRemoteJWKSet(new URL(oneid.jwks_uri)); // Lets a request pass only with a valid access token that carries the scope. const requireScope = (scope) => async (req, res, next) => { const token = req.headers.authorization?.replace(/^Bearer /, ''); try { // Checks the signature, the issuer and the expiry. No audience: OneiD tokens have no aud claim. const { payload } = await jwtVerify(token, keys, { issuer: oneid.issuer, algorithms: ['RS256'] }); req.user = payload; } catch { return res.sendStatus(401); // no token, or the token is not valid } // The scope claim is one string with a space between the scopes. if (!req.user.scope?.split(' ').includes(scope)) return res.sendStatus(403); next(); }; const app = express(); app.get('/orders', requireScope('orders.read'), (req, res) => { res.json({ sub: req.user.sub }); }); app.listen(4000, () => console.log('http://localhost:4000/orders')); ``` ## 4. Run it Set "type": "module" in package.json, then run: node server.js. The API listens on http://localhost:4000. GET /orders answers 401 without a valid token, 403 without the scope orders.read, and otherwise returns the sub of the caller. Test it with a token: ```bash curl -i http://localhost:4000/orders # HTTP/1.1 401 Unauthorized curl -i http://localhost:4000/orders -H "Authorization: Bearer $ACCESS_TOKEN" # HTTP/1.1 200 OK when the token carries orders.read, 403 when it does not ``` > **Checkpoint:** Without a token the API answers 401. With a valid token that carries `orders.read` it answers 200 and returns your `sub`; with a valid token without that scope it answers 403. ## Common issues - **401 with a token that looks valid.** Check the issuer: OneiD’s issuer ends with a slash (`https://YOUR_ONEID/`). - **The API checks `aud` and rejects every token.** OneiD access tokens carry no `aud`; remove the audience check and check the scope instead. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) --- # Protect an ASP.NET Core API > Add OneiD to a .NET API API with Microsoft.AspNetCore.Authentication.JwtBearer, step by step. Source: https://oltinid.com/docs/quickstarts/api-dotnet/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a api with **Microsoft.AspNetCore.Authentication.JwtBearer**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - An access token with the scope `orders.read` to test with. ## 1. Register the application An API is not a client: it does not sign in. It needs an **API scope** that clients can request. 1. Ask your OneiD administrator to create the API scope `orders.read` (or a scope named after your API) in the admin console. 2. Ask for the scope to be allowed on the client that will call the API. 3. Get an access token with that scope: from a [server or browser application](https://oltinid.com/docs/quickstarts/), or with the [client credentials grant](https://oltinid.com/docs/quickstarts/curl/). > **Note:** OneiD access tokens have no `aud` claim. The API accepts a token because it was issued by your OneiD, is unexpired and carries the scope the API requires. Give each API its own scopes. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/). ## 2. Install ```bash dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer ``` ## 3. Add the code `Program.cs` ```csharp using System.Security.Claims; using Microsoft.AspNetCore.Authentication.JwtBearer; var builder = WebApplication.CreateBuilder(args); builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // Reads the issuer name and signing keys from OneiD; checks the signature, issuer and expiry. options.Authority = "https://auth.oltinid.com"; options.TokenValidationParameters.ValidateAudience = false; // OneiD tokens have no aud claim options.MapInboundClaims = false; // keep the claim names that OneiD sends }); builder.Services.AddAuthorization(options => { // The scope claim is one string with a space between the scopes. options.AddPolicy("orders.read", policy => policy.RequireAssertion(context => context.User.FindFirst("scope")?.Value.Split(' ').Contains("orders.read") == true)); }); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapGet("/orders", (ClaimsPrincipal user) => new { sub = user.FindFirst("sub")?.Value }) .RequireAuthorization("orders.read"); app.Run("http://localhost:4000"); ``` ## 4. Run it Run: dotnet run. The API listens on http://localhost:4000. GET /orders answers 401 without a valid token, 403 without the scope orders.read, and otherwise returns the sub of the caller. Test it with a token: ```bash curl -i http://localhost:4000/orders # HTTP/1.1 401 Unauthorized curl -i http://localhost:4000/orders -H "Authorization: Bearer $ACCESS_TOKEN" # HTTP/1.1 200 OK when the token carries orders.read, 403 when it does not ``` > **Checkpoint:** Without a token the API answers 401. With a valid token that carries `orders.read` it answers 200 and returns your `sub`; with a valid token without that scope it answers 403. ## Common issues - **401 with a token that looks valid.** Check the issuer: OneiD’s issuer ends with a slash (`https://YOUR_ONEID/`). - **The API checks `aud` and rejects every token.** OneiD access tokens carry no `aud`; remove the audience check and check the scope instead. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) --- # Protect a Go API > Add OneiD to a Go API API with coreos/go-oidc, step by step. Source: https://oltinid.com/docs/quickstarts/api-go/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a api with **coreos/go-oidc**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - An access token with the scope `orders.read` to test with. ## 1. Register the application An API is not a client: it does not sign in. It needs an **API scope** that clients can request. 1. Ask your OneiD administrator to create the API scope `orders.read` (or a scope named after your API) in the admin console. 2. Ask for the scope to be allowed on the client that will call the API. 3. Get an access token with that scope: from a [server or browser application](https://oltinid.com/docs/quickstarts/), or with the [client credentials grant](https://oltinid.com/docs/quickstarts/curl/). > **Note:** OneiD access tokens have no `aud` claim. The API accepts a token because it was issued by your OneiD, is unexpired and carries the scope the API requires. Give each API its own scopes. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/). ## 2. Install ```bash go get github.com/coreos/go-oidc/v3/oidc ``` ## 3. Add the code `main.go` ```go package main import ( "context" "encoding/json" "log" "net/http" "slices" "strings" "github.com/coreos/go-oidc/v3/oidc" ) func main() { // Reads the address of the signing keys from OneiD. The address ends with a slash, because // go-oidc compares it with the issuer name in the discovery document, and that name has one. provider, err := oidc.NewProvider(context.Background(), "https://auth.oltinid.com/") if err != nil { log.Fatal(err) } // Checks the signature, the issuer and the expiry. SkipClientIDCheck: OneiD tokens have no aud claim. verifier := provider.Verifier(&oidc.Config{SkipClientIDCheck: true}) http.HandleFunc("GET /orders", func(w http.ResponseWriter, r *http.Request) { raw, found := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ") if !found { http.Error(w, "no access token", http.StatusUnauthorized) return } token, err := verifier.Verify(r.Context(), raw) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } var claims struct { Scope string `json:"scope"` // one string with a space between the scopes } if err := token.Claims(&claims); err != nil || !slices.Contains(strings.Fields(claims.Scope), "orders.read") { http.Error(w, "the scope orders.read is required", http.StatusForbidden) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"sub": token.Subject}) }) log.Println("http://localhost:4000/orders") log.Fatal(http.ListenAndServe("localhost:4000", nil)) } ``` ## 4. Run it Run: go run . The API listens on http://localhost:4000. GET /orders answers 401 without a valid token, 403 without the scope orders.read, and otherwise returns the sub of the caller. Test it with a token: ```bash curl -i http://localhost:4000/orders # HTTP/1.1 401 Unauthorized curl -i http://localhost:4000/orders -H "Authorization: Bearer $ACCESS_TOKEN" # HTTP/1.1 200 OK when the token carries orders.read, 403 when it does not ``` > **Checkpoint:** Without a token the API answers 401. With a valid token that carries `orders.read` it answers 200 and returns your `sub`; with a valid token without that scope it answers 403. ## Common issues - **401 with a token that looks valid.** Check the issuer: OneiD’s issuer ends with a slash (`https://YOUR_ONEID/`). - **The API checks `aud` and rejects every token.** OneiD access tokens carry no `aud`; remove the audience check and check the scope instead. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) --- # Protect a Python API (Flask) > Add OneiD to a Python API API with PyJWT with Flask, step by step. Source: https://oltinid.com/docs/quickstarts/api-python/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a api with **PyJWT with Flask**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - An access token with the scope `orders.read` to test with. ## 1. Register the application An API is not a client: it does not sign in. It needs an **API scope** that clients can request. 1. Ask your OneiD administrator to create the API scope `orders.read` (or a scope named after your API) in the admin console. 2. Ask for the scope to be allowed on the client that will call the API. 3. Get an access token with that scope: from a [server or browser application](https://oltinid.com/docs/quickstarts/), or with the [client credentials grant](https://oltinid.com/docs/quickstarts/curl/). > **Note:** OneiD access tokens have no `aud` claim. The API accepts a token because it was issued by your OneiD, is unexpired and carries the scope the API requires. Give each API its own scopes. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/). ## 2. Install ```bash pip install flask "pyjwt[crypto]" ``` ## 3. Add the code `app.py` ```python import json from urllib.request import urlopen import jwt from flask import Flask, abort, jsonify, request app = Flask(__name__) # Reads the issuer name and the address of the signing keys from OneiD. with urlopen("https://auth.oltinid.com/.well-known/openid-configuration") as response: oneid = json.load(response) # PyJWKClient downloads the public keys and finds the key that signed a token. keys = jwt.PyJWKClient(oneid["jwks_uri"]) def check_token(scope): """Returns the claims of the access token, or stops the request with 401 or 403.""" kind, _, token = request.headers.get("Authorization", "").partition(" ") if kind != "Bearer": abort(401) try: # Checks the signature, the issuer and the expiry. No audience: OneiD tokens have no aud claim. claims = jwt.decode( token, keys.get_signing_key_from_jwt(token).key, algorithms=["RS256"], issuer=oneid["issuer"], options={"require": ["exp", "iss", "sub"]}, ) except jwt.PyJWTError: abort(401) # The scope claim is one string with a space between the scopes. if scope not in claims.get("scope", "").split(): abort(403) return claims @app.route("/orders") def orders(): claims = check_token("orders.read") return jsonify(sub=claims["sub"]) if __name__ == "__main__": app.run(host="localhost", port=4000) ``` ## 4. Run it Run: python app.py. The API listens on http://localhost:4000. GET /orders answers 401 without a valid token, 403 without the scope orders.read, and otherwise returns the sub of the caller. Test it with a token: ```bash curl -i http://localhost:4000/orders # HTTP/1.1 401 Unauthorized curl -i http://localhost:4000/orders -H "Authorization: Bearer $ACCESS_TOKEN" # HTTP/1.1 200 OK when the token carries orders.read, 403 when it does not ``` > **Checkpoint:** Without a token the API answers 401. With a valid token that carries `orders.read` it answers 200 and returns your `sub`; with a valid token without that scope it answers 403. ## Common issues - **401 with a token that looks valid.** Check the issuer: OneiD’s issuer ends with a slash (`https://YOUR_ONEID/`). - **The API checks `aud` and rejects every token.** OneiD access tokens carry no `aud`; remove the audience check and check the scope instead. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) --- # Protect a Java API (Spring Security) > Add OneiD to a Java API API with Spring Security, step by step. Source: https://oltinid.com/docs/quickstarts/api-java/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt This quickstart adds OneiD to a api with **Spring Security**. Every file is complete and runs as it is. > **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/). ## Before you start - A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own. - An access token with the scope `orders.read` to test with. ## 1. Register the application An API is not a client: it does not sign in. It needs an **API scope** that clients can request. 1. Ask your OneiD administrator to create the API scope `orders.read` (or a scope named after your API) in the admin console. 2. Ask for the scope to be allowed on the client that will call the API. 3. Get an access token with that scope: from a [server or browser application](https://oltinid.com/docs/quickstarts/), or with the [client credentials grant](https://oltinid.com/docs/quickstarts/curl/). > **Note:** OneiD access tokens have no `aud` claim. The API accepts a token because it was issued by your OneiD, is unexpired and carries the scope the API requires. Give each API its own scopes. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/). ## 2. Install Add the dependencies org.springframework.boot:spring-boot-starter-web and org.springframework.boot:spring-boot-starter-oauth2-resource-server. ## 3. Add the code `application.yml` ```yaml server: port: 4000 ``` `SecurityConfig.java` ```java package com.example.api; import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtIssuerValidator; import org.springframework.security.oauth2.jwt.JwtTimestampValidator; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfig { @Bean SecurityFilterChain api(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(requests -> requests // Spring Security makes the authority SCOPE_ for each scope in the scope claim. .requestMatchers(HttpMethod.GET, "/orders").hasAuthority("SCOPE_orders.read") .anyRequest().denyAll()) .oauth2ResourceServer(server -> server.jwt(Customizer.withDefaults())) .build(); } @Bean JwtDecoder jwtDecoder() { NimbusJwtDecoder decoder = NimbusJwtDecoder .withJwkSetUri("https://auth.oltinid.com/.well-known/jwks") // OneiD's public signing keys // OneiD access tokens have the header typ "at+jwt". Without this line, Spring Security // accepts only "JWT" and refuses them. .jwtProcessorCustomizer(processor -> processor.setJWSTypeVerifier( new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("at+jwt")))) .build(); // Checks the expiry and the issuer; the issuer name ends with a slash. There is no // audience check, because OneiD tokens have no aud claim. decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>( new JwtTimestampValidator(), new JwtIssuerValidator("https://auth.oltinid.com/"))); return decoder; } } ``` `OrdersController.java` ```java package com.example.api; import java.util.Map; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrdersController { // The Jwt is the access token that Spring Security checked. @GetMapping("/orders") public Map orders(@AuthenticationPrincipal Jwt token) { return Map.of("sub", token.getSubject()); } } ``` ## 4. Run it Create a Spring Boot project with these two starters, add these three files, and run it. The API listens on http://localhost:4000. GET /orders answers 401 without a valid token, 403 without the scope orders.read, and otherwise returns the sub of the caller. Test it with a token: ```bash curl -i http://localhost:4000/orders # HTTP/1.1 401 Unauthorized curl -i http://localhost:4000/orders -H "Authorization: Bearer $ACCESS_TOKEN" # HTTP/1.1 200 OK when the token carries orders.read, 403 when it does not ``` > **Checkpoint:** Without a token the API answers 401. With a valid token that carries `orders.read` it answers 200 and returns your `sub`; with a valid token without that scope it answers 403. ## Common issues - **401 with a token that looks valid.** Check the issuer: OneiD’s issuer ends with a slash (`https://YOUR_ONEID/`). - **The API checks `aud` and rejects every token.** OneiD access tokens carry no `aud`; remove the audience check and check the scope instead. - More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Learn more - [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) --- # Authorization code flow with PKCE > Sign users in with OneiD using the authorization code flow with PKCE, from the authorisation request to a validated ID token. Source: https://oltinid.com/docs/guides/authorization-code-pkce/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt Every application that signs users in with OneiD uses the authorization code flow with PKCE. This guide shows each request and response, so you can check what your library does or build the flow yourself. It applies to public clients (browser, mobile, desktop) and confidential clients (server-side) alike; only the token request differs. ## Before you start You need: - a registered client with the authorization code grant (see [Register an application](https://oltinid.com/docs/get-started/register-an-application/)) - your client ID, and for a confidential client the client secret - a redirect URI registered for the client, exactly as your application will send it - your OneiD address, `https://YOUR_ONEID` Your library reads the endpoints from `https://YOUR_ONEID/.well-known/openid-configuration`. The examples below use the paths directly. ## How it works ```text 1. Create code_verifier, state and nonce. Derive code_challenge. 2. Redirect the browser to /connect/authorize. 3. The user signs in at OneiD (and consents, if asked). 4. OneiD redirects back to your redirect_uri with code, state and iss. 5. Check state and iss. Exchange code + code_verifier at /connect/token. 6. Validate the ID token. Start your application session. 7. Call your API with the access token. ``` ## Step 1: Create the PKCE values, state and nonce For every sign-in, create three random values and keep them where your callback can find them, such as the server session or, in a browser application, session storage: - **`code_verifier`**: 43 to 128 characters from `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, `~`. Keep it secret until step 5. - **`state`**: protects the callback against cross-site request forgery. - **`nonce`**: ties the ID token to this sign-in. Then derive the `code_challenge`: the SHA-256 hash of the verifier, Base64url-encoded without padding. Always use `S256`. ```js function base64url(bytes) { return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } const random = () => base64url(crypto.getRandomValues(new Uint8Array(32))); const codeVerifier = random(); const state = random(); const nonce = random(); const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier)); const codeChallenge = base64url(new Uint8Array(digest)); ``` The same in a shell, for testing: ```bash CODE_VERIFIER=$(openssl rand -base64 64 | tr -d '\n=' | tr '+/' '-_' | cut -c1-64) CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr -d '\n=' | tr '+/' '-_') ``` ## Step 2: Send the authorisation request Redirect the user's browser to the authorize endpoint. Line breaks are for reading only. ```http GET /connect/authorize ?client_id=YOUR_CLIENT_ID &response_type=code &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback &scope=openid%20profile%20email%20offline_access &state=hX3n9qT2vYw8LkP0 &nonce=Qm4rT8zWc1JpVb6s &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 HTTP/1.1 Host: YOUR_ONEID ``` | Parameter | Required | Value | |---|---|---| | `client_id` | Yes | Your client ID. | | `response_type` | Yes | `code`. No other response type is accepted. | | `redirect_uri` | Yes | A registered redirect URI, exactly. | | `scope` | Yes | Space-separated. Include `openid` to receive an ID token. Add `offline_access` for a refresh token. | | `code_challenge` | Yes | The value from step 1. | | `code_challenge_method` | Yes | `S256`. | | `state` | Recommended | The value from step 1. | | `nonce` | Recommended | The value from step 1. | | `response_mode` | No | `query` (default) or `form_post`. | | `prompt`, `max_age`, `id_token_hint` | No | See [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/). | | `claims` | No | Requests individual claims for the `id_token` or `userinfo`. Only claims within allowed and consented scopes are released. | OneiD ignores `login_hint`, `ui_locales`, `display` and `acr_values`. It refuses `request` and `request_uri` with `request_not_supported` and `request_uri_not_supported`. If the `client_id` is unknown or the `redirect_uri` is not registered, OneiD does not redirect back. It shows its own error page with `invalid_request`. ## Step 3: The user signs in OneiD shows its sign-in page if the user has no OneiD session, and asks for an authenticator code if MFA applies. If the client asks for consent, OneiD shows the consent page. Your application is not involved in this step. ## Step 4: Receive the callback OneiD redirects the browser to your redirect URI: ```http HTTP/1.1 302 Found Location: https://app.example.com/callback?code=EXAMPLE_AUTHORIZATION_CODE&state=hX3n9qT2vYw8LkP0&iss=https%3A%2F%2FYOUR_ONEID%2F ``` Before you use the code: 1. Check that `state` equals the value you stored. If not, stop. 2. Check that `iss` equals the issuer from discovery, `https://YOUR_ONEID/`. This shows the response came from the OneiD you called. If sign-in did not succeed, the callback carries an error instead of a code: ```http GET /callback?error=access_denied&state=hX3n9qT2vYw8LkP0 HTTP/1.1 Host: app.example.com ``` `access_denied` means the user refused consent. For the other codes, see [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). The code is valid for 5 minutes and works once. ## Step 5: Exchange the code for tokens ### Public client A public client sends its `client_id` in the body and no secret. PKCE proves that the same application started the sign-in. ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=EXAMPLE_AUTHORIZATION_CODE &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback &code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk &client_id=YOUR_CLIENT_ID ``` ### Confidential client A confidential client authenticates with `client_secret_basic`: the client ID and secret, joined by a colon and Base64-encoded, in the `Authorization` header. ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=EXAMPLE_AUTHORIZATION_CODE &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback &code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk ``` The same with curl: ```bash curl -s https://YOUR_ONEID/connect/token \ -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \ -d grant_type=authorization_code \ -d code=EXAMPLE_AUTHORIZATION_CODE \ --data-urlencode redirect_uri=https://app.example.com/callback \ -d code_verifier="$CODE_VERIFIER" ``` OneiD also accepts `client_secret_post` (the secret as `client_secret` in the body). Prefer `client_secret_basic`. ### The token response ```json { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ACCESS_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE", "token_type": "Bearer", "expires_in": 3600, "scope": "openid profile email offline_access", "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ID_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE", "refresh_token": "EXAMPLE_OPAQUE_REFRESH_TOKEN_7Hq2Lm9Xv4Rk" } ``` `refresh_token` is present only when you requested `offline_access` and the client has the refresh token grant. Treat it as opaque. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/). If the exchange fails, OneiD returns a JSON error response with an `error` field. `invalid_grant` means the code expired, was already used, or the `code_verifier` does not match. Do not retry with the same code; start a new sign-in. ## Step 6: Validate the ID token The ID token is a JWT signed with RS256. A decoded payload looks like this: ```json { "iss": "https://YOUR_ONEID/", "sub": "8d0c6a3e-2f4b-4c1e-9a77-1b2c3d4e5f60", "aud": "YOUR_CLIENT_ID", "exp": 1767226800, "iat": 1767225600, "nonce": "Qm4rT8zWc1JpVb6s", "auth_time": 1767225590, "amr": ["pwd", "mfa"], "acr": "urn:oltin:ac:mfa", "name": "Alex Example", "preferred_username": "alex", "email": "alex@example.com", "email_verified": true, "idp": "local" } ``` Your library should check, and you should confirm that it does: 1. **Signature.** Fetch the keys from `jwks_uri` (`https://YOUR_ONEID/.well-known/jwks`), pick the key whose `kid` matches the token header, and verify with RS256. Cache the keys, and re-fetch when a token carries an unknown `kid`. 2. **`iss`** equals `https://YOUR_ONEID/`, including the trailing slash. 3. **`aud`** equals your client ID. 4. **`exp`** is in the future. ID tokens last 20 minutes by default. 5. **`nonce`** equals the value you stored in step 1. Then use `iss` and `sub` together as the user's key in your application. Never key users by email. Ignore claims you do not recognise, including private claims whose names start with `oi_`. The ID token is for your application only. Send the access token, not the ID token, to APIs. ## Step 7: Use the access token Send the access token as a bearer token: ```http GET /orders HTTP/1.1 Host: api.example.com Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ACCESS_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE ``` To read the user's claims from OneiD, call the userinfo endpoint with the same token: ```bash curl -s https://YOUR_ONEID/connect/userinfo \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ## Using form_post With `response_mode=form_post`, OneiD returns the code in an HTML form that the browser posts to your redirect URI, instead of in the query string. The code then does not appear in the browser history or in server logs of the URL. ```http POST /callback HTTP/1.1 Host: app.example.com Content-Type: application/x-www-form-urlencoded code=EXAMPLE_AUTHORIZATION_CODE&state=hX3n9qT2vYw8LkP0&iss=https%3A%2F%2FYOUR_ONEID%2F ``` Your callback must accept POST. The post comes from OneiD's origin, so a cookie with `SameSite=Lax` or `Strict` is not sent with it. If your application keeps `state` or the verifier in a cookie, that cookie needs `SameSite=None; Secure`. Use `query` or `form_post`. Discovery also lists `fragment`, but it is not recommended. ## Security notes - Use a new `code_verifier`, `state` and `nonce` for every sign-in. Use `S256` only. - Check `state` and `iss` on the callback, and `nonce` in the ID token. - Do not reload the callback page. The code works once; a second exchange returns `invalid_grant`. - Keep the client secret on the server. A browser, mobile or desktop application is a public client and has no secret. - Run your application on https. Some frameworks, such as ASP.NET Core, refuse the callback with "Correlation failed" when the application runs on plain http. - Do not build the authorisation request from user input. Use a fixed, registered redirect URI. ## Learn more - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/) --- # Client credentials for services > Get an access token for a service, job or daemon that calls an API without a user, and reuse it until it is about to expire. Source: https://oltinid.com/docs/guides/client-credentials/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt When a program calls an API on its own behalf, with no user involved, it uses the client credentials grant. The program authenticates to OneiD with its client ID and secret and receives an access token. This guide shows the request, the response, what the token contains and how to cache it. ## Before you start Ask your OneiD administrator for a client with: - client type `confidential` - the client credentials grant - the API scopes the service needs, for example `orders.read` You receive a client ID and a client secret. Public clients cannot use client credentials. > **Warning:** The client secret is a password for your service. Keep it in a secret store or an environment variable supplied at run time. Never commit it to source control or write it to logs. ## Request a token Send a POST to the token endpoint with `grant_type=client_credentials` and the scopes you need. ### With client_secret_basic (recommended) The client ID and secret go in the `Authorization` header, joined by a colon and Base64-encoded. ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&scope=orders.read ``` ```bash curl -s https://YOUR_ONEID/connect/token \ -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \ -d grant_type=client_credentials \ -d scope=orders.read ``` ### With client_secret_post The client ID and secret go in the form body. OneiD accepts this, but prefer `client_secret_basic`, because a body is more likely to be logged by tools along the way. ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&scope=orders.read&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET ``` ```bash curl -s https://YOUR_ONEID/connect/token \ -d grant_type=client_credentials \ -d scope=orders.read \ -d client_id=YOUR_CLIENT_ID \ -d client_secret=YOUR_CLIENT_SECRET ``` To request several scopes, separate them with spaces: `scope=orders.read orders.write` (URL-encoded as `orders.read%20orders.write`). A scope that the client is not allowed returns `invalid_scope`. > **Not supported:** OneiD authenticates clients with a client secret only. `private_key_jwt` and mTLS client authentication cannot be used. ## The response ```json { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ACCESS_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE", "token_type": "Bearer", "expires_in": 3600, "scope": "orders.read" } ``` There is no ID token, because no user signed in, and no refresh token. When the access token is about to expire, request a new one with the same call. `expires_in` is in seconds. Access tokens last 1 hour by default; your administrator can change this for the client. > **Checkpoint:** The curl command prints JSON with an `access_token` and `"token_type": "Bearer"`. If you see an error instead, check the client ID and secret, then ask your administrator to confirm that the client is enabled, is `confidential` and has the client credentials grant and the scope you requested. ## What the token contains The access token is a JWT signed with RS256. A decoded payload looks like this: ```json { "iss": "https://YOUR_ONEID/", "sub": "YOUR_CLIENT_ID", "client_id": "YOUR_CLIENT_ID", "name": "Orders sync job", "scope": "orders.read", "iat": 1767225600, "exp": 1767229200 } ``` - **`sub`** is the client ID. There is no user. - **`name`** is the client's display name, as registered by the administrator. - **`scope`** lists the granted scopes, separated by spaces. - There is no `aud` claim. An API checks the issuer, signature, expiry and scope instead. An API can tell a service token from a user token by comparing `sub` with `client_id`, or by the scopes it requires. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/). ## Call the API ```http GET /orders HTTP/1.1 Host: api.example.com Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ACCESS_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE ``` ## Cache the token Request a token once and reuse it for every call until it is close to expiry. Requesting a new token for each API call slows your service down and can hit OneiD's rate limits. A simple pattern: 1. On the first call, request a token and remember it with its expiry time: now plus `expires_in`. 2. Before each API call, reuse the cached token if it has more than a minute left. 3. Otherwise request a new one. If several threads need a token at the same moment, let one request it and the others wait for the result. 4. If the API answers 401, discard the cached token, request a new one and retry once. ```js let cached = null; async function getToken() { if (cached && cached.expiresAt - Date.now() > 60_000) return cached.token; const res = await fetch('https://YOUR_ONEID/connect/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: 'Basic ' + Buffer.from(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`).toString('base64'), }, body: new URLSearchParams({ grant_type: 'client_credentials', scope: 'orders.read' }), }); if (!res.ok) throw new Error(`Token request failed: ${res.status}`); const body = await res.json(); cached = { token: body.access_token, expiresAt: Date.now() + body.expires_in * 1000 }; return cached.token; } ``` Most OAuth client libraries, and many HTTP client frameworks, cache client credentials tokens for you. Check that yours does before writing your own. ## Handle rate limiting The token endpoint is rate limited. When a service sends too many requests, OneiD answers HTTP 429: ```http HTTP/1.1 429 Too Many Requests Retry-After: 12 Content-Type: application/json {"error":"slow_down","error_description":"Too many requests. Try again in 12 seconds."} ``` Wait for the number of seconds in `Retry-After` before trying again. Do not retry in a tight loop. Caching tokens as shown above keeps a service well clear of the limits. See [Rate limits](https://oltinid.com/docs/reference/rate-limits/). ## When the secret changes or expires If the client secret has expired, OneiD returns `invalid_client` with the description "The client secret has expired." If your administrator disables the secret or the client, existing tokens are revoked and new requests fail. Read the secret from configuration at start-up so you can replace it without a code change. ## Learn more - [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) - [curl quickstart](https://oltinid.com/docs/quickstarts/curl/) - [Rate limits](https://oltinid.com/docs/reference/rate-limits/) --- # Refresh tokens > Keep users signed in beyond the access token lifetime with OneiD refresh tokens, and handle rotation, reuse and failures correctly. Source: https://oltinid.com/docs/guides/refresh-tokens/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt A refresh token lets your application get a new access token without sending the user back to OneiD. OneiD rotates refresh tokens on every use and ends a chain of refreshes when the original lifetime runs out. This guide shows how to request, use, store and revoke them. ## Get a refresh token OneiD issues a refresh token when both are true: - the client has the refresh token grant, set by your administrator, and - the authorisation request includes the `offline_access` scope. ```http GET /connect/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid%20profile%20offline_access&state=hX3n9qT2vYw8LkP0&nonce=Qm4rT8zWc1JpVb6s&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256 HTTP/1.1 Host: YOUR_ONEID ``` The token response after the code exchange then contains `refresh_token`. See [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) for the full flow. If the client does not have the refresh token grant, or you leave out `offline_access`, there is no refresh token. Client credentials never return one. Refresh tokens are opaque. Do not parse them, and never send them to an API. ## Use a refresh token Send it to the token endpoint with `grant_type=refresh_token`. A public client sends its client ID: ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Content-Type: application/x-www-form-urlencoded grant_type=refresh_token &refresh_token=EXAMPLE_OPAQUE_REFRESH_TOKEN_7Hq2Lm9Xv4Rk &client_id=YOUR_CLIENT_ID ``` A confidential client authenticates as it does for the code exchange: ```bash curl -s https://YOUR_ONEID/connect/token \ -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \ -d grant_type=refresh_token \ -d refresh_token=EXAMPLE_OPAQUE_REFRESH_TOKEN_7Hq2Lm9Xv4Rk ``` The response contains a new access token and a new refresh token: ```json { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ACCESS_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE", "token_type": "Bearer", "expires_in": 3600, "scope": "openid profile offline_access", "refresh_token": "EXAMPLE_OPAQUE_REFRESH_TOKEN_Np5Wc8Ds1Ky3" } ``` > **Checkpoint:** The `refresh_token` in the response differs from the one you sent. Your application now stores the new value and discards the old one. ## Rotation: always store the new refresh token Every successful refresh returns a new refresh token. The one you sent is now used. Replace it in your store before you do anything else with the response. If the same refresh token is used again after a short grace period of about 30 seconds, OneiD: 1. refuses the request with `invalid_grant`, and 2. revokes the whole chain, including the newer refresh token issued from it. The user must then sign in again. This protects users when a refresh token is stolen: either the thief or your application uses it second, and the chain ends. ### Avoid accidental reuse Reuse usually comes from your own application refreshing twice at once, for example two browser tabs or two worker threads that notice an expired access token at the same moment. - **Serialise refreshes.** Let one caller refresh, and let the others wait for its result. In a browser application, coordinate tabs, for example with a lock or by keeping tokens in one tab. - **Write the new token before you release the lock.** A second caller must read the new refresh token, not the one that was just used. - **Do not rely on the grace period.** It covers a lost response or a retry within a few seconds. It is not a design for parallel refreshes. - **Refresh shortly before expiry, not on every request.** Use `expires_in` to know when the access token runs out. ## Lifetime: not sliding Refresh tokens last 14 days by default; your administrator can change this per client. The lifetime is not sliding: each new refresh token in a chain expires when the original one would have. When the chain reaches the end, the refresh fails with `invalid_grant` and the user signs in again. Plan for this. Users of an application they open every day still see a sign-in at the end of the refresh token lifetime. If they still have a OneiD session, that sign-in completes without a password prompt. ## Each refresh checks the user OneiD re-checks the user on every refresh. The refresh fails with `invalid_grant` when the user: - has been deleted - is locked - must change their password - must enrol in MFA OneiD also revokes refresh tokens itself when the user's password changes, an administrator removes a role, locks the user, resets the user's MFA or sets a temporary password, and when the user signs out of your application. A refresh after any of these fails with `invalid_grant`. ## Handle a failed refresh Treat `invalid_grant` from a refresh as "this session is over": 1. Delete the stored refresh token and access token. 2. End the user's session in your application. 3. Send the user to sign in again with a new authorisation request. Do not retry the same refresh token. Retrying cannot succeed, and after the grace period it counts as reuse. Other responses: | Response | Meaning | What to do | |---|---|---| | `invalid_grant` | Expired, used, revoked, or the user can no longer sign in. | Sign the user in again. | | `invalid_client` | Wrong secret, expired secret or disabled client. | Fix the configuration; ask your administrator. | | HTTP 429 with `slow_down` | Too many requests. | Wait for `Retry-After` seconds. | See [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/). ## Revoke on sign-out When the user signs out, end the refresh token too. Two ways do this: - **Sign out through OneiD.** Signing out with the end session endpoint revokes your application's tokens for that user. See [Sign-out](https://oltinid.com/docs/guides/logout/). - **Revoke the token directly.** If your application only clears its own session, revoke the refresh token first: ```http POST /connect/revoke HTTP/1.1 Host: YOUR_ONEID Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU Content-Type: application/x-www-form-urlencoded token=EXAMPLE_OPAQUE_REFRESH_TOKEN_Np5Wc8Ds1Ky3&token_type_hint=refresh_token ``` A public client sends `client_id=YOUR_CLIENT_ID` in the body instead of the `Authorization` header. ## Storage - **Server-side applications:** keep refresh tokens on the server, for example in the session store or an encrypted database column. Never send them to the browser. - **Browser applications:** keep tokens in memory. Avoid `localStorage` for refresh tokens, because any script on the page can read it. See [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/). - **Mobile and desktop applications:** use the platform's secure storage, such as the iOS Keychain or Android Keystore. ## Learn more - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/) --- # Sign-out > Sign users out of your application and OneiD with RP-initiated logout, and design for applications that are not notified. Source: https://oltinid.com/docs/guides/logout/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt Signing a user out involves two sessions: your application's own session and the OneiD session that gives single sign-on. Your application clears its own session, then sends the browser to OneiD's end session endpoint, which ends the OneiD session and can return the user to your application. This follows OpenID Connect RP-Initiated Logout 1.0. ## How sign-out works ```text 1. The user chooses Sign out in your application. 2. Your application clears its own session and removes the tokens it holds. 3. Your application redirects the browser to https://YOUR_ONEID/connect/logout with id_token_hint, post_logout_redirect_uri and state. 4. OneiD ends the OneiD session and revokes your application's tokens for the user. If users sign in at an upstream OpenID Connect provider, OneiD continues to that provider's sign-out. 5. OneiD redirects the browser to your post_logout_redirect_uri with state. ``` ## Before you start Ask your administrator to register your post-logout redirect URIs on the client, for example `https://app.example.com/signed-out`. They must match exactly, like redirect URIs. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). Keep the ID token from sign-in. You send it as `id_token_hint`. ## Send the sign-out request The end session endpoint accepts GET and POST. ```http GET /connect/logout ?id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ID_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE &post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2Fsigned-out &state=r7Kp2Xw9Tq4Lm1Vz HTTP/1.1 Host: YOUR_ONEID ``` As a POST, from a form the browser submits: ```http POST /connect/logout HTTP/1.1 Host: YOUR_ONEID Content-Type: application/x-www-form-urlencoded id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ID_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE&post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2Fsigned-out&state=r7Kp2Xw9Tq4Lm1Vz ``` POST keeps the ID token out of the URL. Use it if your framework supports it. If you no longer have the ID token, send `client_id` instead: ```http GET /connect/logout?client_id=YOUR_CLIENT_ID&post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2Fsigned-out&state=r7Kp2Xw9Tq4Lm1Vz HTTP/1.1 Host: YOUR_ONEID ``` > **Checkpoint:** After sign-out, the browser lands on `https://app.example.com/signed-out?state=r7Kp2Xw9Tq4Lm1Vz`. Open your application and choose Sign in: OneiD asks for the password again. ## Parameters | Parameter | Required | Description | |---|---|---| | `id_token_hint` | Recommended | An ID token OneiD issued to your application for this user. Identifies the user and the client. | | `post_logout_redirect_uri` | No | Where to send the user afterwards. Must be registered for the client, exactly. | | `client_id` | When there is no `id_token_hint` | Your client ID. | | `state` | Recommended | An opaque value OneiD returns to the `post_logout_redirect_uri`. Check it there. | ## Rules - **A `post_logout_redirect_uri` needs `id_token_hint` or `client_id`.** Without either, OneiD refuses the request with `invalid_request`, because it cannot tell which client's registered URIs to check. - **The redirect URI must be registered.** An unregistered `post_logout_redirect_uri` is not followed. - **A forged `id_token_hint` is refused.** OneiD accepts only ID tokens it issued. - **A different user asks for confirmation.** If the hint names another user than the one signed in to OneiD, OneiD asks the user to confirm the sign-out. - **Without `post_logout_redirect_uri`, the user stays on OneiD.** They end on OneiD's signed-out page. - **A disabled client gets `unauthorized_client`.** ## What sign-out ends | What | Ended | |---|---| | Your application's session | Only if your application clears it. OneiD cannot reach into your application. | | The OneiD session | Yes. Signing out of OneiD ends the OneiD session, so the next sign-in asks for the password again. | | Your application's tokens for this user | Yes. Refresh tokens and access tokens issued to your client for this user are revoked in OneiD's store. | | Other applications' tokens | No. | | Other applications' sessions | No. See below. | | The upstream provider's session | When users sign in at an upstream OpenID Connect provider, OneiD continues to that provider's sign-out. | An API that validates access tokens locally, as most do, accepts a revoked access token until it expires. Keep access token lifetimes short; they are 1 hour by default. ## What is not supported > **Not supported:** OneiD does not support front-channel logout, back-channel logout or the session management iframe. When a user signs out of one application, OneiD does not notify the other applications the user signed in to. Other applications keep their own sessions until those end. Design for this: - **Keep application sessions short,** or tie them to the access token's lifetime. - **Check silently on important pages.** Send an authorisation request with `prompt=none` as a top-level redirect, not in a hidden iframe: OneiD pages cannot be framed. If OneiD answers `login_required`, the user has signed out of OneiD; end your session. See [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/). - **Treat a failed refresh as sign-out.** When a refresh returns `invalid_grant`, end your session and remove the tokens. - **Tell users.** On your signed-out page, say that other applications may still be signed in. ## Clear your application's session Do this before you redirect to OneiD, so the user is signed out of your application even if the redirect fails: 1. Delete your session cookie, or end the server session. 2. Remove the access token, ID token and refresh token you hold. Keep a copy of the ID token only long enough to build the sign-out request. 3. Redirect to `/connect/logout`. Make sign-out a POST in your application, from a button in a form, so another site cannot sign your users out with a link. On the `post_logout_redirect_uri` page, check `state`, then show a signed-out page. Do not start a new sign-in automatically. If your application only wants to sign the user out of itself and keep the OneiD session, skip the redirect and revoke the refresh token at `/connect/revoke` instead. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/#revoke-on-sign-out). ## Learn more - [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) - [Endpoints](https://oltinid.com/docs/reference/endpoints/) --- # Sessions, prompt and max_age > Control when OneiD asks users to sign in again, check sessions silently, and require a recent or MFA sign-in for sensitive actions. Source: https://oltinid.com/docs/guides/sessions-and-reauthentication/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt OneiD remembers a signed-in user with a browser session, which gives single sign-on across your applications. The `prompt`, `max_age` and `id_token_hint` parameters let your application decide when that session is good enough and when the user must sign in again. The ID token's `auth_time`, `amr` and `acr` claims tell you how and when the user actually signed in. ## The OneiD session When a user signs in, OneiD keeps a browser session for 8 hours, extended while the user is active. While it lasts, any of your applications that sends the user to `/connect/authorize` gets a sign-in without a password prompt. Your application has its own session, separate from OneiD's. OneiD's session decides whether the user must type a password; your session decides whether your application sends the user to OneiD at all. Signing out of OneiD ends the OneiD session. See [Sign-out](https://oltinid.com/docs/guides/logout/). ## Authorisation request parameters Add these to the authorisation request described in [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/). | Parameter | Effect at OneiD | |---|---| | `prompt=none` | No sign-in page, no consent page. If interaction is needed, OneiD returns an error to your redirect URI. | | `prompt=login` | Forces a new sign-in, even if the user has a OneiD session. | | `prompt=select_account` | Same as `prompt=login`. OneiD has no account picker. | | `prompt=consent` | Shows the consent page, even if consent was given before or the client does not ask for consent. | | `max_age=N` | Forces a new sign-in if the user signed in more than `N` seconds ago, judged by `auth_time`. | | `id_token_hint` | An ID token from an earlier sign-in. If it names a different user than the one signed in, OneiD forces a new sign-in. | > **Not supported:** OneiD accepts `login_hint`, `ui_locales`, `display` and `acr_values` but they have no effect. In particular, sending `acr_values=urn:oltin:ac:mfa` does not force MFA. ## How and when the user signed in The ID token carries three claims about the sign-in: | Claim | Values | Meaning | |---|---|---| | `auth_time` | Seconds since 1970 | When the user last entered credentials, not when this token was issued. | | `amr` | JSON array: `pwd`, `mfa`, `external` | How the user signed in. A password with an authenticator code gives `["pwd","mfa"]`. A sign-in at an upstream OpenID Connect provider gives `["external"]`. | | `acr` | `urn:oltin:ac:pwd`, `urn:oltin:ac:mfa`, `urn:oltin:ac:external` | The same as `amr`, as one value. | These claims are in the ID token only, not in the access token or userinfo. ## Require a recent sign-in For actions such as changing payment details, ask for a fresh sign-in and then check that you got one. ```http GET /connect/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid&state=Wd2kP8sLq5Xn0Rt3&nonce=Hy6cV1mZb9Jf4Ks7&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&max_age=300 HTTP/1.1 Host: YOUR_ONEID ``` After the code exchange, check the ID token: `auth_time` must be no more than 300 seconds ago, allowing a small clock skew. Use `prompt=login` instead of `max_age` to force a sign-in regardless of age. Always check `auth_time` yourself. The parameters ask OneiD to re-authenticate; the claim proves it happened. ## Require MFA for an action OneiD does not enforce `acr_values`. To require MFA: 1. **Check the claims.** After sign-in, accept the action only if `amr` contains `mfa` (equivalently, `acr` is `urn:oltin:ac:mfa`). 2. **If it does not, ask the user to sign in again** with `prompt=login`, then check the new ID token. Whether the new sign-in includes an authenticator code depends on the user's MFA setup in OneiD. 3. **If MFA must always apply, ask your administrator** to make MFA mandatory for the users concerned. This is the only way to make OneiD itself require it. ```js function hasMfa(idTokenClaims) { return Array.isArray(idTokenClaims.amr) && idTokenClaims.amr.includes('mfa'); } ``` > **Note:** When users sign in at an upstream OpenID Connect provider, `amr` is `["external"]` and `acr` is `urn:oltin:ac:external`. That provider's own MFA applies, and OneiD does not add its own code on top. The ID token does not tell you whether the provider used MFA. ## Check the session silently with prompt=none `prompt=none` asks OneiD to complete the sign-in only if it can do so without showing anything. Use it to find out whether the user still has a OneiD session, for example on an important page of an application whose session outlives OneiD's. ```http GET /connect/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid&state=Fn3qR7tYw2Lp9Vx1&nonce=Pz8mK4cX6bJs0Dh5&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&prompt=none HTTP/1.1 Host: YOUR_ONEID ``` If the user has a session and nothing else is needed, the callback carries a code as usual. Otherwise it carries an error: ```http GET /callback?error=login_required&state=Fn3qR7tYw2Lp9Vx1 HTTP/1.1 Host: app.example.com ``` | Error | Meaning | What to do | |---|---|---| | `login_required` | The user has no OneiD session, or must sign in again. | End your session, or start a normal sign-in. | | `consent_required` | The user has not given consent for these scopes. | Start a normal sign-in so the user sees the consent page. | | `interaction_required` | The user must do something first, such as change their password or set up MFA. | Start a normal sign-in. | Run the check as a top-level redirect. OneiD pages cannot be framed, so a check in a hidden iframe does not work. For browser applications that need fresh tokens in the background, use refresh tokens instead. See [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/). ## Pending password change or MFA enrolment An administrator can require a user to change their password or to set up MFA. Until the user does so: - On a normal sign-in, OneiD sends the user to change the password or enrol an authenticator before the sign-in to your application can complete. - Under `prompt=none`, your application receives `interaction_required`. - A refresh token request returns `invalid_grant`. Your application does not need special handling beyond these errors. The user completes the step at OneiD. ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Sign-out](https://oltinid.com/docs/guides/logout/) - [Claims](https://oltinid.com/docs/reference/claims/) - [OneiD accounts and MFA](https://oltinid.com/docs/sign-in-sources/oneid-accounts/) --- # Browser applications and CORS > Connect a single-page application to OneiD as a public client, store tokens safely, renew them in the background and fix CORS errors. Source: https://oltinid.com/docs/guides/browser-applications/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt A single-page application (SPA) runs entirely in the user's browser and talks to OneiD directly. It is a public client: it has no secret, uses the authorization code flow with PKCE, and calls the token endpoint from the browser, which needs CORS. This guide covers the setup, token storage, background renewal and the CORS errors you may meet. ## Architecture ```text Browser (your SPA) OneiD Your API ------------------ ----- -------- 1. Redirect to /connect/authorize -> 2. User signs in 3. <- Redirect to callback with code 4. POST /connect/token (CORS) ----> 5. <- ID token, access token, refresh token 6. GET /orders, Authorization: Bearer ... ---------------------> 7. Validate token ``` Steps 1 and 3 are browser navigations, so CORS does not apply. Step 4 is a cross-origin `fetch`, so OneiD must allow your application's origin. ## What to ask your administrator for - Client type `public`. PKCE is always required. - Grant types: authorization code, and refresh token if you want background renewal. - Redirect URIs, for example `https://app.example.com/callback` and `http://localhost:4200/callback`. - Post-logout redirect URIs, for example `https://app.example.com/signed-out`. - Allowed scopes, for example `openid profile email offline_access orders.read`. - **Allowed CORS origins:** every origin your SPA is served from, written `scheme://host[:port]` with no path, for example `https://app.example.com` and `http://localhost:4200`. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/) for the full template. ## Which endpoints allow cross-origin requests | Endpoint | Cross-origin requests | |---|---| | Discovery, `/.well-known/openid-configuration` | Any origin | | JWKS, `/.well-known/jwks` | Any origin | | Token, `/connect/token` | Registered origins on an enabled client only | | Userinfo, `/connect/userinfo` | Registered origins on an enabled client only | | Revocation, `/connect/revoke` | Registered origins on an enabled client only | | Introspection, `/connect/introspect` | Never | | Authorize and end session | Not applicable: these are navigations, not `fetch` calls | CORS requests never carry cookies. The SPA authenticates to these endpoints with tokens and its client ID only. ## Configure a library Use a maintained OpenID Connect library rather than writing the flow yourself. Common choices: - **Plain JavaScript or TypeScript:** `oidc-client-ts` - **React:** `react-oidc-context`, which wraps `oidc-client-ts` - **Angular:** `oidc-client-ts` in an Angular service These libraries work with any standards-based provider. The examples below are a starting point, not an endorsement of a particular version. ```ts import { UserManager, WebStorageStateStore, InMemoryWebStorage } from 'oidc-client-ts'; export const userManager = new UserManager({ authority: 'https://YOUR_ONEID', client_id: 'YOUR_CLIENT_ID', redirect_uri: 'https://app.example.com/callback', post_logout_redirect_uri: 'https://app.example.com/signed-out', response_type: 'code', scope: 'openid profile email offline_access orders.read', // Keep tokens in memory, not in localStorage. userStore: new WebStorageStateStore({ store: new InMemoryWebStorage() }), // Renew before expiry with the refresh token (request offline_access). // Without a refresh token the library falls back to a hidden iframe, // which OneiD does not allow. automaticSilentRenew: true, }); // Sign in: await userManager.signinRedirect(); // Callback: await userManager.signinRedirectCallback(); // Sign out: await userManager.signoutRedirect(); ``` The library reads the endpoints from discovery, creates the PKCE values, `state` and `nonce`, and validates the ID token. It compares the ID token's `iss` with the issuer from discovery, `https://YOUR_ONEID/`, including the trailing slash. > **Checkpoint:** After sign-in, the browser's network panel shows a `POST` to `https://YOUR_ONEID/connect/token` with status 200, and your application shows the signed-in user's name. For a step-by-step setup, follow the [JavaScript](https://oltinid.com/docs/quickstarts/javascript/), [React](https://oltinid.com/docs/quickstarts/react/) or [Angular](https://oltinid.com/docs/quickstarts/angular/) quickstart. ## Store tokens safely Anything a script on your page can read, an injected script can read too. - **Keep tokens in memory.** A JavaScript variable or the library's in-memory store is the safest place in the browser. - **Avoid `localStorage` for refresh tokens.** A refresh token there survives the session and can be read by any script that runs on your origin. `sessionStorage` has the same exposure while the tab is open. - **Accept the trade-off.** With in-memory storage, reloading the page loses the tokens. The library then sends the user to OneiD again; while the OneiD session lasts, that completes without a password prompt. - **Consider a backend-for-frontend.** For applications that handle sensitive data, run a small server-side component as a confidential client. It holds the tokens, calls the API, and gives the browser only an `HttpOnly`, `Secure`, `SameSite` session cookie. The browser then never sees a token. Use a [server-side quickstart](https://oltinid.com/docs/quickstarts/) for that component. - **Set a Content Security Policy** on your SPA that allows `connect-src` to `https://YOUR_ONEID` and your API, and restricts scripts to your own origin. ## Renew tokens in the background Access tokens last 1 hour by default. To keep the user signed in without a redirect, request `offline_access` and let the library renew with the refresh token before the access token expires. OneiD rotates refresh tokens on every use, and using one twice after a grace period of about 30 seconds revokes the whole chain. In a browser this matters when several tabs share one refresh token: - With in-memory storage, each tab signs in on its own and holds its own chain, so tabs do not collide. - If you share tokens between tabs, let only one tab refresh at a time and pass the new tokens to the others. When a refresh returns `invalid_grant`, the session is over: clear the tokens and start a new sign-in. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/). Do not use hidden iframes with `prompt=none` for renewal. OneiD pages cannot be framed, so iframe-based silent renewal does not work. Use a refresh token, or send the whole page to OneiD with `prompt=none` as a top-level redirect. See [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/). ## Troubleshooting CORS A CORS failure appears in the browser console, not as an OAuth error. It looks similar to this: ```text Access to fetch at 'https://YOUR_ONEID/connect/token' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ``` Check, in order: 1. **The origin is registered exactly.** Compare the origin in the error with the client's allowed CORS origins: scheme, host and port must match. `http://localhost:4200` and `http://127.0.0.1:4200` are different origins. Do not include a path or a trailing slash. 2. **The client is enabled.** OneiD answers cross-origin requests only for origins on an enabled client. 3. **Wait briefly after a change.** A newly registered origin takes effect within about 15 seconds. 4. **You are not calling introspection.** Introspection never allows cross-origin requests. A browser application does not need it; your API validates tokens. 5. **The request reached OneiD at all.** If the network panel shows no response, check that the address is `https://YOUR_ONEID` and that a proxy or content blocker is not interfering. If the token request succeeds but your API call fails with a CORS error, the problem is in your API's CORS settings, not OneiD's. ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) - [Security best practices](https://oltinid.com/docs/guides/security-best-practices/) - [Quickstarts](https://oltinid.com/docs/quickstarts/) --- # Mobile and desktop applications > Sign users in to a mobile or desktop app with OneiD using the system browser, PKCE and a private-use or loopback redirect URI. Source: https://oltinid.com/docs/guides/native-applications/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt Mobile and desktop applications sign users in with the authorization code flow with PKCE, following the practices in RFC 8252, OAuth 2.0 for Native Apps. The app opens the system browser for sign-in and receives the result through a redirect URI that leads back to the app. The app is a public client: it has no secret. ## The rules in short - **Public client.** An installed app cannot keep a secret. Do not embed one. - **PKCE with `S256`.** Always required for public clients. - **The system browser.** Open the sign-in in the platform browser or its in-app browser tab, never in an embedded web view. - **A redirect URI the app can receive:** a private-use URI scheme, a loopback address, or a claimed https URL. - **Exact match.** The redirect URI the app sends must equal a registered one, including the port for loopback. ## Why the system browser An embedded web view lets the app read what the user types, including the password, and does not share the OneiD session with the browser. The system browser, or an in-app browser tab such as `ASWebAuthenticationSession` on iOS or Custom Tabs on Android: - keeps the password out of your app - shows the user the real OneiD address - shares the OneiD session, so single sign-on works across apps and the web ## Choose a redirect URI ### Private-use URI scheme (mobile and desktop) Use a scheme based on a domain you control, in reverse order, followed by `:/` and a path: ```text com.example.app:/callback ``` OneiD accepts private-use schemes only for public clients and only in reverse-domain form. Register the scheme with the operating system so that it opens your app. ### Loopback (desktop) A desktop app can start a local HTTP listener and use a loopback redirect: ```text http://127.0.0.1:52817/callback ``` OneiD allows `http` only for loopback addresses: `127.0.0.1`, `[::1]` and `localhost`. Prefer `127.0.0.1` or `[::1]` over `localhost`, as RFC 8252 recommends. > **Warning:** OneiD matches redirect URIs exactly, including the port. RFC 8252 suggests that servers accept any port on a loopback redirect, but OneiD does not. Choose a fixed port for your app, register exactly that URI, and make sure the listener binds to that port. If the port can be taken, register a small number of alternative ports and try them in order. ### Claimed https URL (mobile) On iOS (Universal Links) and Android (App Links), an app can claim an https URL on a domain you control, for example `https://app.example.com/callback`. It is an ordinary https redirect URI to OneiD, and it gives the strongest guarantee that only your app receives the code. ## What to ask your administrator for - Client type `public`. - Grant types: authorization code, and refresh token if the app should stay signed in. - Redirect URIs, exactly as the app sends them, for example `com.example.app:/callback` or `http://127.0.0.1:52817/callback`. - Post-logout redirect URIs, if the app signs users out through OneiD. - Allowed scopes, for example `openid profile offline_access orders.read`. Native apps do not call OneiD from a web page, so they do not need allowed CORS origins. ## The flow ```text 1. The app creates code_verifier, state and nonce, and derives code_challenge. 2. The app opens the system browser at https://YOUR_ONEID/connect/authorize. 3. The user signs in at OneiD. 4. OneiD redirects to com.example.app:/callback?code=...&state=...&iss=... The operating system hands this URL to the app. 5. The app checks state and iss, closes the browser tab, and POSTs code + code_verifier + client_id to /connect/token. 6. The app validates the ID token and stores the tokens securely. ``` The authorisation request: ```http GET /connect/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=com.example.app%3A%2Fcallback&scope=openid%20profile%20offline_access&state=Kc7tN2wQ9xLr4Bz1&nonce=Vm3pS8hJd6Gy0Fq5&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256 HTTP/1.1 Host: YOUR_ONEID ``` The token request, sent by the app directly: ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=EXAMPLE_AUTHORIZATION_CODE &redirect_uri=com.example.app%3A%2Fcallback &code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk &client_id=YOUR_CLIENT_ID ``` The response and the ID token checks are the same as for any client. See [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/). > **Checkpoint:** After sign-in, the browser tab closes or hands control back, your app receives the callback URL with `code`, `state` and `iss`, and the token request returns HTTP 200 with an `id_token`. ## Store tokens - Use the platform's secure storage: the Keychain on iOS and macOS, the Android Keystore, the Windows Credential Manager or Data Protection API, or the Secret Service on Linux. - Store the newest refresh token every time you refresh. OneiD rotates refresh tokens, and reusing an old one after about 30 seconds revokes the whole chain. If several parts of your app may refresh at once, let one of them do it. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/). ## Libraries Use a maintained library that implements RFC 8252 rather than writing the flow yourself: - **iOS and macOS:** AppAuth for iOS - **Android:** AppAuth for Android - **Desktop:** a general OpenID Connect client library for your language that supports PKCE and a loopback or private-use redirect These libraries work with any standards-based provider. Configure them with the OneiD address as the issuer, so they read discovery from `https://YOUR_ONEID/.well-known/openid-configuration`. ## Sign-out To sign the user out of OneiD as well as your app, open the end session endpoint in the same browser the app used for sign-in, with `id_token_hint` and a registered `post_logout_redirect_uri`. Clear the tokens from secure storage first. See [Sign-out](https://oltinid.com/docs/guides/logout/). ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) - [Register an application](https://oltinid.com/docs/get-started/register-an-application/) --- # Protect an API (validate access tokens) > Validate OneiD access tokens in your API by checking the signature, issuer, expiry and scope, and answer with the right 401 or 403. Source: https://oltinid.com/docs/guides/protect-an-api/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt An API protected by OneiD accepts a bearer access token on each request and checks it locally. OneiD access tokens are signed JWTs, so your API needs no call to OneiD per request: it verifies the signature with OneiD's published keys and then checks the claims. ## What an access token looks like OneiD access tokens are JWTs signed with RS256. They are not encrypted. The header carries a `kid` that names the signing key in OneiD's JWKS. | Claim | Meaning | |---|---| | `iss` | The OneiD issuer, `https://YOUR_ONEID/`, with the trailing slash. | | `sub` | The user's ID. For a client credentials token, the client ID. | | `exp`, `iat` | Expiry and issue time, in seconds since 1970. | | `scope` | The granted scopes, separated by spaces. | | `client_id` | The client the token was issued to. | | `role` | The user's roles, when the `roles` scope was granted. | Scope-released profile, email and phone claims can also be present. Ignore claims you do not know. > **Warning:** OneiD access tokens have no `aud` claim. Do not require an audience. A library that checks the audience by default rejects every OneiD token; turn that check off and check the scope instead. ## Validate a token step by step Your API does the following for every request. Use a maintained JWT library; it does most of these steps for you. 1. Read the token from the `Authorization: Bearer ` header. Reject the request if the header is missing or uses another scheme. 2. Check that the header `alg` is `RS256`. Reject `none` and every other algorithm. 3. Find the key whose `kid` matches the token's `kid` in OneiD's JWKS and verify the signature. 4. Check that `iss` equals the `issuer` from the discovery document exactly, including the trailing slash: `https://YOUR_ONEID/`. 5. Check `exp`, and `nbf` if present, against the current time. Allow a small clock skew, such as a minute at most. 6. Check that the space-separated `scope` claim contains the scope this endpoint requires. 7. If the endpoint needs a role, check the `role` claim. ### Get the keys from discovery Read the discovery document once at start-up and take `jwks_uri` and `issuer` from it. Do not hard-code them. ```http GET /.well-known/openid-configuration HTTP/1.1 Host: YOUR_ONEID ``` The discovery document gives `jwks_uri` as `https://YOUR_ONEID/.well-known/jwks`. ### Cache the JWKS and refresh on an unknown key Cache the JWKS. When a token arrives with a `kid` that is not in your cache, fetch the JWKS again once and retry. Limit how often you re-fetch, so that tokens with made-up `kid` values cannot make your API call OneiD on every request. OneiD publishes a new signing key in the JWKS before it starts signing with it, and keeps a retired key in the JWKS for 30 days. An API that caches the JWKS and re-fetches on an unknown `kid` keeps working through key rotation without changes. ### Example in Node.js This example uses the `jose` library. `createRemoteJWKSet` caches the keys and re-fetches them when it sees an unknown `kid`. ```js import { createRemoteJWKSet, jwtVerify } from 'jose'; const discovery = await fetch('https://YOUR_ONEID/.well-known/openid-configuration') .then((r) => r.json()); const jwks = createRemoteJWKSet(new URL(discovery.jwks_uri)); export async function verifyAccessToken(token, requiredScope) { const { payload } = await jwtVerify(token, jwks, { issuer: discovery.issuer, // "https://YOUR_ONEID/" with the slash algorithms: ['RS256'], clockTolerance: 60, // seconds // no audience option: OneiD access tokens have no aud claim }); const scopes = typeof payload.scope === 'string' ? payload.scope.split(' ') : []; if (!scopes.includes(requiredScope)) { const err = new Error('insufficient_scope'); err.status = 403; throw err; } return payload; } ``` The [API quickstarts](https://oltinid.com/docs/quickstarts/) show the same checks for each language: [Node.js](https://oltinid.com/docs/quickstarts/api-node/), [.NET](https://oltinid.com/docs/quickstarts/api-dotnet/), [Go](https://oltinid.com/docs/quickstarts/api-go/), [Python](https://oltinid.com/docs/quickstarts/api-python/) and [Java](https://oltinid.com/docs/quickstarts/api-java/). ## Use one scope per API Because access tokens have no audience, the scope is what ties a token to an API. A token that carries `orders.read` is accepted by every API that accepts `orders.read`. - Ask your OneiD administrator to create distinct API scopes for each API, for example `orders.read` and `orders.write` for the orders API and `invoices.read` for the invoices API. - Require one of your own API's scopes on every endpoint. Never accept a token only because its signature and issuer are valid. - Do not reuse another API's scope names, and do not accept identity scopes such as `openid` or `profile` as proof of access to your API. Then a token issued for the invoices API cannot be used at the orders API, because it does not carry an orders scope. > **Note:** API scopes exist only after an administrator creates them. They appear in the access token's `scope` claim and add no other claims. See [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/). ## Answer with 401 or 403 Follow RFC 6750 so that clients can tell what went wrong. | Situation | Status | `WWW-Authenticate` header | |---|---|---| | No token | 401 | `Bearer` | | Token malformed, expired, wrong issuer or bad signature | 401 | `Bearer error="invalid_token"` | | Valid token without the required scope | 403 | `Bearer error="insufficient_scope", scope="orders.read"` | | Valid token and scope, but the user lacks the role your endpoint needs | 403 | Not required | ```http HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer error="invalid_token", error_description="The access token expired" ``` ```http HTTP/1.1 403 Forbidden WWW-Authenticate: Bearer error="insufficient_scope", scope="orders.write" ``` Keep `error_description` short and do not echo the token. ## Use roles for authorisation When the client requests the `roles` scope and the user has roles, the access token carries a `role` claim. With one role it can be a single string; with several roles it is an array. Accept both forms. ```json { "iss": "https://YOUR_ONEID/", "sub": "8d0c6a3e-2f4b-4c1e-9a77-1b2c3d4e5f60", "scope": "openid roles orders.read", "role": ["OrderViewer", "Support"] } ``` Roles are assigned to users in OneiD or mapped from directory or provider groups. See [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/). Client credentials tokens represent a service, not a user. Their `sub` is the client ID. If an endpoint must only serve users, or only serve one service, check `sub` or `client_id` as well. ## Revocation and introspection OneiD can revoke tokens: when a user signs out of an application, when the user's password changes, when an administrator removes a role or locks the user, and in other cases listed in [Tokens](https://oltinid.com/docs/reference/tokens/). An API that validates JWTs locally does not see a revocation until the token expires. You have two options: - **Short-lived access tokens with local validation.** This is the recommended pattern for most APIs. Access tokens live 1 hour by default, and an administrator can shorten the lifetime for a client. - **Introspection.** An API that must see revocation immediately can call `POST /connect/introspect`. Introspection requires a confidential client and only answers for tokens issued to that same client, so it does not fit an API that serves tokens issued to other clients. ## Learn more - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) - [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/) --- # Scopes, claims and roles > Request the right scopes, read the claims OneiD releases for them, and use roles from OneiD for authorisation in your application. Source: https://oltinid.com/docs/guides/scopes-claims-roles/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt Scopes decide what an application may ask for. Claims are the facts about the user that OneiD puts into tokens and the userinfo response. Roles are a kind of claim your application can use to decide what a user may do. ## Request scopes Send the scopes in the `scope` parameter of the authorisation request, separated by spaces. Include `openid` for every OpenID Connect sign-in. ```http GET /connect/authorize?client_id=YOUR_CLIENT_ID &response_type=code &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback &scope=openid%20profile%20email%20roles%20orders.read &code_challenge=...&code_challenge_method=S256 &state=...&nonce=... HTTP/1.1 Host: YOUR_ONEID ``` A client may only request scopes that an administrator allowed for it. Any other scope makes the request fail with `invalid_scope`. Ask your OneiD administrator which scopes your client is allowed. There are two kinds of scope: - **Identity scopes** release claims about the user to your application. - **API scopes** grant access to an API. They appear only in the access token's `scope` claim and add no claims. ## Identity scopes | Scope | What it releases | |---|---| | `openid` | Required for OpenID Connect. Gives you an ID token with `sub`. | | `profile` | `name`, `given_name`, `family_name`, `preferred_username`, `updated_at` (a number) and `idp` | | `email` | `email` and `email_verified` (a boolean) | | `phone` | `phone_number` and `phone_number_verified` (a boolean) | | `roles` | `role`, one value per role | | `offline_access` | No claims. Asks for a refresh token; the client must also have the refresh token grant. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/). | > **Not supported:** OneiD has no `address` scope. ### Where the claims go | Claim | ID token | Access token | Userinfo | |---|---|---|---| | `sub` | Always | Always | Always | | `auth_time`, `amr`, `acr` | Yes | No | No | | Profile, email and phone claims | When the scope is granted | When the scope is granted | When the scope is granted | | `role` | When `roles` is granted | When `roles` is granted | When `roles` is granted, as a JSON array | In tokens, a user with one role can have `role` as a single string and a user with several roles as an array. Accept both forms. The `idp` claim tells you where the user signed in: `local` (a OneiD account), `ldap` (a directory) or `oidc` (an upstream OpenID Connect provider). See [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/). ID tokens may also contain private claims whose names start with `oi_`. Ignore claims you do not know. The full list is in [Claims](https://oltinid.com/docs/reference/claims/). ## API scopes An administrator creates API scopes in the admin console, for example `orders.read` and `orders.write`, and allows them for the clients that call the API. OneiD has no API scopes until an administrator creates them. Give each API its own scopes. OneiD access tokens have no `aud` claim, so the scope is what tells an API that a token is meant for it. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/#use-one-scope-per-api). > **Warning:** The scopes `admin_api`, `admin_api_readonly` and `admin_console_webhooks` are for OneiD's own administration. Do not request them in your applications. ## Consent and granted scopes Each client either asks users for consent or does not: - **Consent asked.** OneiD shows a consent page that lists the scopes. The user can untick optional scopes. If the user ticks "remember", OneiD does not ask again for the same set of scopes. - **Consent not asked.** Typical for your organisation's own applications. OneiD still shows the consent page when the request contains `prompt=consent`. If the user refuses consent, your application receives `access_denied` at the redirect URI. Because the user can untick scopes, you may receive fewer scopes than you asked for. Read the `scope` field of the token response to see what was granted, and let your application work with less. For example, if `email` was not granted, ask the user for an email address instead of failing. ## Roles A role is a name such as `OrderViewer` that OneiD puts in the `role` claim. Roles come from one of two places, depending on the sign-in source of your OneiD deployment: - **Assigned in OneiD.** An administrator creates roles and assigns them to users in the admin console. - **Mapped from groups.** With LDAP or Active Directory, or an upstream OpenID Connect provider, OneiD turns the user's groups into roles through a mapping your administrator configures. See [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/#from-groups-to-roles). To receive roles, the client must be allowed the `roles` scope and request it. ### Use roles for authorisation - In a web application, read `role` from the ID token after sign-in and store the roles in your application session. - In an API, read `role` from the access token. - Map OneiD role names to your own permissions in one place in your code. Do not scatter role-name checks across the code base. - A token keeps the roles it was issued with. When an administrator removes a role, OneiD revokes the user's tokens in its store, so refresh tokens stop working. An API that validates access tokens locally still accepts an access token that was already issued until it expires. OneiD has built-in administrator roles such as `All` and `UserManager`. They control OneiD's admin console. Do not reuse them as roles in your own application; ask for application roles instead. ## The claims parameter OneiD supports the OpenID Connect `claims` request parameter for the `id_token` and `userinfo` members. OneiD reads the claim names. It ignores `essential`, `value` and `values`. ```text claims={"id_token":{"email":null,"preferred_username":null}} ``` URL-encode the JSON when you send it. OneiD only releases claims that belong to scopes the client is allowed and the user consented to; the `claims` parameter cannot release more. Since scope-released claims already appear in the ID token, most applications do not need this parameter. ## Identify users by issuer and subject Use `iss` and `sub` together as the key for a user in your database. - `sub` is stable and opaque. Do not parse it. Its format depends on the sign-in source: an opaque OneiD user ID for OneiD accounts, the directory account name in lower case, without the domain, for LDAP, and the provider's own `sub` for an upstream OpenID Connect provider. - Do not use `email` as a key. An email address can change and can be reused. - Before you treat an email address as belonging to the user, check that `email_verified` is `true`. - `iss` ends with a slash: `https://YOUR_ONEID/`. Store it as it appears in the token. ## Learn more - [Claims](https://oltinid.com/docs/reference/claims/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Protect an API (validate access tokens)](https://oltinid.com/docs/guides/protect-an-api/) - [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/) --- # Security best practices > Check your OneiD integration against a list of practices for flows, secrets, tokens, sessions and APIs before you go live. Source: https://oltinid.com/docs/guides/security-best-practices/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt Use this page as a checklist before an application or API goes live with OneiD. Each item says what to do and why. Most OpenID Connect libraries do the protocol checks for you when you configure them correctly. ## Sign-in requests - [ ] **Use the authorization code flow with PKCE and `S256` in every application**, including server-side applications. Public clients must use PKCE; confidential clients have it required by default. Keep it on. - [ ] **Send `state` and check it on the callback.** It binds the response to the browser that started the sign-in and stops cross-site request forgery. Use a fresh, unguessable value for each request. - [ ] **Send `nonce` and check it in the ID token.** It stops a stolen ID token from being replayed into your application. - [ ] **Check `iss` in the authorisation response.** OneiD includes `iss` in every authorisation response (RFC 9207). It must equal the issuer from discovery. - [ ] **Register exact redirect URIs.** OneiD compares redirect URIs exactly and allows no wildcards. Register one URI per environment instead of a pattern. - [ ] **Use https everywhere.** OneiD accepts http redirect URIs only for loopback addresses (`localhost`, `127.0.0.1`, `[::1]`), for local development and desktop applications. - [ ] **Do not reload the callback page.** An authorisation code works once. A second attempt fails with `invalid_grant`. ## Client secrets - [ ] **Never put a client secret in a browser, mobile or desktop application.** Anything shipped to a user's device can be read. These applications are public clients and use PKCE without a secret. - [ ] **Keep the secret in a secret store**, such as your platform's secret manager or environment variables injected at run time. Do not commit it to source control or put it in a configuration file in a repository. - [ ] **Plan secret rotation.** A client has one secret at a time. When an administrator replaces it, the old secret stops working immediately. Agree a moment with your OneiD administrator, update your application at the same time, and check that sign-in works afterwards. - [ ] **Know when the secret expires.** An expired secret gives `invalid_client` with "The client secret has expired." Ask your administrator for the expiry date and put the replacement in your calendar. - [ ] **Send the secret with `client_secret_basic`** (HTTP Basic authentication). `client_secret_post` also works. ## Tokens in your application - [ ] **Validate ID tokens** before you trust them: signature (RS256), `iss` with the trailing slash, `aud` equals your client ID, `exp`, and `nonce`. See [Tokens](https://oltinid.com/docs/reference/tokens/). - [ ] **Store tokens where only your application can read them.** - Server-side web applications: in the server-side session, not in a cookie the browser can read. - Browser applications: in memory. Avoid `localStorage`, which any script on the page can read. - Mobile and desktop applications: in the operating system's secure storage. - [ ] **Handle refresh-token rotation.** Every refresh returns a new refresh token. Store the new one and discard the old one. Do not run two refreshes with the same token at the same time: a refresh token used again after a short grace period is refused, and OneiD then revokes the whole chain, including newer tokens. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/). - [ ] **Treat `invalid_grant` on refresh as "sign in again".** It also happens when the user was locked, deleted or must change the password. - [ ] **Never log tokens, codes or secrets.** Do not put them in URLs you build yourself, error messages or analytics. Log the `sub` and the token's expiry instead if you need a trace. - [ ] **Request the fewest scopes you need.** Ask for `offline_access` only when the application really needs to work without the user present. ## Sessions and sensitive actions - [ ] **Keep application sessions short.** OneiD does not notify other applications when a user signs out of one application. Each application keeps its own session until it ends. Short sessions, `prompt=none` checks and failed refreshes are how you notice. See [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/). - [ ] **Sign out properly.** Clear your session, then send the user to OneiD's end-session endpoint with `id_token_hint`. See [Sign-out](https://oltinid.com/docs/guides/logout/). - [ ] **Check `amr` or `acr` before sensitive actions.** OneiD does not enforce `acr_values`; asking for `urn:oltin:ac:mfa` does not force MFA. If an action needs MFA, check that `acr` is `urn:oltin:ac:mfa` or `urn:oltin:ac:external` as your policy allows, or that `amr` contains `mfa`. If MFA must always happen, ask your administrator to make MFA mandatory for the users. - [ ] **Ask for a fresh sign-in when it matters.** Use `max_age` or `prompt=login` and then check `auth_time` in the new ID token. ## APIs - [ ] **Validate every access token**: RS256 signature, `iss` with the trailing slash, `exp`, and the required scope. Do not require `aud`; OneiD access tokens have none. - [ ] **Use distinct scopes per API**, so a token for one API cannot be used at another. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/#use-one-scope-per-api). - [ ] **Cache the JWKS** and re-fetch it when a token has an unknown `kid`. Do not fetch it on every request. - [ ] **Allow a small clock skew**, no more than a minute or so, when you check `exp` and `nbf`. ## Behaving well - [ ] **Handle HTTP 429.** When OneiD answers 429, wait for the number of seconds in the `Retry-After` header before you try again. Do not retry in a tight loop. See [Rate limits](https://oltinid.com/docs/reference/rate-limits/). - [ ] **Cache client credentials tokens** until shortly before they expire, instead of requesting a new token per call. - [ ] **Read the discovery document** instead of hard-coding endpoint addresses. ## Reporting a security problem If you find a security problem in OneiD, report it privately through the [contact page](https://oltinid.com/contact/?topic=security) (topic: Security report). The contact details are also published at `https://oltinid.com/.well-known/security.txt`. Please do not share details publicly before we have had a chance to respond. ## Learn more - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) - [Protect an API (validate access tokens)](https://oltinid.com/docs/guides/protect-an-api/) - [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/) --- # Endpoints > Every OneiD protocol endpoint with its method, path, authentication, parameters, example request and response, and errors. Source: https://oltinid.com/docs/reference/endpoints/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt OneiD exposes the standard OAuth 2.0 and OpenID Connect endpoints under your OneiD address. Paths on this page are relative to `https://YOUR_ONEID`. Read the exact URLs from the [discovery document](https://oltinid.com/docs/reference/discovery/) rather than building them by hand. | Endpoint | Method | Path | |---|---|---| | [Discovery](#discovery) | GET | `/.well-known/openid-configuration` | | [JWKS](#jwks) | GET | `/.well-known/jwks` | | [Authorize](#authorize) | GET, POST | `/connect/authorize` | | [Token](#token) | POST | `/connect/token` | | [Userinfo](#userinfo) | GET, POST | `/connect/userinfo` | | [Introspection](#introspection) | POST | `/connect/introspect` | | [Revocation](#revocation) | POST | `/connect/revoke` | | [End session](#end-session) | GET, POST | `/connect/logout` | > **Not supported:** OneiD has no device authorization, pushed authorization request (PAR), dynamic client registration, session management (`check_session_iframe`), front-channel logout or back-channel logout endpoint. ## Discovery `GET /.well-known/openid-configuration` Returns the OpenID Connect discovery document: the issuer, every endpoint URL and the features OneiD supports. Your library reads it at startup. The [discovery document reference](https://oltinid.com/docs/reference/discovery/) explains each field. **Authentication:** none. Any origin may call it from a browser. **Parameters:** none. ```http GET /.well-known/openid-configuration HTTP/1.1 Host: YOUR_ONEID ``` ```json { "issuer": "https://YOUR_ONEID/", "authorization_endpoint": "https://YOUR_ONEID/connect/authorize", "token_endpoint": "https://YOUR_ONEID/connect/token", "introspection_endpoint": "https://YOUR_ONEID/connect/introspect", "end_session_endpoint": "https://YOUR_ONEID/connect/logout", "revocation_endpoint": "https://YOUR_ONEID/connect/revoke", "userinfo_endpoint": "https://YOUR_ONEID/connect/userinfo", "jwks_uri": "https://YOUR_ONEID/.well-known/jwks", "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"], "response_types_supported": ["code"], "id_token_signing_alg_values_supported": ["RS256"] } ``` The example is shortened. The full document has more fields. > **Note:** The `issuer` ends with a slash: `https://YOUR_ONEID/`. If your library compares issuers exactly, use the value from discovery, including the slash. **Errors:** none specific to this endpoint. ## JWKS `GET /.well-known/jwks` Returns the public keys that verify the signatures of ID tokens and access tokens. Each key has a `kid`. A token's header names the `kid` of the key that signed it. **Authentication:** none. Any origin may call it from a browser. **Parameters:** none. ```http GET /.well-known/jwks HTTP/1.1 Host: YOUR_ONEID ``` ```json { "keys": [ { "kid": "EXAMPLE_KID", "use": "sig", "kty": "RSA", "alg": "RS256", "e": "AQAB", "n": "EXAMPLE_PUBLIC_KEY_MODULUS..." } ] } ``` During a key rotation the set contains more than one key. Cache the set and fetch it again when a token arrives with a `kid` you do not know. See [signing keys and rotation](https://oltinid.com/docs/reference/tokens/#signing-keys-and-rotation). **Errors:** none specific to this endpoint. ## Authorize `GET /connect/authorize` or `POST /connect/authorize` Starts a sign-in. The browser is sent here; OneiD signs the user in (or reuses the OneiD session), asks for consent when the client requires it, and redirects back to your `redirect_uri` with an authorization code. With `POST`, send the same parameters as an `application/x-www-form-urlencoded` body. **Authentication:** none for the client. The user authenticates in the browser. | Parameter | Required | Description | |---|---|---| | `client_id` | Yes | Your client ID. | | `response_type` | Yes | Always `code`. | | `redirect_uri` | Yes | Must match a redirect URI registered for the client exactly. | | `scope` | Yes | Space-separated scopes. Include `openid` for OpenID Connect. The client must be allowed every scope it asks for. | | `code_challenge` | Yes | The PKCE code challenge: the base64url-encoded SHA-256 hash of your code verifier. | | `code_challenge_method` | Yes | Always `S256`. | | `state` | Recommended | An unguessable value that OneiD returns unchanged. Check it on the callback. | | `nonce` | Recommended | An unguessable value that OneiD copies into the ID token. Check it when you validate the ID token. | | `response_mode` | No | `query` (default) or `form_post`. | | `prompt` | No | `none`, `login`, `select_account` or `consent`. See [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/). | | `max_age` | No | Maximum time in seconds since the user last signed in. If more time has passed, the user must sign in again. | | `id_token_hint` | No | An ID token OneiD issued earlier. If it names a different user from the one signed in, OneiD asks the user to sign in. | | `claims` | No | A JSON object that names claims for the `id_token` and `userinfo` members. See [the claims parameter](https://oltinid.com/docs/reference/claims/#the-claims-request-parameter). | `prompt` values: - `none`: OneiD shows no page. If the user would have to sign in, consent or complete a pending action, OneiD returns `login_required`, `consent_required` or `interaction_required` to your redirect URI. - `login` and `select_account`: the user must sign in again. OneiD has no account picker. - `consent`: OneiD shows the consent page. OneiD accepts `login_hint`, `ui_locales`, `display` and `acr_values` but they have no effect. Sending `acr_values=urn:oltin:ac:mfa` does not force MFA; check the `acr` or `amr` claim in the ID token instead. > **Not supported:** OneiD does not accept request objects. A `request` parameter is answered with `request_not_supported` and a `request_uri` parameter with `request_uri_not_supported` at your redirect URI. ```http GET /connect/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid%20profile%20email&state=Xk3fQ9vLp2&nonce=n-7Hq2Lw81&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256 HTTP/1.1 Host: YOUR_ONEID ``` On success, OneiD redirects to your redirect URI with the code, your `state` and the issuer (`iss`): ```http HTTP/1.1 302 Found Location: https://app.example.com/callback?code=Pq7xR2...&state=Xk3fQ9vLp2&iss=https%3A%2F%2FYOUR_ONEID%2F ``` With `response_mode=form_post`, the browser posts the same values to your redirect URI as a form instead. The authorization code is single-use and expires after 5 minutes. Exchange it at the [token endpoint](#token) straight away. **Errors:** returned to your redirect URI as `error`, `error_description`, `state` and `iss`. | Error | Cause | |---|---| | `invalid_request` | A required parameter is missing or wrong. | | `invalid_scope` | The client is not allowed one of the requested scopes. | | `unauthorized_client` | The client is disabled. | | `access_denied` | The user refused consent. | | `login_required`, `consent_required`, `interaction_required` | `prompt=none` was sent and the user would have to interact. | | `request_not_supported`, `request_uri_not_supported` | A `request` or `request_uri` parameter was sent. | > **Warning:** If the `client_id` is unknown or the `redirect_uri` is not registered for the client, OneiD does not redirect. It shows its own error page with `invalid_request`, because it cannot trust the redirect URI. ## Token `POST /connect/token` Exchanges an authorization code, a refresh token or client credentials for tokens. Send the parameters as an `application/x-www-form-urlencoded` body. **Authentication:** - Confidential clients authenticate with their client secret. Use HTTP Basic (`client_secret_basic`, recommended) or send `client_id` and `client_secret` in the body (`client_secret_post`). - Public clients send `client_id` in the body and no secret. `private_key_jwt` appears in discovery but cannot be used, because OneiD has no way to register a client's public key. Mutual TLS client authentication is not supported. Browser applications can call this endpoint cross-origin only from an origin registered as an allowed CORS origin on the client. See [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/). ### authorization_code Exchanges the code from the [authorize](#authorize) redirect. | Parameter | Required | Description | |---|---|---| | `grant_type` | Yes | `authorization_code`. | | `code` | Yes | The code from the callback. | | `redirect_uri` | Yes | The same redirect URI you sent to the authorize endpoint. | | `code_verifier` | Yes | The PKCE code verifier whose hash you sent as `code_challenge`. | | `client_id` | Public clients and `client_secret_post` | Your client ID. | | `client_secret` | `client_secret_post` only | Your client secret. | ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=Pq7xR2...&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk ``` ```json { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0...", "token_type": "Bearer", "expires_in": 3600, "scope": "openid profile email offline_access", "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0...", "refresh_token": "EXAMPLE_OPAQUE_REFRESH_TOKEN" } ``` `refresh_token` is present only when the client has the refresh_token grant and the request asked for `offline_access`. `id_token` is present when the request asked for `openid`. `expires_in` reflects the access token lifetime set for the client (1 hour by default). ### refresh_token Exchanges a refresh token for new tokens. Every refresh returns a new refresh token; store it and discard the old one. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/). | Parameter | Required | Description | |---|---|---| | `grant_type` | Yes | `refresh_token`. | | `refresh_token` | Yes | The most recent refresh token you received. | | `client_id` | Public clients and `client_secret_post` | Your client ID. | | `client_secret` | `client_secret_post` only | Your client secret. | ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=EXAMPLE_OPAQUE_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID ``` The response has the same shape as for `authorization_code`, with a new `refresh_token`. ### client_credentials Issues an access token to a confidential client acting for itself, with no user. Public clients cannot use this grant. No refresh token is issued; request a new access token when the current one expires. See [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/). | Parameter | Required | Description | |---|---|---| | `grant_type` | Yes | `client_credentials`. | | `scope` | Recommended | Space-separated API scopes the client is allowed. The access token carries the scopes you ask for. | | `client_id` | `client_secret_post` only | Your client ID. | | `client_secret` | `client_secret_post` only | Your client secret. | ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&scope=orders.read ``` ```json { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0...", "token_type": "Bearer", "expires_in": 3600, "scope": "orders.read" } ``` In this access token, `sub` is the client ID and `name` is the client's display name. ### Token endpoint errors Errors are returned as JSON with `error` and `error_description`. | Error | HTTP status | Cause | |---|---|---| | `invalid_request` | 400 | A required parameter is missing or malformed. | | `invalid_client` | 401 | Unknown client, wrong secret, expired secret ("The client secret has expired.") or disabled client. | | `invalid_grant` | 400 | The code or refresh token is expired, already used or revoked; the code verifier does not match; or the user was deleted, locked, must change their password or must enrol in MFA. | | `invalid_scope` | 400 | The client is not allowed one of the requested scopes. | | `unsupported_grant_type` | 400 | The grant type is not one OneiD supports. | | `slow_down` | 429 | Too many requests. See [Rate limits](https://oltinid.com/docs/reference/rate-limits/). | ## Userinfo `GET /connect/userinfo` or `POST /connect/userinfo` Returns claims about the signed-in user, limited to the scopes granted to the access token and any `userinfo` claims requested through the [claims parameter](https://oltinid.com/docs/reference/claims/#the-claims-request-parameter). **Authentication:** a user access token from OneiD in the `Authorization: Bearer` header. **Parameters:** none. ```http GET /connect/userinfo HTTP/1.1 Host: YOUR_ONEID Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0... ``` ```json { "sub": "3f2a9c1e-5b7d-4e1a-9c2b-7d4e8f1a2b3c", "name": "Alex Example", "given_name": "Alex", "family_name": "Example", "preferred_username": "alex", "idp": "local", "updated_at": 1700000000, "email": "alex@example.com", "email_verified": true, "role": ["Staff"] } ``` `sub` is always present. Other claims appear only when their scope was granted and the user has a value. `role` is always a JSON array here. See [Claims](https://oltinid.com/docs/reference/claims/). Browser applications can call this endpoint cross-origin only from a registered CORS origin. **Errors:** a missing, expired or revoked access token gets HTTP 401 with a `WWW-Authenticate: Bearer` header. ## Introspection `POST /connect/introspect` Tells a confidential client whether a token is active, following RFC 7662. OneiD answers only for tokens issued to the calling client. **Authentication:** confidential clients only, with their client secret (`client_secret_basic` recommended, `client_secret_post` accepted). Introspection never allows cross-origin browser requests. | Parameter | Required | Description | |---|---|---| | `token` | Yes | The token to inspect. | | `token_type_hint` | No | `access_token` or `refresh_token`. | ```http POST /connect/introspect HTTP/1.1 Host: YOUR_ONEID Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU Content-Type: application/x-www-form-urlencoded token=eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0...&token_type_hint=access_token ``` For an active token the response contains `"active": true` and details of the token, for example: ```json { "active": true, "iss": "https://YOUR_ONEID/", "sub": "3f2a9c1e-5b7d-4e1a-9c2b-7d4e8f1a2b3c", "client_id": "YOUR_CLIENT_ID", "scope": "openid profile orders.read", "token_type": "Bearer", "iat": 1700000000, "exp": 1700003600 } ``` For an expired, revoked or unknown token the response is: ```json { "active": false } ``` > **Tip:** Most APIs do not need introspection. Short-lived access tokens validated locally are simpler and faster. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/). **Errors:** `invalid_client` (HTTP 401) when client authentication fails; `invalid_request` when `token` is missing. ## Revocation `POST /connect/revoke` Revokes a refresh token or an access token in OneiD's store, following RFC 7009. Revoke the refresh token when a user signs out of your application. **Authentication:** confidential clients authenticate with their client secret. Public clients send `client_id` in the body. Browser applications can call this endpoint cross-origin only from a registered CORS origin. | Parameter | Required | Description | |---|---|---| | `token` | Yes | The token to revoke. | | `token_type_hint` | No | `refresh_token` or `access_token`. | | `client_id` | Public clients and `client_secret_post` | Your client ID. | | `client_secret` | `client_secret_post` only | Your client secret. | ```http POST /connect/revoke HTTP/1.1 Host: YOUR_ONEID Content-Type: application/x-www-form-urlencoded token=EXAMPLE_OPAQUE_REFRESH_TOKEN&token_type_hint=refresh_token&client_id=YOUR_CLIENT_ID ``` A successful revocation returns HTTP 200. > **Note:** An API that validates access tokens locally does not see a revocation until the token expires. Keep access token lifetimes short. **Errors:** `invalid_client` (HTTP 401) when client authentication fails; `invalid_request` when `token` is missing. ## End session `GET /connect/logout` or `POST /connect/logout` Signs the user out of OneiD (RP-Initiated Logout 1.0) and, when you pass a registered `post_logout_redirect_uri`, sends the browser back to your application. Signing out revokes your application's tokens for the user. When users come from an upstream OpenID Connect provider, the sign-out continues to that provider. See [Sign-out](https://oltinid.com/docs/guides/logout/). **Authentication:** none for the client. Identify your application with `id_token_hint` or `client_id`. | Parameter | Required | Description | |---|---|---| | `id_token_hint` | Recommended | An ID token OneiD issued to your application for this user. | | `client_id` | When no `id_token_hint` is sent and you use `post_logout_redirect_uri` | Your client ID. | | `post_logout_redirect_uri` | No | Where to send the browser afterwards. Must match a post-logout redirect URI registered for the client exactly. | | `state` | No | A value OneiD returns unchanged to `post_logout_redirect_uri`. | ```http GET /connect/logout?id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0...&post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2Fsigned-out&state=Lg5pW0 HTTP/1.1 Host: YOUR_ONEID ``` ```http HTTP/1.1 302 Found Location: https://app.example.com/signed-out?state=Lg5pW0 ``` Without `post_logout_redirect_uri`, the user ends on OneiD's signed-out page. If the `id_token_hint` names a different user from the one signed in, OneiD asks the user to confirm the sign-out first. **Errors:** OneiD shows logout errors on its own page and does not redirect the browser. | Error | Cause | |---|---| | `invalid_request` | `post_logout_redirect_uri` was sent with neither `id_token_hint` nor `client_id`. | | `unauthorized_client` | The client is disabled. | OneiD also refuses an `id_token_hint` it did not issue and a `post_logout_redirect_uri` that is not registered for the client. > **Not supported:** Front-channel logout, back-channel logout and the session management iframe. Other applications the user signed in to are not notified. ## Learn more - [Discovery document](https://oltinid.com/docs/reference/discovery/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/) --- # Discovery document > Read OneiD's OpenID Connect discovery document field by field and know which advertised options to use. Source: https://oltinid.com/docs/reference/discovery/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt OneiD publishes an OpenID Connect discovery document at `https://YOUR_ONEID/.well-known/openid-configuration`. Point your library at your OneiD address and it reads the endpoints, keys and supported features from this document. This page explains each field and the few values you should not rely on. ## The document ```http GET /.well-known/openid-configuration HTTP/1.1 Host: YOUR_ONEID ``` ```json { "issuer": "https://YOUR_ONEID/", "authorization_endpoint": "https://YOUR_ONEID/connect/authorize", "token_endpoint": "https://YOUR_ONEID/connect/token", "introspection_endpoint": "https://YOUR_ONEID/connect/introspect", "end_session_endpoint": "https://YOUR_ONEID/connect/logout", "revocation_endpoint": "https://YOUR_ONEID/connect/revoke", "userinfo_endpoint": "https://YOUR_ONEID/connect/userinfo", "jwks_uri": "https://YOUR_ONEID/.well-known/jwks", "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"], "response_types_supported": ["code"], "response_modes_supported": ["form_post", "fragment", "query"], "scopes_supported": [ "openid", "offline_access", "profile", "email", "phone", "roles", "admin_api", "admin_api_readonly", "admin_console_webhooks" ], "claims_supported": [ "aud", "exp", "iat", "iss", "sub", "name", "given_name", "family_name", "preferred_username", "email", "email_verified", "phone_number", "phone_number_verified", "updated_at", "auth_time", "amr", "acr", "role", "idp" ], "id_token_signing_alg_values_supported": ["RS256"], "code_challenge_methods_supported": ["plain", "S256"], "subject_types_supported": ["public"], "token_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt", "client_secret_basic"], "introspection_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt", "client_secret_basic"], "revocation_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt", "client_secret_basic"], "claims_parameter_supported": true, "request_parameter_supported": false, "request_uri_parameter_supported": false, "authorization_response_iss_parameter_supported": true, "acr_values_supported": ["urn:oltin:ac:pwd", "urn:oltin:ac:mfa", "urn:oltin:ac:external"] } ``` ## Fields ### Issuer and endpoints | Field | Value on OneiD | Meaning | |---|---|---| | `issuer` | `https://YOUR_ONEID/` | The issuer identifier. It ends with a slash. The `iss` claim in every token and the `iss` parameter in authorisation responses carry the same value. | | `authorization_endpoint` | `https://YOUR_ONEID/connect/authorize` | Where the browser starts a sign-in. | | `token_endpoint` | `https://YOUR_ONEID/connect/token` | Where clients exchange codes, refresh tokens and client credentials for tokens. | | `userinfo_endpoint` | `https://YOUR_ONEID/connect/userinfo` | Returns claims about the user for a user access token. | | `introspection_endpoint` | `https://YOUR_ONEID/connect/introspect` | Token introspection for confidential clients. | | `revocation_endpoint` | `https://YOUR_ONEID/connect/revoke` | Revokes refresh tokens and access tokens. | | `end_session_endpoint` | `https://YOUR_ONEID/connect/logout` | RP-initiated sign-out. | | `jwks_uri` | `https://YOUR_ONEID/.well-known/jwks` | The public signing keys. | Details of each endpoint are in [Endpoints](https://oltinid.com/docs/reference/endpoints/). > **Warning:** If your library compares the issuer exactly, use the `issuer` value from discovery, including the trailing slash. `https://YOUR_ONEID` without the slash does not match. ### Flows | Field | Value on OneiD | Meaning | |---|---|---| | `grant_types_supported` | `authorization_code`, `client_credentials`, `refresh_token` | The grants OneiD issues tokens for. Each client is allowed only the grants an administrator set for it. | | `response_types_supported` | `code` | Only the authorization code flow. No implicit or hybrid flow. | | `response_modes_supported` | `form_post`, `fragment`, `query` | How the authorisation response is returned. Use `query` (the default) or `form_post`. | | `code_challenge_methods_supported` | `plain`, `S256` | PKCE methods. Always use `S256`. | | `authorization_response_iss_parameter_supported` | `true` | Authorisation responses include an `iss` parameter (RFC 9207). Check that it equals the issuer. | > **Note:** `fragment` is listed but is not recommended or tested with OneiD. Use `query` or `form_post`. > **Warning:** `plain` is listed in `code_challenge_methods_supported`. Do not use it. Send `code_challenge_method=S256`. ### Scopes and claims | Field | Value on OneiD | Meaning | |---|---|---| | `scopes_supported` | `openid`, `offline_access`, `profile`, `email`, `phone`, `roles`, `admin_api`, `admin_api_readonly`, `admin_console_webhooks` | The identity scopes and OneiD's own administration scopes. Do not use the administration scopes in your applications. API scopes that your administrator creates, such as `orders.read`, may not appear here; ask your OneiD administrator which API scopes exist. | | `claims_supported` | `aud`, `exp`, `iat`, `iss`, `sub`, `name`, `given_name`, `family_name`, `preferred_username`, `email`, `email_verified`, `phone_number`, `phone_number_verified`, `updated_at`, `auth_time`, `amr`, `acr`, `role`, `idp` | Claims OneiD can issue. Which ones you receive depends on the scopes granted. See [Claims](https://oltinid.com/docs/reference/claims/). | | `claims_parameter_supported` | `true` | The `claims` request parameter is read for the `id_token` and `userinfo` members. | | `subject_types_supported` | `public` | Each user has the same `sub` for every client. | | `acr_values_supported` | `urn:oltin:ac:pwd`, `urn:oltin:ac:mfa`, `urn:oltin:ac:external` | The values the `acr` claim in the ID token can take. Sending `acr_values` in a request has no effect. | ### Signing and client authentication | Field | Value on OneiD | Meaning | |---|---|---| | `id_token_signing_alg_values_supported` | `RS256` | ID tokens are signed with RS256. Access tokens use the same algorithm. | | `token_endpoint_auth_methods_supported` | `client_secret_post`, `private_key_jwt`, `client_secret_basic` | How confidential clients authenticate at the token endpoint. Use `client_secret_basic`; `client_secret_post` is also accepted. | | `introspection_endpoint_auth_methods_supported` | Same as above | Client authentication at the introspection endpoint. | | `revocation_endpoint_auth_methods_supported` | Same as above | Client authentication at the revocation endpoint. | > **Not supported:** `private_key_jwt` is listed, but there is no way to register a client's public key, so it cannot be used. Clients authenticate with a client secret. Mutual TLS client authentication is not supported. ### Request objects | Field | Value on OneiD | Meaning | |---|---|---| | `request_parameter_supported` | `false` | Signed request objects in a `request` parameter are not accepted. OneiD answers with `request_not_supported`. | | `request_uri_parameter_supported` | `false` | `request_uri` is not accepted. OneiD answers with `request_uri_not_supported`. | ### Fields that are absent The document has no `registration_endpoint`, `device_authorization_endpoint`, `pushed_authorization_request_endpoint`, `check_session_iframe`, `frontchannel_logout_supported` or `backchannel_logout_supported`. OneiD does not offer these features. See [Standards support](https://oltinid.com/docs/reference/standards-support/). ## Caching - Fetch the document once when your application starts, and keep it in memory. Do not fetch it on every request. - Refresh it from time to time, for example once a day, and when calls start failing in a way that suggests a changed configuration. - Fetch the [JWKS](https://oltinid.com/docs/reference/endpoints/#jwks) separately and refresh it when a token carries a `kid` you do not know. Mainstream OpenID Connect libraries do both for you. - Discovery and JWKS answer requests from any origin, so a browser application can fetch them directly. ## Learn more - [Endpoints](https://oltinid.com/docs/reference/endpoints/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Standards support](https://oltinid.com/docs/reference/standards-support/) --- # Tokens > Understand the ID tokens, access tokens, refresh tokens and codes OneiD issues, their lifetimes, signing keys and how to validate them. Source: https://oltinid.com/docs/reference/tokens/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt OneiD issues four kinds of credentials: ID tokens and access tokens, which are signed JWTs, and authorization codes and refresh tokens, which are opaque. This page describes each one, their default lifetimes, the signing keys, and what your application or API must check. ## Overview | Credential | Format | Who reads it | Purpose | |---|---|---|---| | ID token | JWT, signed with RS256 | Your application (the client) | Tells your application who signed in and how. | | Access token | JWT, signed with RS256 | Your API, or OneiD's userinfo endpoint | Grants access to an API for the scopes it carries. | | Refresh token | Opaque | Only OneiD | Gets new tokens without a new sign-in. | | Authorization code | Opaque | Only OneiD | Exchanged once for tokens at the token endpoint. | Tokens are signed, not encrypted. Anyone who holds a JWT can read its claims, so keep tokens out of URLs and logs. ## ID token The ID token is a JWT for your application. Its audience (`aud`) is your client ID. Do not send it to APIs; send the access token instead. ### Header ```json { "alg": "RS256", "kid": "EXAMPLE_KID" } ``` `kid` names the key in the [JWKS](https://oltinid.com/docs/reference/endpoints/#jwks) that verifies the signature. ### Claims | Claim | Type | When present | Description | |---|---|---|---| | `iss` | string | Always | The issuer, `https://YOUR_ONEID/`, with the trailing slash. | | `sub` | string | Always | The user's identifier. Opaque and stable. See [sub formats](https://oltinid.com/docs/reference/claims/#sub-formats). | | `aud` | string | Always | Your client ID. | | `exp` | number | Always | Expiry time, in seconds since the Unix epoch. | | `iat` | number | Always | Issue time, in seconds since the Unix epoch. | | `nonce` | string | When you sent `nonce` | The `nonce` from your authorisation request. | | `auth_time` | number | Always | When the user last signed in, in seconds since the Unix epoch. | | `amr` | array of strings | Always | How the user signed in: `pwd`, `mfa`, `external`. | | `acr` | string | Always | The authentication level derived from `amr`, for example `urn:oltin:ac:mfa`. | | `name`, `given_name`, `family_name`, `preferred_username`, `updated_at`, `idp` | string, number for `updated_at` | `profile` scope granted | Profile claims. | | `email`, `email_verified` | string, boolean | `email` scope granted | Email claims. | | `phone_number`, `phone_number_verified` | string, boolean | `phone` scope granted | Phone claims. | | `role` | string or array of strings | `roles` scope granted | One value per role. | Claims appear only when the user has a value. The [Claims reference](https://oltinid.com/docs/reference/claims/) lists every claim and the scope that releases it. ID tokens may also contain private claims whose names start with `oi_`. Ignore them, and ignore any claim you do not recognise. > **Note:** There is no `sid` claim in OneiD ID tokens. ### Example (decoded payload) ```json { "iss": "https://YOUR_ONEID/", "sub": "3f2a9c1e-5b7d-4e1a-9c2b-7d4e8f1a2b3c", "aud": "YOUR_CLIENT_ID", "exp": 1700001200, "iat": 1700000000, "nonce": "n-7Hq2Lw81", "auth_time": 1699999950, "amr": ["pwd", "mfa"], "acr": "urn:oltin:ac:mfa", "name": "Alex Example", "preferred_username": "alex", "idp": "local", "email": "alex@example.com", "email_verified": true, "role": "Staff" } ``` ## Access token OneiD access tokens are signed JWTs (RS256), with a `kid` header that matches a key in the JWKS. They are not encrypted. ### Header ```json { "alg": "RS256", "kid": "EXAMPLE_KID" } ``` ### Claims | Claim | Type | Description | |---|---|---| | `iss` | string | The issuer, `https://YOUR_ONEID/`. | | `sub` | string | The user's identifier. For the client credentials grant, the client ID. | | `exp` | number | Expiry time, in seconds since the Unix epoch. | | `iat` | number | Issue time, in seconds since the Unix epoch. | | `scope` | string | The granted scopes, separated by spaces, for example `"openid profile orders.read"`. | | `client_id` | string | The client the token was issued to. | | `jti` | string | A token identifier. May be present. | | `name` | string | With `profile`: the user's name. For the client credentials grant: the client's display name. | | Other profile, email and phone claims | various | Present when their scope was granted, as in the ID token. | | `role` | string or array of strings | Present when `roles` was granted. One value per role. | The access token may contain other claims. Ignore claims you do not recognise. > **Warning:** OneiD access tokens have no `aud` claim. Do not configure your API to require an audience. Check the issuer, signature, expiry and the scope your API needs instead. `auth_time`, `amr` and `acr` are in the ID token only, not in the access token. API scopes, such as `orders.read`, appear only in the `scope` claim and add no other claims. ### Example (decoded payload) User access token: ```json { "iss": "https://YOUR_ONEID/", "sub": "3f2a9c1e-5b7d-4e1a-9c2b-7d4e8f1a2b3c", "exp": 1700003600, "iat": 1700000000, "scope": "openid profile roles orders.read", "client_id": "YOUR_CLIENT_ID", "name": "Alex Example", "preferred_username": "alex", "idp": "local", "role": ["Staff", "OrdersAdmin"] } ``` Client credentials access token: ```json { "iss": "https://YOUR_ONEID/", "sub": "YOUR_CLIENT_ID", "exp": 1700003600, "iat": 1700000000, "scope": "orders.read", "client_id": "YOUR_CLIENT_ID", "name": "Order export service" } ``` ## Refresh token and authorization code Refresh tokens and authorization codes are opaque, encrypted strings. Never parse them or rely on their length or format. Store them as received. The authorization code is single-use. Exchange it immediately at the token endpoint. Refresh tokens behave as follows: - **Issued when:** the client has the refresh_token grant and the authorisation request asked for `offline_access`. - **Rotated on every use:** each refresh returns a new refresh token. Store the new one and discard the old one. - **Not sliding:** a chain of refreshes ends when the original refresh token lifetime runs out. After that, the user must sign in again. - **Reuse is detected:** a refresh token used a second time after a short grace period (about 30 seconds) is refused with `invalid_grant`, and the whole chain, including newer refresh tokens, is revoked. - **The user is checked again:** if the user was deleted or locked, or must change their password or enrol in MFA, the refresh fails with `invalid_grant`. See [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) for how to use them safely. ## Default lifetimes | Credential | Default lifetime | Can be changed | |---|---|---| | Authorization code | 5 minutes | No, fixed | | Access token | 1 hour | Yes, per client, by an administrator | | ID token | 20 minutes | Yes, per client, by an administrator | | Refresh token | 14 days | Yes, per client, by an administrator | Ask your OneiD administrator if your client uses different lifetimes. The token endpoint's `expires_in` tells you the access token lifetime in seconds. The OneiD browser session is separate from these tokens. It lasts 8 hours and is extended while the user is active. See [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/). ## Revocation You can revoke refresh tokens and access tokens at the [revocation endpoint](https://oltinid.com/docs/reference/endpoints/#revocation). OneiD also revokes tokens itself when: - a user signs out of an application (that application's tokens for that user), - the user's password changes, - an administrator removes a role from the user, locks the user, resets the user's MFA or sets a temporary password, - an administrator disables the client or its client secret. Revocation takes effect in OneiD's store. An API that validates access tokens locally does not see the revocation until the token expires. Keep access token lifetimes short. An API that must see revocation immediately can call [introspection](https://oltinid.com/docs/reference/endpoints/#introspection) as a confidential client, but introspection answers only for tokens issued to the calling client, so for most APIs short-lived access tokens with local validation are the recommended pattern. ## Signing keys and rotation - OneiD signs ID tokens and access tokens with RSA keys (2048-bit by default) using RS256. - The public keys are published at `https://YOUR_ONEID/.well-known/jwks`, each with a `kid`. - When keys are rotated, the new key is published in the JWKS before it starts signing. - A retired key stays in the JWKS for 30 days, so tokens it signed still validate. Cache the JWKS. When a token carries a `kid` that is not in your cached set, fetch the JWKS again and retry once. Mainstream JWT and OpenID Connect libraries do this for you. Do not hard-code a key. ## Validate an ID token Your application must check every ID token before it trusts it. Most OpenID Connect libraries do this; make sure the checks are switched on. 1. The token is a JWT signed with `RS256`. Reject any other algorithm, including `none`. 2. The signature verifies with the JWKS key named by `kid`. 3. `iss` equals the `issuer` from discovery exactly, including the trailing slash. 4. `aud` is your client ID. 5. `exp` is in the future. Allow at most a small clock skew. 6. `iat` is not in the future. 7. `nonce` equals the `nonce` you sent in the authorisation request. 8. If you sent `max_age`, `auth_time` is recent enough. 9. If your application requires MFA, `amr` contains `mfa` (or `acr` is `urn:oltin:ac:mfa`). Sending `acr_values` does not enforce this for you. Note that users from an upstream OpenID Connect provider have `amr` `["external"]`; that provider's own MFA policy applies. ## Validate an access token Your API must check every access token before it serves the request. See [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) for library examples. 1. The token is a JWT signed with `RS256`. Reject any other algorithm, including `none`. 2. The signature verifies with the JWKS key named by `kid`. 3. `iss` equals the `issuer` from discovery exactly, including the trailing slash. 4. `exp` is in the future. Allow at most a small clock skew. 5. `scope` contains the scope the endpoint needs. Split the value on spaces; do not search for a substring. 6. Do not require an `aud` claim. OneiD access tokens do not have one. 7. If your API should serve only certain clients, check `client_id` against your own list. 8. Authorise by `scope` and `role`. Identify the user by `iss` and `sub`, never by `email`. ## Learn more - [Claims](https://oltinid.com/docs/reference/claims/) - [Protect an API](https://oltinid.com/docs/guides/protect-an-api/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) --- # Claims > Look up every claim OneiD issues, where it appears, which scope releases it, and how to use the claims request parameter. Source: https://oltinid.com/docs/reference/claims/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt A claim is a piece of information about the user or the token, such as `sub` or `email`. Which claims your application receives depends on the scopes the client is allowed, the scopes it requests and what the user consents to. This page lists every claim and where it appears. ## All claims | Claim | Type | In ID token | In access token | In userinfo | Released by | Description | |---|---|---|---|---|---|---| | `iss` | string | Yes | Yes | No | Always | The issuer, `https://YOUR_ONEID/`, with the trailing slash. | | `sub` | string | Yes | Yes | Yes | Always | The user's identifier. For the client credentials grant, the client ID. See [sub formats](#sub-formats). | | `aud` | string | Yes | No | No | Always | Your client ID. Access tokens have no `aud`. | | `exp` | number | Yes | Yes | No | Always | Expiry time, in seconds since the Unix epoch. | | `iat` | number | Yes | Yes | No | Always | Issue time, in seconds since the Unix epoch. | | `nonce` | string | Yes | No | No | The `nonce` request parameter | Copied from the authorisation request. | | `auth_time` | number | Yes | No | No | Always | When the user last signed in, in seconds since the Unix epoch. | | `amr` | array of strings | Yes | No | No | Always | How the user signed in. See [amr values](#amr-values). | | `acr` | string | Yes | No | No | Always | The authentication level, derived from `amr`. See [acr values](#acr-values). | | `scope` | string | No | Yes | No | Always | The granted scopes, separated by spaces. | | `client_id` | string | No | Yes | No | Always | The client the access token was issued to. | | `name` | string | Yes | Yes | Yes | `profile` | The user's full name. In a client credentials token, the client's display name. | | `given_name` | string | Yes | Yes | Yes | `profile` | First name. | | `family_name` | string | Yes | Yes | Yes | `profile` | Last name. | | `preferred_username` | string | Yes | Yes | Yes | `profile` | The user name. Do not use it as a key. | | `updated_at` | number | Yes | Yes | Yes | `profile` | When the user's profile was last updated, in seconds since the Unix epoch. | | `idp` | string | Yes | Yes | Yes | `profile` | Where the user signed in. See [idp values](#idp-values). | | `email` | string | Yes | Yes | Yes | `email` | Email address. Do not use it as a key. | | `email_verified` | boolean | Yes | Yes | Yes | `email` | Whether the email address is verified. | | `phone_number` | string | Yes | Yes | Yes | `phone` | Phone number. | | `phone_number_verified` | boolean | Yes | Yes | Yes | `phone` | Whether the phone number is verified. | | `role` | string or array of strings | Yes | Yes | Yes | `roles` | One value per role. In userinfo always a JSON array. In a JWT, accept both a single string and an array. | Scope-released claims appear only when the user has a value. Tokens may contain other claims, such as private claims whose names start with `oi_`; ignore claims you do not recognise. There is no `address` scope and no `address` claim. There is no `sid` claim. API scopes that your administrator creates, such as `orders.read`, appear in the access token's `scope` claim only. They add no claims. `offline_access` asks for a refresh token. It releases no claims. ## amr values `amr` (authentication methods references) is a JSON array in the ID token. | Value | Meaning | |---|---| | `pwd` | The user signed in with a password at OneiD (OneiD account or LDAP directory). | | `mfa` | The user also entered an authenticator-app code. Appears together with `pwd`: `["pwd", "mfa"]`. | | `external` | The user signed in at an upstream OpenID Connect provider: `["external"]`. | ## acr values `acr` (authentication context class reference) is derived from `amr`. | Value | When | |---|---| | `urn:oltin:ac:pwd` | Password sign-in without MFA. | | `urn:oltin:ac:mfa` | Password sign-in with MFA. | | `urn:oltin:ac:external` | Sign-in at an upstream OpenID Connect provider. | > **Warning:** OneiD does not enforce `acr_values`. Asking for `urn:oltin:ac:mfa` does not force MFA. To require MFA, check `acr` or `amr` in the ID token in your application, and ask your OneiD administrator to make MFA mandatory for the users concerned. When users sign in at an upstream OpenID Connect provider, that provider's own MFA applies. OneiD does not add its own code on top, and the ID token shows `external`. ## idp values `idp` is released with the `profile` scope. | Value | Where the user signed in | |---|---| | `local` | A OneiD account. | | `ldap` | An LDAP or Active Directory directory connected to OneiD. | | `oidc` | An upstream OpenID Connect provider, such as Okta. | ## sub formats Treat `sub` as opaque and stable. Identify a user by the pair `iss` + `sub`, never by `email` or `preferred_username`. | Sign-in source | `sub` value | |---|---| | OneiD account | An opaque OneiD user ID. | | LDAP or Active Directory | The directory account name in lower case, without the domain. | | Upstream OpenID Connect provider | The upstream provider's `sub`, unchanged. | | Client credentials grant | The client ID. | Each OneiD deployment has one sign-in source. See [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/). ## The claims request parameter OneiD supports the OpenID Connect `claims` request parameter for the `id_token` and `userinfo` members. - OneiD reads the claim names you list. - `essential`, `value` and `values` are ignored. - A claim is released only when it lies within a scope the client is allowed and the user consented to. The `claims` parameter never releases more than the scopes allow. > **Tip:** In most cases you do not need the `claims` parameter. Request the scope instead; its claims go to the ID token, the access token and userinfo. Example `claims` value: ```json { "id_token": { "email": null, "preferred_username": null }, "userinfo": { "phone_number": null } } ``` Send it URL-encoded as a parameter of the authorisation request, together with the scopes that cover the claims: ```http GET /connect/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid%20profile%20email%20phone&state=Xk3fQ9vLp2&nonce=n-7Hq2Lw81&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&claims=%7B%22id_token%22%3A%7B%22email%22%3Anull%2C%22preferred_username%22%3Anull%7D%2C%22userinfo%22%3A%7B%22phone_number%22%3Anull%7D%7D HTTP/1.1 Host: YOUR_ONEID ``` ## Learn more - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Tokens](https://oltinid.com/docs/reference/tokens/) - [Endpoints](https://oltinid.com/docs/reference/endpoints/) --- # Errors and troubleshooting > Recognise every error OneiD returns, where it appears and what to change, and fix the most common integration problems. Source: https://oltinid.com/docs/reference/errors/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt OneiD reports errors with the standard OAuth 2.0 error codes. Where the error appears depends on the endpoint: at your redirect URI, as JSON from the token endpoint, or on OneiD's own error page when it cannot safely redirect. This page lists every error, then walks through common problems. ## Error response format ### Errors at your redirect URI Errors from the authorize endpoint are sent to your redirect URI with these parameters: | Parameter | Description | |---|---| | `error` | The error code, for example `access_denied`. | | `error_description` | A human-readable explanation. Log it; do not parse it or show it to users as is. | | `state` | The `state` you sent. Check it as you would on success. | | `iss` | The issuer, `https://YOUR_ONEID/`. Check that it matches. | ```http HTTP/1.1 302 Found Location: https://app.example.com/callback?error=login_required&error_description=The+user+must+sign+in.&state=Xk3fQ9vLp2&iss=https%3A%2F%2FYOUR_ONEID%2F ``` With `response_mode=form_post`, the same parameters arrive as a posted form. ### Errors on OneiD's error page If the `client_id` is unknown or the `redirect_uri` is not registered for the client, OneiD does not redirect to your application. It shows its own error page with `invalid_request`. Logout errors are also shown on OneiD's page; the browser is not redirected. ### Errors from the token endpoint and other back-channel endpoints The token, introspection and revocation endpoints answer errors with a JSON body and an HTTP status: ```http HTTP/1.1 400 Bad Request Content-Type: application/json;charset=UTF-8 Cache-Control: no-store { "error": "invalid_grant", "error_description": "..." } ``` | HTTP status | Errors | |---|---| | 400 | `invalid_request`, `invalid_grant`, `invalid_scope`, `unsupported_grant_type` | | 401 | `invalid_client` | | 429 | `slow_down` (rate limit). See [Rate limits](https://oltinid.com/docs/reference/rate-limits/). | The body may contain other fields, such as `error_uri`. Ignore fields you do not use. Branch on `error`, never on `error_description`. The userinfo endpoint answers a missing, expired or revoked access token with HTTP 401 and a `WWW-Authenticate: Bearer` header. ## Error codes | Error | Where it appears | Cause | What to do | |---|---|---|---| | `invalid_request` | OneiD error page (authorize) | The `client_id` is unknown, or the `redirect_uri` does not exactly match one registered for the client. | Use the exact redirect URI registered for the client, or ask your administrator to register it. | | `invalid_request` | Redirect URI or token endpoint | A required parameter is missing or malformed, for example `code_challenge`. | Fix the request. See [Endpoints](https://oltinid.com/docs/reference/endpoints/). | | `invalid_request` | OneiD error page (logout) | `post_logout_redirect_uri` was sent with neither `id_token_hint` nor `client_id`. | Send `id_token_hint` (recommended) or `client_id`. | | `invalid_client` | Token, introspection, revocation (HTTP 401) | Unknown client, wrong secret, disabled client, or expired secret ("The client secret has expired."). | Check the client ID and secret. Ask your administrator whether the client is enabled and the secret is current. | | `unauthorized_client` | Authorize, logout | The client is disabled. | Ask your administrator to enable the client. | | `invalid_grant` | Token endpoint | The code or refresh token is expired, already used or revoked; the code verifier does not match the code challenge; or the user was deleted, locked, must change their password or must enrol in MFA. | Start a new sign-in. Do not retry with the same code or refresh token. | | `invalid_scope` | Authorize, token endpoint | The client is not allowed one of the requested scopes. | Request only allowed scopes, or ask your administrator to allow the scope for the client. | | `unsupported_grant_type` | Token endpoint | The grant type is not supported, for example `password`. | Use `authorization_code`, `refresh_token` or `client_credentials`. | | `access_denied` | Redirect URI | The user refused consent. | Show a calm message and let the user try again. | | `login_required` | Redirect URI | `prompt=none` was sent and the user must sign in. | Start an interactive sign-in without `prompt=none`. | | `consent_required` | Redirect URI | `prompt=none` was sent and the user must give consent. | Start an interactive sign-in without `prompt=none`. | | `interaction_required` | Redirect URI | `prompt=none` was sent and the user must change their password or enrol in MFA first. | Start an interactive sign-in without `prompt=none`. | | `request_not_supported` | Redirect URI | A `request` parameter (request object) was sent. | Send the parameters directly in the query or form. | | `request_uri_not_supported` | Redirect URI | A `request_uri` parameter was sent. | Send the parameters directly in the query or form. | | `slow_down` | Any rate-limited endpoint (HTTP 429) | Too many requests. | Wait for the number of seconds in `Retry-After`, then retry. | ## Troubleshooting ### OneiD shows an error page instead of returning to my application **Symptom:** after the redirect to OneiD, the browser stays on a OneiD error page with `invalid_request`. **Cause:** the `client_id` is unknown, or the `redirect_uri` does not exactly match one registered for the client. OneiD compares redirect URIs exactly: scheme, host, port, path and trailing slash. There are no wildcards. `https` is required except for loopback addresses (`localhost`, `127.0.0.1`, `[::1]`). **Fix:** compare the `redirect_uri` in the request with the registered list character by character. Ask your administrator to register the exact value. See [Client settings](https://oltinid.com/docs/reference/client-settings/). ### invalid_client or unauthorized_client **Symptom:** the token endpoint returns `invalid_client` (HTTP 401), or the authorize or logout endpoint returns `unauthorized_client`. **Cause:** `invalid_client` means client authentication failed: unknown client ID, wrong secret, expired secret or disabled client. `unauthorized_client` at authorize and logout means the client is disabled. **Fix:** check that you send the right client ID and secret, with `client_secret_basic` or `client_secret_post`. If the secret was replaced, use the new one. Ask your administrator whether the client is enabled and the secret has not expired. ### invalid_grant at the token endpoint **Symptom:** exchanging a code or refreshing tokens returns `invalid_grant`. **Cause:** one of: - The code was already used. Codes are single-use; reloading the callback page sends the same code again. - The code expired. Codes are valid for 5 minutes. - The `code_verifier` does not match the `code_challenge`, often because the verifier was lost or replaced between the redirect and the callback. - The refresh token expired, was revoked, or was used a second time after the grace period. - The user was deleted or locked, or must change their password or enrol in MFA. **Fix:** exchange the code once, right away, and do not reload the callback page. Keep the code verifier with the `state` until the callback. After a refresh, store the new refresh token. When `invalid_grant` persists, start a new sign-in. ### invalid_scope **Symptom:** the authorize or token endpoint returns `invalid_scope`. **Cause:** the client asked for a scope it is not allowed. **Fix:** request only the scopes allowed for the client, or ask your administrator to allow the scope. API scopes such as `orders.read` must be created and allowed by an administrator first. ### access_denied **Symptom:** the callback receives `error=access_denied`. **Cause:** the user refused consent on the consent page. **Fix:** handle it as a normal outcome. Explain that the application needs the permission and offer to try again. ### HTTP 429 Too Many Requests **Symptom:** a request returns HTTP 429 with `"error": "slow_down"`. **Cause:** the client sent too many requests in a short time. **Fix:** wait the number of seconds in the `Retry-After` header, then retry with backoff. Cache tokens until they expire instead of requesting a new one per call. See [Rate limits](https://oltinid.com/docs/reference/rate-limits/). ### CORS error in the browser console **Symptom:** a browser application shows a CORS error when its library calls the token, userinfo or revocation endpoint. **Cause:** the application's origin is not registered as an allowed CORS origin on an enabled client. **Fix:** ask your administrator to add the origin, in the form `scheme://host[:port]` with no path, to the client's allowed CORS origins. Changes take effect within about 15 seconds. Introspection never allows cross-origin requests. See [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/). ### .NET: "Correlation failed" **Symptom:** an ASP.NET Core application returns from OneiD and fails with "Correlation failed". **Cause:** the application runs on plain `http`. The browser does not send back the correlation cookie that the OpenID Connect handler set before the redirect. **Fix:** run the application on `https`, also in development, and register the `https` redirect URI for the client. ## Learn more - [Endpoints](https://oltinid.com/docs/reference/endpoints/) - [Rate limits](https://oltinid.com/docs/reference/rate-limits/) - [Security best practices](https://oltinid.com/docs/guides/security-best-practices/) --- # Rate limits > Know which OneiD endpoints are rate limited, the token endpoint defaults, the 429 response and how to retry correctly. Source: https://oltinid.com/docs/reference/rate-limits/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt OneiD limits how many requests a client or browser can send in a short time, to protect sign-in and token issuance from abuse. When you exceed a limit, OneiD answers with HTTP 429 and tells you how long to wait. ## What is limited | Area | Limited by | Default | |---|---|---| | Token endpoint (`/connect/token`) | IP address | 120 requests per minute | | Token endpoint (`/connect/token`) | Client and IP address | 600 requests per minute | | Sign-in | IP address and account | Limited; values not published | | MFA code entry | IP address and account | Limited; values not published | | Password recovery | IP address and account | Limited; values not published | The token endpoint limits apply by default. The operator of your OneiD deployment may configure different limits; ask your OneiD administrator if you need the values in use. Account-level protections apply in addition to rate limits. For example, 5 wrong passwords lock a OneiD account for 5 minutes, and 5 wrong MFA codes lock MFA for 15 minutes. ## The 429 response ```http HTTP/1.1 429 Too Many Requests Retry-After: 12 Content-Type: application/json { "error": "slow_down", "error_description": "Too many requests. Try again in 12 seconds." } ``` | Part | Description | |---|---| | Status | `429 Too Many Requests`. | | `Retry-After` header | How many seconds to wait before the next request. | | `error` | Always `slow_down`. | | `error_description` | The same wait time, in words. Do not parse it; use `Retry-After`. | ## Retry correctly 1. When you receive HTTP 429, read `Retry-After` and wait at least that many seconds before you send the request again. 2. If the retry is refused again, wait longer each time (exponential backoff, for example 2, 4, 8 seconds, with some random jitter), and stop after a few attempts. 3. Do not retry immediately in a loop. 4. Surface a clear message to the user if a sign-in step is limited, rather than retrying silently. ## Stay within the limits - **Reuse access tokens.** Cache an access token until shortly before it expires. Do not request a new token for every API call. This matters most for the client credentials grant. - **Refresh once.** When several parts of your application need a new token at the same time, let one of them refresh and share the result. - **Spread scheduled jobs.** Many services starting at the same moment can hit the per-IP limit together, especially behind one outbound address. - **Cache discovery and JWKS.** Fetch them at startup and refresh them occasionally, not per request. ## Learn more - [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/) - [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/) --- # Client settings > See every setting an administrator can set on a OneiD client, what each one controls and the rules OneiD applies to it. Source: https://oltinid.com/docs/reference/client-settings/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt Every application that signs users in or calls the token endpoint is registered in OneiD as a client. An administrator creates and changes clients in the admin console or through the [Admin API](https://oltinid.com/docs/operate/admin-api/). There is no dynamic client registration. This page lists every client setting, so that developers know what to ask for and administrators know what each setting does. ## Settings | Setting | Values | Default | What it controls | |---|---|---|---| | Client ID | Text | Set by the administrator | The identifier your application sends as `client_id`. It appears as `aud` in ID tokens and `client_id` in access tokens. | | Display name | Text | Set by the administrator | A readable name for the application. In client credentials access tokens it is the `name` claim. | | Client type | `public` or `confidential` | Set by the administrator | `public` for browser, mobile and desktop applications, which cannot keep a secret. `confidential` for server-side applications and services, which hold a client secret. | | Grant types | `authorization_code`, `refresh_token`, `client_credentials` | Set by the administrator | Which grants the client may use. `client_credentials` is for confidential clients only. | | PKCE required | On or off | On | Whether the client must send a PKCE code challenge. Always on for public clients. An administrator can turn it off for a confidential client; keep it on. | | Redirect URIs | List of URIs | None | Where OneiD may send the authorisation response. See [redirect URI rules](#redirect-uri-rules). | | Post-logout redirect URIs | List of URIs | None | Where OneiD may send the browser after sign-out. Must match exactly; the [redirect URI rules](#redirect-uri-rules) apply. | | Allowed scopes | Identity scopes and API scopes | Set by the administrator | The scopes the client may request. A request for any other scope gets `invalid_scope`. | | Allowed CORS origins | List of origins | None | Browser origins that may call the token, userinfo and revocation endpoints cross-origin. See [CORS origins](#cors-origins). | | Consent required | On or off | Set by the administrator | Whether users see a consent page for this client. See [consent](#consent). | | Access token lifetime | Duration | 1 hour | How long access tokens for this client are valid. | | ID token lifetime | Duration | 20 minutes | How long ID tokens for this client are valid. | | Refresh token lifetime | Duration | 14 days | How long a refresh token chain lasts. Refreshes do not extend it. | | Enabled | Enabled or disabled | Set by the administrator | A disabled client cannot sign users in or get tokens. | | Client secret | One secret, optional expiry | Confidential clients only | How a confidential client authenticates. See [client secret](#client-secret). | The authorization code lifetime (5 minutes) is fixed and cannot be changed per client. ## Notes ### Client type and PKCE - A public client has no secret. PKCE with `S256` is always required. - A confidential client has one client secret. PKCE is required by default. Keep it on: it protects the authorization code even for server-side applications. ### Grant types - `authorization_code`: user sign-in. The client needs the redirect URIs your application uses. - `refresh_token`: lets the client exchange refresh tokens. The client also has to request `offline_access` to receive one, so `offline_access` must be among its allowed scopes. - `client_credentials`: a service acting for itself, with no user. Confidential clients only. ### Redirect URI rules - OneiD compares redirect URIs exactly. There are no wildcards. - `https` is required, except `http` on loopback addresses: `localhost`, `127.0.0.1` and `[::1]`. - Private-use schemes in reverse-domain form, such as `com.example.app:/callback`, are allowed for public clients only. - A redirect URI must not contain a fragment (`#...`). ### Allowed scopes - Identity scopes: `openid`, `profile`, `email`, `phone`, `roles`, `offline_access`. There is no `address` scope. - API scopes are created by an administrator, for example `orders.read`. None exist by default. - The privileged scopes `admin_api`, `admin_api_readonly` and `admin_console_webhooks` are for OneiD's own administration. Only a full administrator (role `All`) can allow them for a client, and they are removed at sign-in for users without an administrator role. Do not use them in applications. ### CORS origins - Enter each origin as `scheme://host[:port]`, without a path, for example `https://app.example.com`. - Origins take effect only on an enabled client. Changes take effect within about 15 seconds. - Introspection never allows cross-origin requests. Discovery and JWKS answer any origin without registration. - CORS requests never carry OneiD cookies. ### Consent - **Consent required (explicit):** users see a consent page for the scopes the client requests. If the user ticks "remember", OneiD keeps the decision and does not ask again for the same set of scopes. Users can untick optional scopes. A refusal returns `access_denied`. - **Consent not required (implicit):** typical for first-party applications. No consent page is shown, unless the request carries `prompt=consent`. ### Enabled and disabled When a client is disabled: - the authorize and logout endpoints answer `unauthorized_client`, - the token, introspection and revocation endpoints answer `invalid_client`, - OneiD revokes the client's tokens. ### Client secret - A confidential client has exactly one client secret. - When OneiD generates the secret, the value is shown once. Copy it into your application's secret store straight away; it cannot be displayed again. - An administrator can supply a secret instead. It must be at least 32 characters long. - A secret can have an optional expiry. After it, the token endpoint answers `invalid_client` with "The client secret has expired." - Replacing a secret takes effect immediately: the old secret stops working at once. Plan the change: have the new value ready to deploy to your application when the administrator replaces it. - Disabling the secret disables the client and revokes its tokens. - Your application sends the secret with `client_secret_basic` (recommended) or `client_secret_post`. Never put a secret in a browser, mobile or desktop application. > **Note:** `private_key_jwt` and mutual TLS client authentication are not available. There is no setting for a client public key or certificate. ## Endpoint permissions OneiD derives each client's endpoint permissions from its grant types and client type. Administrators do not set them. | Endpoint | Allowed when | |---|---| | Token | Always | | Revocation | Always | | Authorize | The client has the `authorization_code` grant | | End session (logout) | The client has the `authorization_code` grant | | Introspection | The client is confidential | ## Learn more - [Register an application](https://oltinid.com/docs/get-started/register-an-application/) - [Choose a flow](https://oltinid.com/docs/get-started/choose-a-flow/) - [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/) - [Automate with the Admin API](https://oltinid.com/docs/operate/admin-api/) --- # Standards support > Check which OAuth 2.0 and OpenID Connect standards and features OneiD supports, which it does not, and what to use instead. Source: https://oltinid.com/docs/reference/standards-support/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt This page lists the standards and features OneiD supports and the ones it does not. For each unsupported item it says what to do instead. If something you need is not listed, ask your OneiD administrator or [talk to us](https://oltinid.com/contact/?topic=organisation). ## Conformance testing OneiD was tested with the OpenID Foundation's conformance suite, using the Config, Basic, Form Post and RP-Initiated Logout provider test plans, with no failed test. This is a test result, not a certification. ## Specifications | Specification | Status | Note or alternative | |---|---|---| | OAuth 2.0 (RFC 6749) | Supported | Authorization code, client credentials and refresh token grants. | | PKCE (RFC 7636) | Supported | `S256`. Required for public clients and, by default, for confidential clients. | | Bearer token usage (RFC 6750) | Supported | Send access tokens in the `Authorization: Bearer` header. | | JSON Web Token (RFC 7519) | Supported | ID tokens and access tokens are JWTs signed with RS256. | | Token introspection (RFC 7662) | Supported | Confidential clients only, for tokens issued to the calling client. | | Token revocation (RFC 7009) | Supported | Revokes refresh tokens and access tokens in OneiD's store. | | Authorization server issuer identification (RFC 9207) | Supported | Authorisation responses include `iss`. | | OpenID Connect Core 1.0 | Supported | Code flow, ID tokens, userinfo, `prompt`, `max_age`, `claims` parameter. | | OpenID Connect Discovery 1.0 | Supported | `/.well-known/openid-configuration`. | | OpenID Connect RP-Initiated Logout 1.0 | Supported | `/connect/logout`. | | OAuth 2.0 Form Post Response Mode | Supported | `response_mode=form_post`. | | OpenID Connect Dynamic Client Registration | Not supported | An administrator registers clients in the admin console or through the Admin API. | | OpenID Connect Session Management | Not supported | No `check_session_iframe`. Use `prompt=none` checks or refresh failures to notice a session end. | | OpenID Connect Front-Channel Logout | Not supported | Other applications are not notified at sign-out. Keep application sessions short. | | OpenID Connect Back-Channel Logout | Not supported | As above. | | Pushed authorization requests, PAR (RFC 9126) | Not supported | Send parameters directly in the authorisation request. | | JWT-secured authorization requests, JAR (RFC 9101) | Not supported | `request` and `request_uri` are answered with `request_not_supported` and `request_uri_not_supported`. Send parameters directly. | | DPoP (RFC 9449) | Not supported | Use bearer tokens over TLS with short lifetimes. | | Mutual TLS client authentication and certificate-bound tokens (RFC 8705) | Not supported | Authenticate with a client secret. | | Token exchange (RFC 8693) | Not supported | Use client credentials for service-to-service calls. | | Device authorization grant (RFC 8628) | Not supported | OneiD has no flow for devices without a browser. On devices with a browser, use authorization code with PKCE. | | Client-initiated backchannel authentication, CIBA | Not supported | No alternative in OneiD. | | Resource indicators (RFC 8707) | Not supported | Access tokens have no `aud`. APIs check issuer, signature, expiry and scope. | | SAML 2.0 | Not supported | Connect applications with OpenID Connect. Upstream identity providers must speak OpenID Connect. | | WS-Federation | Not supported | Connect applications with OpenID Connect. | | SCIM | Not supported | Manage users through the admin console or the [Admin API](https://oltinid.com/docs/operate/admin-api/). | ## Grants and flows | Grant or flow | Status | Note or alternative | |---|---|---| | Authorization code with PKCE | Supported | For every application that signs users in. | | Refresh token | Supported | Request `offline_access`; the client needs the refresh_token grant. Rotated on every use. | | Client credentials | Supported | Confidential clients only. No refresh token. | | Implicit | Not supported | Use authorization code with PKCE. | | Hybrid | Not supported | Use authorization code with PKCE. | | Resource owner password credentials (password) | Not supported | Use authorization code with PKCE. | | Device code | Not supported | See device authorization grant above. | | Token exchange | Not supported | See above. | | CIBA | Not supported | See above. | ## Client authentication | Method | Status | Note or alternative | |---|---|---| | `client_secret_basic` | Supported | Recommended for confidential clients. | | `client_secret_post` | Supported | Accepted. | | Public client (`client_id` only, no secret) | Supported | For browser, mobile and desktop applications, with PKCE. | | `private_key_jwt` | Not supported | Listed in discovery, but there is no way to register a client's public key. Use a client secret. | | `tls_client_auth`, `self_signed_tls_client_auth` | Not supported | Use a client secret. | ## Response types, modes and PKCE methods | Item | Status | Note or alternative | |---|---|---| | `response_type=code` | Supported | The only response type. | | `response_mode=query` | Supported | The default. | | `response_mode=form_post` | Supported | | | `response_mode=fragment` | Not recommended | Listed in discovery but not recommended or tested. Use `query` or `form_post`. | | `code_challenge_method=S256` | Supported | Always use it. | | `code_challenge_method=plain` | Do not use | Listed in discovery. Use `S256`. | ## Tokens | Item | Status | Note or alternative | |---|---|---| | JWT access tokens, RS256 | Supported | With `kid` matching the JWKS. | | ID tokens, RS256 | Supported | The only signing algorithm. | | Opaque refresh tokens and authorization codes | Supported | Never parse them. | | `aud` claim in access tokens | Not supported | APIs must not require an audience. Check scope instead. | | Encrypted tokens (JWE) | Not supported | Tokens are signed, not encrypted. Keep them out of URLs and logs. | | Pairwise subject identifiers | Not supported | `subject_types_supported` is `public`. | | Signing key rotation | Supported | New keys are published before use; retired keys stay in the JWKS for 30 days. | ## Scopes and claims | Item | Status | Note or alternative | |---|---|---| | `openid`, `profile`, `email`, `phone`, `roles`, `offline_access` | Supported | See [Claims](https://oltinid.com/docs/reference/claims/). | | Custom API scopes | Supported | Created by an administrator. Appear in the access token's `scope` claim. | | `address` scope | Not supported | Keep postal addresses in your application. | | `sid` claim | Not supported | | | `claims` request parameter | Supported | `id_token` and `userinfo` members. Claim names are read; `essential`, `value` and `values` are ignored. | ## Authorisation request parameters | Parameter | Status | Note or alternative | |---|---|---| | `state`, `nonce` | Supported | Recommended on every request. | | `prompt=none`, `login`, `select_account`, `consent` | Supported | `select_account` forces a new sign-in; there is no account picker. | | `max_age` | Supported | Compared with `auth_time`. | | `id_token_hint` | Supported | A different user from the one signed in forces sign-in. | | `login_hint` | Not honoured | Accepted with no effect. The user enters their user name. | | `ui_locales` | Not honoured | Sign-in pages are in English. | | `display` | Not honoured | Accepted with no effect. | | `acr_values` | Not enforced | Accepted with no effect. Check `acr` or `amr` in the ID token, and ask your administrator to make MFA mandatory. | | `request`, `request_uri` | Not supported | See JAR above. | ## Sessions and sign-out | Item | Status | Note or alternative | |---|---|---| | Single sign-on across applications | Supported | Through the OneiD browser session (8 hours, extended while active). | | RP-initiated logout | Supported | See [Sign-out](https://oltinid.com/docs/guides/logout/). | | Sign-out at an upstream OpenID Connect provider | Supported | Sign-out continues to the provider. | | Front-channel and back-channel logout | Not supported | Use short application sessions, `prompt=none` checks or refresh failures. | | Session management iframe | Not supported | As above. | ## Multi-factor authentication | Method | Status | Note or alternative | |---|---|---| | Authenticator app (TOTP, 6 digits, 30 seconds) | Supported | Optional or mandatory, for everyone or per user. Applies to OneiD accounts and LDAP users. | | Upstream provider MFA | Supported | Users from an upstream OpenID Connect provider use that provider's MFA. | | SMS codes | Not supported | Use an authenticator app. | | Email codes | Not supported | Use an authenticator app. | | Push notifications | Not supported | Use an authenticator app. | | Passkeys and WebAuthn | Not supported | Use an authenticator app. | | Recovery codes | Not supported | If a user loses their device, an administrator resets their MFA. | ## Users, sign-in sources and federation | Item | Status | Note or alternative | |---|---|---| | OneiD accounts | Supported | Created by administrators. See [OneiD accounts and MFA](https://oltinid.com/docs/sign-in-sources/oneid-accounts/). | | LDAP and Active Directory | Supported | One directory server per deployment. See [LDAP and Active Directory](https://oltinid.com/docs/sign-in-sources/ldap/). | | Upstream OpenID Connect provider, such as Okta | Supported | See [Okta and other OpenID Connect providers](https://oltinid.com/docs/sign-in-sources/openid-connect-provider/). | | Microsoft Entra ID as a sign-in source | Not available | [Contact us](https://oltinid.com/contact/?topic=organisation) if you need Entra ID. | | Several sign-in sources in one deployment | Not supported | Each deployment has one sign-in source. | | Social-login buttons | Not supported | | | User self-registration | Not supported | Administrators create OneiD accounts. | | SAML identity providers | Not supported | Use an OpenID Connect provider. | | SCIM provisioning | Not supported | Use the [Admin API](https://oltinid.com/docs/operate/admin-api/). | | Tenants or organisations inside a deployment | Not supported | Each deployment has one issuer, one database and one user store. | ## Administration and integration | Item | Status | Note or alternative | |---|---|---| | Admin console | Supported | Applications, users, roles, API scopes, sessions, audit log, signing keys, import and export of clients. | | Admin API | Supported | Under `/api/admin/v1`. See [Automate with the Admin API](https://oltinid.com/docs/operate/admin-api/). | | Incoming help-desk webhooks | Supported | Reset a user's password or MFA from a help-desk tool. | | Outgoing event webhooks | Not supported | OneiD does not send events to other systems. | | Custom logos and colours on sign-in pages | Not supported | Sign-in pages use the OneiD design. | | Sign-in page languages other than English | Not supported | | ## Learn more - [Discovery document](https://oltinid.com/docs/reference/discovery/) - [Endpoints](https://oltinid.com/docs/reference/endpoints/) - [Choose a flow](https://oltinid.com/docs/get-started/choose-a-flow/) --- # Where users come from > Understand the sign-in sources a OneiD deployment can use, what users see with each, and how groups become roles. Source: https://oltinid.com/docs/sign-in-sources/overview/ · Section: Sign-in sources · All OneiD documentation: https://oltinid.com/llms.txt Every OneiD deployment checks users against one sign-in source. The source decides where users are kept and where their passwords are checked. Applications do not need to know which source is in use: they receive the same kind of tokens either way. ## One source per deployment A OneiD deployment uses exactly one sign-in source. You choose it when the deployment is set up. To use two sources, for example a directory for staff and OneiD accounts for partners, you need two deployments, each with its own OneiD address. ## Sources compared | | OneiD accounts | LDAP and Active Directory | OpenID Connect provider (for example Okta) | Microsoft Entra ID | |---|---|---|---|---| | What users see | The OneiD sign-in page | The OneiD sign-in page; they enter their directory user name and password | The provider's own sign-in page | Not available | | Where the password is checked | OneiD | Your directory, over LDAPS. OneiD does not store the password. | The provider | | | Who creates users | OneiD administrators | Your directory administrators | The provider's administrators | | | `sub` | An opaque OneiD user ID | The directory account name, in lower case, without the domain | The provider's `sub`, unchanged | | | `idp` | `local` | `ldap` | `oidc` | | | MFA | OneiD MFA with an authenticator app | OneiD MFA with an authenticator app | The provider's own MFA | | | `amr` after sign-in | `["pwd"]`, or `["pwd","mfa"]` with MFA | `["pwd"]`, or `["pwd","mfa"]` with MFA | `["external"]` | | Microsoft Entra ID is not available as a sign-in source. [Contact us](https://oltinid.com/contact/?topic=organisation) if you need Entra ID. Read more about each source: - [OneiD accounts and MFA](https://oltinid.com/docs/sign-in-sources/oneid-accounts/) - [LDAP and Active Directory](https://oltinid.com/docs/sign-in-sources/ldap/) - [Okta and other OpenID Connect providers](https://oltinid.com/docs/sign-in-sources/openid-connect-provider/) ## What applications see Applications use OneiD in the same way whatever the source: - The same endpoints, flows and token formats. - The same claims for the same scopes. `name`, `email` and the other profile claims come from the source. - The same `role` claim. With OneiD accounts, administrators assign roles in OneiD. With a directory or a provider, roles come from groups (see below). Three things differ, and your application can read them if it cares: - `idp` (with the `profile` scope) names the source. - `amr` and `acr` in the ID token say how the user signed in. With an upstream provider, `acr` is `urn:oltin:ac:external`; OneiD cannot see whether the provider asked for MFA. - The format of `sub`. Treat it as opaque and key users by `iss` and `sub` together. See [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/#identify-users-by-issuer-and-subject). ## From groups to roles With LDAP and Active Directory and with an OpenID Connect provider, OneiD turns the user's groups into roles at each sign-in. Your OneiD operator configures four rules: | Rule | What it does | |---|---| | Required group | Optional. Users who are not members of this group cannot sign in. | | Administrators group | Optional. Members get OneiD's `All` administrator role, which gives full access to the admin console. | | Group-to-role mappings | Each listed group becomes the role you name, for example group `Order-Viewers` becomes role `OrderViewer`. | | Pass-through of unmapped groups | Optional. Groups without a mapping become roles named with a prefix you choose, for example `ext-Finance`. A pass-through role never takes the name of a built-in administrator role. | Groups that are not mapped and not passed through give no role. > **Warning:** Choose the administrators group with care. Its members can change every setting in OneiD. Use a small, dedicated group. For an OpenID Connect provider, OneiD reads the `groups` and `role` claims the provider sends. For LDAP and Active Directory, OneiD reads the user's group memberships from the directory. ## Learn more - [OneiD accounts and MFA](https://oltinid.com/docs/sign-in-sources/oneid-accounts/) - [LDAP and Active Directory](https://oltinid.com/docs/sign-in-sources/ldap/) - [Okta and other OpenID Connect providers](https://oltinid.com/docs/sign-in-sources/openid-connect-provider/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) --- # OneiD accounts and MFA > Learn how OneiD accounts are created, how passwords, lockout, recovery and MFA work, and what users and administrators can do. Source: https://oltinid.com/docs/sign-in-sources/oneid-accounts/ · Section: Sign-in sources · All OneiD documentation: https://oltinid.com/llms.txt With OneiD accounts, OneiD keeps the users itself and checks their passwords. Administrators create the accounts, and users can protect them with an authenticator app. This page describes what users experience and what administrators can do. ## How accounts are created An administrator creates each account in the admin console, or through the [Admin API](https://oltinid.com/docs/operate/admin-api/). There is no self-service sign-up: users cannot register themselves. When the administrator sets the first password, they can require the user to change it at the first sign-in. See [Mandatory password change](#mandatory-password-change). ## Passwords A password must: - be at least 8 characters long, - contain an upper-case letter, a lower-case letter, a digit and a symbol, - differ from the user name and the email address. Administrators can also ban patterns, for example the organisation's name. ### Lockout After 5 wrong passwords, OneiD locks the account for 5 minutes. A locked account and an unknown user name get the same message, so the sign-in page does not reveal which accounts exist. An administrator can unlock an account sooner. ### Forgotten password 1. On the sign-in page, the user chooses to reset the password. 2. OneiD sends a 6-digit code to the user's email address. The code is valid for 10 minutes. 3. The user enters the code and a new password. After 6 wrong codes, OneiD blocks password resets for that address for 15 minutes. ### Forgotten user name Users who forget their user name can ask for it on the sign-in page. OneiD sends it to their email address. ### Mandatory password change An administrator can require a user to change the password. At the next sign-in, OneiD sends the user to change it before they can continue to any application. Until the password is changed: - a sign-in request with `prompt=none` returns `interaction_required` to the application, - refresh requests for that user fail with `invalid_grant`. ## Multi-factor authentication OneiD offers MFA with an authenticator app (TOTP): the app shows a 6-digit code that changes every 30 seconds. Use any authenticator app that supports these time-based codes. > **Not supported:** OneiD does not offer SMS codes, email codes, push notifications, passkeys or WebAuthn, or recovery codes. ### MFA policy An administrator chooses how MFA applies: - **Optional.** Users can turn MFA on from their profile page. - **Mandatory for everyone.** Every user must enrol. - **Mandatory per user.** The administrator requires MFA for chosen users. A user who must use MFA but has not enrolled is sent to enrol at the next sign-in, before they reach any application. Until then, `prompt=none` requests return `interaction_required` and refresh requests fail with `invalid_grant`. ### Enrol an authenticator app 1. Sign in to OneiD. If MFA is mandatory, OneiD shows the enrolment page straight away. Otherwise open your profile page and choose to turn on MFA. 2. OneiD shows a QR code. Scan it with your authenticator app. 3. Enter the 6-digit code the app shows, to confirm that the app is set up. > **Checkpoint:** OneiD confirms that MFA is on. From now on, sign-in asks for a code after your password. ### Sign in with MFA 1. Enter your user name and password. 2. Enter the current 6-digit code from your authenticator app. After 5 wrong codes, OneiD locks MFA for the account for 15 minutes. After an MFA sign-in, the ID token contains `amr` `["pwd","mfa"]` and `acr` `urn:oltin:ac:mfa`. Applications can check these before sensitive actions. ### Lost device There are no recovery codes. If a user loses the device with the authenticator app, an administrator resets the user's MFA. The user then enrols again. ### Turn off MFA A user can turn off MFA on the profile page. OneiD asks for the current password or a code from the authenticator app, and sends an email to tell the user that MFA was turned off. If MFA is mandatory for the user, OneiD sends them to enrol again at the next sign-in. ## The profile page Signed-in users have a profile page in OneiD where they can: - change their display name, - change their password, - turn MFA on or off. ## The consent page When an application asks for consent, OneiD shows the application's name and the scopes it requests. The user can: - untick optional scopes, - tick "remember" so OneiD does not ask again for the same scopes, - refuse, in which case the application receives `access_denied`. Sign-in, consent and profile pages are in English and use the OneiD design. Your own logo and colours cannot be configured. ## What administrators can do In the admin console, an administrator with the right role can: | Action | Effect | |---|---| | Create a user | Creates the account. | | Assign roles | The roles appear in the `role` claim when an application requests the `roles` scope. | | Unlock | Ends a password lockout early. | | Reset MFA | Removes the user's authenticator app. The user enrols again. | | Reset the password by email | Sends the user a reset code. | | Set a temporary password | Sets a new password that the administrator passes to the user. | | Require a password change | Sends the user to change the password at the next sign-in. | | Require MFA | Sends the user to enrol at the next sign-in. | When an administrator removes a role, locks the user, resets MFA or sets a temporary password, OneiD revokes the user's tokens. A password change by the user does the same. ## Learn more - [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/) - [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/) - [Security best practices](https://oltinid.com/docs/guides/security-best-practices/) - [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/) --- # LDAP and Active Directory > Prepare your directory so OneiD can sign users in with their directory accounts, and decide how directory groups become roles. Source: https://oltinid.com/docs/sign-in-sources/ldap/ · Section: Sign-in sources · All OneiD documentation: https://oltinid.com/llms.txt With LDAP or Active Directory as the sign-in source, users sign in to OneiD with their directory user name and password. OneiD checks the password against your directory over LDAPS and never stores it. Your directory stays the place where accounts and groups are managed. ## How sign-in works 1. An application sends the user to OneiD. 2. The user enters the directory user name and password on the OneiD sign-in page. 3. OneiD checks the password with your directory over an encrypted LDAPS connection. 4. OneiD reads the user's details and groups from the directory and turns the groups into roles. 5. If MFA applies, the user enters a code from an authenticator app. See [OneiD accounts and MFA](https://oltinid.com/docs/sign-in-sources/oneid-accounts/#multi-factor-authentication); MFA works the same way for directory users. 6. OneiD issues tokens to the application. Applications receive the same tokens as with any other source, with `idp` set to `ldap`. ### The user's `sub` For directory users, `sub` is the directory account name in lower case, without the domain. For example, the account `JDoe` in the domain `EXAMPLE` has the `sub` value `jdoe`. Applications should still treat `sub` as opaque and key users by `iss` and `sub`. ## What to give your OneiD operator Your OneiD operator configures the connection. For a deployment hosted by OneiD, the operator is the OneiD team. Prepare the following and send the secret values over a secure channel, never by plain email. - [ ] **Directory server address.** The host name of one directory server that OneiD can reach over LDAPS. - [ ] **Port.** 636 unless your directory uses another LDAPS port. - [ ] **Network access.** Allow connections from OneiD to that server and port. Ask your operator for the addresses OneiD connects from. - [ ] **Service account.** A directory account with read access to the users and groups that will sign in. OneiD uses it to look users up. It needs no write access. - [ ] **Service account password.** Supplied as a secret. - [ ] **Bind type.** Basic or Negotiate. - [ ] **Search base.** The part of the directory that contains the users, for example `OU=Staff,DC=example,DC=com`. - [ ] **Domain.** Your directory domain, for example `EXAMPLE`. - [ ] **Server certificate thumbprint (optional).** The SHA-256 thumbprint of the directory server's certificate. With it, OneiD accepts only that certificate. - [ ] **Group rules.** The required group, the administrators group, the group-to-role mappings and, if wanted, a prefix for unmapped groups. See below. ## Map directory groups to roles OneiD turns the user's directory groups into roles at each sign-in. The rules are described in [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/#from-groups-to-roles). An example plan you could send to your operator: | Rule | Directory group | Result | |---|---|---| | Required group | `App-Users` | Only members can sign in. | | Administrators group | `OneiD-Admins` | Members get OneiD's `All` administrator role. | | Mapping | `Order-Viewers` | Role `OrderViewer` | | Mapping | `Order-Managers` | Role `OrderManager` | | Pass-through prefix | `dir-` | An unmapped group `Finance` becomes role `dir-Finance`. | The same plan written out: ```yaml requiredGroup: App-Users administratorsGroup: OneiD-Admins mappings: Order-Viewers: OrderViewer Order-Managers: OrderManager passThroughUnmappedGroups: true passThroughPrefix: dir- ``` This is a planning format to agree with your operator, not a configuration file you upload. > **Warning:** Members of the administrators group can change every setting in OneiD. Use a small group that exists only for this purpose. Leave out pass-through if your applications only need a few roles. Fewer roles in tokens are easier to reason about. ## Limitations - **One directory server.** OneiD connects to one server. There is no list of fallback servers. When that server cannot be reached, directory users cannot sign in. Talk to your operator about how to keep the address you give available. - **Cloud-only accounts.** Accounts that exist only in Microsoft Entra ID and not in your on-premises directory cannot sign in through LDAP. Microsoft Entra ID is not available as a sign-in source; [talk to us](https://oltinid.com/contact/?topic=organisation) if you need it. - **One source per deployment.** A deployment that uses your directory does not also offer OneiD accounts or an upstream provider. ## Learn more - [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/) - [OneiD accounts and MFA](https://oltinid.com/docs/sign-in-sources/oneid-accounts/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Run OneiD in your own environment](https://oltinid.com/docs/operate/overview/) --- # Okta and other OpenID Connect providers > Register OneiD at Okta or another OpenID Connect provider so users sign in there, and map the provider's groups to roles. Source: https://oltinid.com/docs/sign-in-sources/openid-connect-provider/ · Section: Sign-in sources · All OneiD documentation: https://oltinid.com/llms.txt With an OpenID Connect provider as the sign-in source, OneiD sends users to your provider to sign in, for example Okta. The provider checks the password and runs its own MFA. OneiD then issues its own tokens to your applications, so applications connect only to OneiD. ## How sign-in works 1. An application sends the user to OneiD. 2. OneiD sends the user straight on to the provider's sign-in page. OneiD uses the authorization code flow with PKCE towards the provider; this is fixed. 3. The user signs in at the provider, including any MFA the provider requires. 4. The provider returns the user to OneiD at `https://YOUR_ONEID/signin-oidc-external`. 5. OneiD reads the user's claims and groups, turns the groups into roles and issues tokens to the application. Applications receive the same tokens as with any other source, with `idp` set to `oidc`, `amr` set to `["external"]` and `acr` set to `urn:oltin:ac:external`. The `sub` is the provider's `sub`, unchanged. ### MFA MFA happens at the provider. OneiD does not add its own code on top. If your applications need MFA, require it in the provider's sign-in policy. ### Sign-out When a user signs out of an application through OneiD, OneiD ends its own session and then sends the user on to the provider's sign-out, so the user is signed out there as well. ## Register OneiD at the provider At your provider, OneiD is an ordinary web application. Create it with these settings: | Setting | Value | |---|---| | Application type | Web application (server-side, with a client secret) | | Grant type | Authorization code | | PKCE | Required, if the provider offers the option | | Sign-in redirect URI | `https://YOUR_ONEID/signin-oidc-external` | | Sign-out redirect URI | `https://YOUR_ONEID/signout-callback-oidc` | | Scopes | `openid`, `profile`, `email`, and whatever scope your provider needs to release groups | | Groups | Send a `groups` claim with the groups OneiD should map to roles | `/signin-oidc-external` and `/signout-callback-oidc` are the default paths. Ask your OneiD operator to confirm them for your deployment before you register. ## What to give your OneiD operator Your OneiD operator configures the connection. For a deployment hosted by OneiD, the operator is the OneiD team. Send the client secret over a secure channel, never by plain email. - [ ] **Authority.** The provider's issuer address, from which OneiD reads the provider's discovery document. For Okta, for example `https://YOUR_OKTA_DOMAIN` or `https://YOUR_OKTA_DOMAIN/oauth2/default`, depending on which authorisation server you use. - [ ] **Client ID** of the application you registered. - [ ] **Client secret** of that application. Supplied as a secret. - [ ] **Scopes** OneiD should request, for example `openid profile email groups`. - [ ] **Group rules.** The required group, the administrators group, the group-to-role mappings and, if wanted, a prefix for unmapped groups. ## Map provider groups to roles OneiD reads the `groups` and `role` claims the provider sends and turns them into roles at each sign-in. The rules are described in [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/#from-groups-to-roles). An example plan: | Rule | Provider group | Result | |---|---|---| | Required group | `App-Users` | Only members can sign in. | | Administrators group | `OneiD-Admins` | Members get OneiD's `All` administrator role. | | Mapping | `Order-Viewers` | Role `OrderViewer` | | Mapping | `Order-Managers` | Role `OrderManager` | > **Warning:** Members of the administrators group can change every setting in OneiD. Use a small group that exists only for this purpose. If the provider sends no groups, users get no roles from it. If you set a required group and the provider sends no groups, nobody can sign in. Check the groups claim first. ## Step by step for Okta Okta's admin console changes over time, so the names below can differ by version. The settings in the table above are what matters. 1. In the Okta admin console, open **Applications** and choose **Create App Integration**. 2. Choose **OIDC - OpenID Connect** as the sign-in method and **Web Application** as the application type. 3. Give the integration a name, for example `OneiD`. 4. Under grant types, keep **Authorization Code**. Turn on the option to require PKCE if your Okta version shows it. 5. Set the sign-in redirect URI to `https://YOUR_ONEID/signin-oidc-external`. 6. Set the sign-out redirect URI to `https://YOUR_ONEID/signout-callback-oidc`. 7. Under assignments, choose who may use the integration, for example the `App-Users` group. 8. Save. Copy the **Client ID** and the **Client secret**. 9. Add a groups claim. In the integration's sign-on settings, find the OpenID Connect ID token section and set a **Groups claim** named `groups` with a filter, for example **Starts with** `App-` or **Matches regex** `.*`. If you use a custom authorisation server, add the `groups` claim there instead. 10. Note the authority. With the org authorisation server it is your Okta domain, for example `https://YOUR_OKTA_DOMAIN`. With a custom authorisation server it is the server's issuer, for example `https://YOUR_OKTA_DOMAIN/oauth2/default`. 11. Send the authority, client ID, client secret, scopes and group plan to your OneiD operator. > **Tip:** Use a filter that releases only the groups OneiD needs. Users in many groups otherwise get large tokens and many roles. > **Checkpoint:** After your operator has configured OneiD, open an application that signs in through OneiD. You land on Okta's sign-in page, and after sign-in you return to the application. With the `roles` scope, the ID token shows the mapped roles in `role`. ## Other providers Other OpenID Connect providers that support the authorization code flow with PKCE and publish a discovery document are set up the same way. Register OneiD with the settings in [Register OneiD at the provider](#register-oneid-at-the-provider) and give your operator the same details. Microsoft Entra ID is not available as a sign-in source. [Contact us](https://oltinid.com/contact/?topic=organisation) if you need Entra ID. ## Learn more - [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/) - [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/) - [Sign-out](https://oltinid.com/docs/guides/logout/) --- # Build with AI coding agents > Give an AI coding agent the OneiD documentation in a form it can read, and use ready-made prompts to add sign-in or protect an API. Source: https://oltinid.com/docs/ai/build-with-ai/ · Section: AI agents · All OneiD documentation: https://oltinid.com/llms.txt AI coding agents such as Claude Code, Cursor, GitHub Copilot and ChatGPT can add OneiD sign-in to an application or protect an API, as long as they work from the OneiD documentation rather than from general assumptions about identity providers. This page shows how to hand them the documentation and what they need from you. ## Give your agent the documentation The OneiD documentation is published in forms that agents read well. | Source | Address | Use it when | |---|---|---| | Index | `https://oltinid.com/llms.txt` | The agent should pick the pages it needs. | | Full documentation | `https://oltinid.com/llms-full.txt` | The agent should read everything at once, in one file. | | One page as Markdown | Add `.md` to the page address, without the trailing slash | You want to give the agent one specific page. | | OneiD skill file | `https://oltinid.com/oneid-skill.md` | Your agent supports skill or rules files. | | Connector specification | [Connector specification](https://oltinid.com/docs/ai/connector-specification/) | The agent builds a reusable integration for a framework or product. | For example, the Markdown version of the sign-out guide is `https://oltinid.com/docs/guides/logout.md`. Every documentation page also has a **Copy page** button. It copies the page as Markdown, so you can paste it into a chat. The **Open in AI** menu next to it starts a conversation about the page in Claude, ChatGPT, Microsoft Copilot, Perplexity, Grok or Mistral Le Chat, with the prompt already filled in. For Gemini it copies the prompt and opens Gemini for you to paste it. **Cursor** opens the prompt in the Cursor app, and **Copy prompt for a coding agent** gives you a prompt for Claude Code, GitHub Copilot, Windsurf, Codex or any other agent that works in your project. ### The OneiD skill file `https://oltinid.com/oneid-skill.md` is a single Markdown file that agents can load as a skill or rules file, so that OneiD's rules are in front of the agent while it works. Load it the way your agent loads skills or rules, for example by saving it in the project's rules or instructions folder or by attaching it to the conversation. ### What is not available > **Not supported:** There is no hosted documentation MCP server for OneiD and no AI assistant on oltinid.com. Use the files above. ## What the agent must ask you for An agent cannot register applications in OneiD. An administrator registers each application, and the agent needs the results. Before it writes code, a good agent asks for: - **The OneiD address**, for example `https://YOUR_ONEID`. The issuer is this address with a trailing slash. - **The client ID** of the registered application. - **The client type**: public (browser, mobile, desktop; no secret) or confidential (server-side; with a client secret). - **The client secret**, for a confidential client. The agent should read it from configuration or a secret store, never write it into code. - **The redirect URI**, exactly as registered, for example `https://app.example.com/callback`. - **The post-logout redirect URI**, if the application signs users out. - **The scopes** the client is allowed, including any API scopes. - **For an API**: the scope or scopes each endpoint requires. If you do not have these yet, ask your OneiD administrator. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/). ## Prompt: add OneiD sign-in to an application Copy this prompt, fill in the values in angle brackets, and give it to your agent together with the documentation. ```text Add sign-in with OneiD to this application. Read the OneiD documentation first: - https://oltinid.com/llms-full.txt (all documentation), or at least - https://oltinid.com/docs/ai/connector-specification.md - https://oltinid.com/docs/guides/authorization-code-pkce.md - https://oltinid.com/docs/guides/logout.md My OneiD settings: - OneiD address: - Client ID: - Client type: - Client secret: read it from the environment variable (confidential only) - Redirect URI (registered): - Post-logout redirect URI (registered): - Scopes: Requirements: - Use the framework's maintained OpenID Connect library. Configure it from the discovery document. - Authorization code flow with PKCE (S256), state and nonce. - The issuer is the OneiD address with a trailing slash. Compare it exactly. - Validate the ID token as the connector specification describes. - Key users by iss + sub, never by email. - Read roles from the "role" claim; it can be a string or an array. - Sign-out: clear the local session, then redirect to the end-session endpoint with id_token_hint and post_logout_redirect_uri. - Never log tokens or the client secret. - Do not use features OneiD does not support (see the connector specification, section 16). When you are done, list what you changed and walk the conformance checklist in section 17 of the connector specification. ``` ## Prompt: protect an API with OneiD ```text Protect this API with OneiD access tokens. Read the OneiD documentation first: - https://oltinid.com/docs/guides/protect-an-api.md - https://oltinid.com/docs/ai/connector-specification.md (section 12) My OneiD settings: - OneiD address: - Required scopes: - Roles (optional): Requirements: - Use a maintained JWT library for the framework. - Get issuer and jwks_uri from the discovery document. Cache the JWKS and re-fetch on an unknown kid. - Accept only RS256. Check iss exactly, with the trailing slash. Check exp with at most 60 seconds of clock skew. - Do NOT require an aud claim; OneiD access tokens have none. - Check the required scope in the space-separated "scope" claim on every endpoint. - Return 401 with WWW-Authenticate: Bearer error="invalid_token" for bad tokens, and 403 with error="insufficient_scope" when the scope is missing. - Never log tokens. Add tests for: a valid token, an expired token, a wrong issuer, a token signed with an unknown key, a missing scope, and a request without a token. ``` ## Check the agent's work Review what the agent produced before you ship it. In particular: - The issuer contains the trailing slash and the code does not require `aud` on access tokens. - PKCE uses `S256`. - No client secret is in the code or in a committed file. - No token is written to logs. - The application handles `invalid_grant` on refresh by signing the user in again. The [Security best practices](https://oltinid.com/docs/guides/security-best-practices/) checklist covers the rest. ## Learn more - [Connector specification](https://oltinid.com/docs/ai/connector-specification/) - [Quickstarts](https://oltinid.com/docs/quickstarts/) - [Register an application](https://oltinid.com/docs/get-started/register-an-application/) - [Security best practices](https://oltinid.com/docs/guides/security-best-practices/) --- # Connector specification > The normative rules a OneiD connector for any framework, product, gateway or SaaS must follow, with a conformance checklist and tests. Source: https://oltinid.com/docs/ai/connector-specification/ · Section: AI agents · All OneiD documentation: https://oltinid.com/llms.txt This page specifies how to build a OneiD connector: an integration that lets a web framework, a product, an API gateway or a SaaS product sign users in with OneiD, call APIs with OneiD tokens, or accept OneiD tokens. It is written so that a developer or an AI coding agent can build and test a connector from this page alone. Where this page and general OpenID Connect habits differ, this page wins. ## 1. Scope and terms ### 1.1 Requirement words The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, RECOMMENDED, MAY and OPTIONAL are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in capitals. ### 1.2 Modes A connector implements one or more of these modes: | Mode | What the connector does | Sections | |---|---|---| | Sign-in (relying party) | Signs users in to an application through OneiD and keeps an application session. | 2 to 11, 14, 15 | | Resource server | Accepts OneiD access tokens on an API and decides whether to serve the request. | 2, 3, 12, 14, 15 | | Machine to machine | Gets access tokens for a service, without a user, to call APIs. | 2, 3, 13, 14, 15 | ### 1.3 Terms | Term | Meaning | |---|---| | OneiD address | The base address of a OneiD deployment, for example `https://YOUR_ONEID`. | | Issuer | The OneiD address with a trailing slash, `https://YOUR_ONEID/`. It is the `issuer` in discovery and the `iss` in every token. | | Client | An application registered in OneiD. It has a client ID. | | Public client | A client without a secret: browser, mobile or desktop applications. PKCE is always required. | | Confidential client | A server-side client with one client secret. | | Deployment | One OneiD installation: one issuer, one database, one user store. There are no tenants inside a deployment. | ### 1.4 Facts the connector must be built around - Clients are registered by a OneiD administrator in the admin console or through the Admin API. There is no dynamic client registration. The connector MUST NOT try to register itself; it MUST let a person enter the values from section 2. - Each customer of OneiD has its own deployment and therefore its own issuer. A connector used by several organisations (for example a SaaS product) MUST support one configuration per organisation, each with its own issuer, client and caches, and MUST keep their users apart. - OneiD access tokens have no `aud` claim. - The issuer ends with a slash. ## 2. Configuration inputs The connector MUST accept these inputs. It MUST NOT hard-code any of them. | Input | Required | Rules | |---|---|---| | OneiD address | Yes | An https address. The connector MUST accept it with or without the trailing slash and derive the issuer by ensuring exactly one trailing slash. | | Client ID | Sign-in and machine-to-machine modes | As registered. | | Client type | Sign-in mode | `public` or `confidential`. | | Client secret | Confidential clients | MUST be read from a secret store or the environment, never from source code. MUST NOT be asked for or used by a public client. | | Redirect URI | Sign-in mode | MUST equal a registered redirect URI exactly. | | Post-logout redirect URI | Optional | MUST equal a registered post-logout redirect URI exactly. | | Scopes | Sign-in and machine-to-machine modes | Space-separated. Sign-in default: `openid profile email`. `openid` MUST be included for sign-in. | | Request refresh tokens | Optional | When on, the connector adds `offline_access`. Default off. | | Response mode | Optional | `query` (default) or `form_post`. | | Role claim name | Optional | Default `role`. | | Role mapping | Optional | A table from OneiD role names to the product's own roles. | | Required scopes | Resource-server mode | Per route or operation. | | Allowed issuers | Resource-server mode | The issuers whose tokens are accepted. | | Clock skew | Optional | Default 60 seconds. SHOULD NOT exceed 300 seconds. | | Token endpoint authentication | Optional | `client_secret_basic` (default) or `client_secret_post`. | The connector SHOULD check its configuration at start-up and report a clear error, for example when the redirect URI is not https and not a loopback address. ## 3. Discovery and keys ### 3.1 Discovery document 1. The connector MUST fetch the discovery document from `{issuer}.well-known/openid-configuration`, which is `https://YOUR_ONEID/.well-known/openid-configuration`. 2. The connector MUST check that the `issuer` in the document equals the issuer derived from the configured OneiD address, character for character. If not, it MUST stop and report a configuration error. 3. The connector MUST use the `issuer` value from the document, including the trailing slash, for every `iss` comparison. 4. The connector MUST take endpoint addresses from the document: `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `jwks_uri`, `end_session_endpoint`, `revocation_endpoint` and `introspection_endpoint`. It MUST NOT build them from the address. 5. The connector SHOULD cache the document, for example for 24 hours, and MUST NOT fetch it per request. For reference, a OneiD discovery document looks like this (abridged): ```json { "issuer": "https://YOUR_ONEID/", "authorization_endpoint": "https://YOUR_ONEID/connect/authorize", "token_endpoint": "https://YOUR_ONEID/connect/token", "userinfo_endpoint": "https://YOUR_ONEID/connect/userinfo", "jwks_uri": "https://YOUR_ONEID/.well-known/jwks", "end_session_endpoint": "https://YOUR_ONEID/connect/logout", "revocation_endpoint": "https://YOUR_ONEID/connect/revoke", "introspection_endpoint": "https://YOUR_ONEID/connect/introspect", "response_types_supported": ["code"], "response_modes_supported": ["form_post", "fragment", "query"], "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["RS256"], "code_challenge_methods_supported": ["plain", "S256"], "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "private_key_jwt"], "claims_parameter_supported": true, "request_parameter_supported": false, "request_uri_parameter_supported": false, "authorization_response_iss_parameter_supported": true, "acr_values_supported": ["urn:oltin:ac:pwd", "urn:oltin:ac:mfa", "urn:oltin:ac:external"] } ``` Some advertised values MUST NOT be used: `plain` (section 4), `fragment` (section 4) and `private_key_jwt` (section 5). ### 3.2 Signing keys (JWKS) 1. The connector MUST cache the JWKS from `jwks_uri`. 2. When a token's `kid` is not in the cache, the connector MUST fetch the JWKS again once and retry the lookup. It MUST limit these re-fetches, for example to one per minute per issuer, so that tokens with made-up `kid` values cannot cause a fetch per request. 3. If the `kid` is still unknown, the connector MUST reject the token. 4. The connector MUST only use keys from the issuer's own `jwks_uri`. OneiD signs with RSA keys. A new key is published in the JWKS before it starts signing; a retired key stays in the JWKS for 30 days. A connector that follows these rules keeps working through key rotation. ## 4. Sign-in ### 4.1 Authorisation request The connector MUST use the authorization code flow with PKCE. It MUST send the user's browser to `authorization_endpoint` with these parameters: | Parameter | Rule | |---|---| | `response_type` | MUST be `code`. | | `client_id` | The configured client ID. | | `redirect_uri` | The configured redirect URI, exactly as registered. | | `scope` | The configured scopes, including `openid`. | | `state` | MUST be sent. At least 128 bits from a cryptographically secure random generator, base64url-encoded. | | `nonce` | MUST be sent. At least 128 bits from a cryptographically secure random generator. | | `code_challenge` | MUST be sent. `BASE64URL(SHA256(code_verifier))` without padding. | | `code_challenge_method` | MUST be `S256`. The connector MUST NOT use `plain`. | | `response_mode` | OPTIONAL. `query` or `form_post`. The connector MUST NOT use `fragment`. | | `prompt` | OPTIONAL. `none`, `login` or `consent`. See section 8. | | `max_age` | OPTIONAL. See section 8. | | `id_token_hint` | OPTIONAL. If it names a different user than the one signed in, OneiD asks for a new sign-in. | | `claims` | OPTIONAL. OneiD reads claim names in the `id_token` and `userinfo` members and ignores `essential`, `value` and `values`. | The `code_verifier` MUST be 43 to 128 characters from the unreserved set, generated from at least 256 bits of secure randomness. The connector MUST store `state`, `nonce`, `code_verifier`, the redirect URI and the time of the request, bound to the user's browser (for example in the server-side session or in an encrypted, HttpOnly cookie). Each stored request MUST be usable once and SHOULD expire after a short time. The connector MUST NOT send `request`, `request_uri`, `resource` or `audience`. `request` and `request_uri` are answered with `request_not_supported` and `request_uri_not_supported`; OneiD does not support resource indicators or an audience parameter. The connector MUST NOT rely on `login_hint`, `ui_locales`, `display` or `acr_values`: OneiD accepts them and they have no effect. Example request: ```http GET /connect/authorize?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback &scope=openid%20profile%20email &state=Zk3q8xY2...&nonce=n0Q4r7... &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 HTTP/1.1 Host: YOUR_ONEID ``` ### 4.2 Redirect URI rules - OneiD compares redirect URIs exactly. There are no wildcards. - Redirect URIs MUST use https, except loopback addresses (`localhost`, `127.0.0.1`, `[::1]`), which MAY use http. - Private-use schemes in reverse-domain form (for example `com.example.app:/callback`) are allowed for public clients only. - Redirect URIs MUST NOT contain a fragment. If the `redirect_uri` is not registered or the `client_id` is unknown, OneiD does not redirect back. It shows an error page with `invalid_request`. The connector cannot detect this; it is a configuration error. ### 4.3 Callback With `response_mode=query`, the callback receives a GET request with the response in the query. With `form_post`, it receives a POST with a form body. The connector MUST handle the mode it requested. The connector MUST process the callback in this order: 1. Find the stored request by `state`. If `state` is missing, unknown, already used or expired, the connector MUST reject the response and MUST NOT call the token endpoint. 2. Check `iss`. OneiD includes `iss` in every authorisation response (RFC 9207). The connector MUST reject the response if `iss` is missing or does not equal the issuer exactly. 3. If the response contains `error`, handle it as in section 14 and stop. 4. Mark the stored request as used. 5. Exchange `code` at the token endpoint once (section 5). 6. Validate the ID token (section 6). 7. Create the application session (section 8) and redirect the user to the stored return address, which MUST be a local or allow-listed address. With `form_post`, the browser sends the callback as a cross-site POST. A cookie that holds the stored request MUST be `SameSite=None; Secure` to be sent with it, or the connector MUST keep the stored request elsewhere. ## 5. Token request and client authentication The connector MUST send a POST to `token_endpoint` with `Content-Type: application/x-www-form-urlencoded`. | Parameter | Value | |---|---| | `grant_type` | `authorization_code` | | `code` | The code from the callback | | `redirect_uri` | The same redirect URI as in the authorisation request | | `code_verifier` | The stored verifier | | `client_id` | Public clients, and confidential clients using `client_secret_post` | Client authentication: - Confidential clients SHOULD use `client_secret_basic`: an `Authorization: Basic` header with `base64(urlencode(client_id) + ":" + urlencode(client_secret))`. They MAY use `client_secret_post`. - Public clients MUST send `client_id` in the body and no secret. - The connector MUST NOT use `private_key_jwt`. Discovery lists it, but there is no way to register a client's public key with OneiD. Mutual TLS client authentication is not supported. ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=CODE_FROM_CALLBACK &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback &code_verifier=VERIFIER_FROM_STEP_4 ``` A successful response contains `access_token`, `token_type` (`Bearer`), `expires_in`, `id_token`, `scope`, and `refresh_token` when section 9 applies. - Authorisation codes work once and expire after 5 minutes. The connector MUST NOT retry a code exchange that failed with `invalid_grant`; it MUST start a new sign-in. - Authorisation codes and refresh tokens are opaque. The connector MUST NOT parse them. - In sign-in mode, the connector SHOULD NOT read the user's identity from the access token. It MUST use the ID token. ## 6. ID token validation The connector MUST validate every ID token in this order and reject it if any step fails: 1. The token is a signed JWT in compact form. 2. The header `alg` is `RS256`. Any other value, including `none`, is rejected. 3. The header `kid` names a key from the JWKS (section 3.2) and the signature verifies with it. 4. `iss` equals the issuer from discovery exactly, including the trailing slash. 5. `aud` equals the client ID, or is an array that contains only the client ID. 6. `exp` is later than the current time minus the clock skew. 7. `iat` is not later than the current time plus the clock skew. 8. `nonce` equals the stored nonce for this sign-in. 9. `sub` is present and not empty. 10. If `max_age` was sent, `auth_time` is present and the current time minus `auth_time` is no more than `max_age` plus the clock skew. 11. If the connector requires a level of authentication (section 8.4), `acr` or `amr` meets it. The connector MUST ignore claims it does not know, including private claims whose names start with `oi_`. The ID token is validated once, at sign-in. Its lifetime (20 minutes by default) is not the application session's lifetime. The connector MUST keep the raw ID token for sign-out (section 10). Example ID token payload after an MFA sign-in with `openid profile email roles`: ```json { "iss": "https://YOUR_ONEID/", "sub": "8d0c6a3e-2f4b-4c1e-9a77-1b2c3d4e5f60", "aud": "YOUR_CLIENT_ID", "exp": 1767226800, "iat": 1767225600, "nonce": "n0Q4r7...", "auth_time": 1767225590, "amr": ["pwd", "mfa"], "acr": "urn:oltin:ac:mfa", "name": "Jane Doe", "preferred_username": "jdoe", "email": "jane.doe@example.com", "email_verified": true, "idp": "local", "role": ["OrderViewer", "Support"] } ``` ## 7. User mapping ### 7.1 Key The connector MUST identify a user by the pair (`iss`, `sub`). It MUST NOT use `email`, `preferred_username` or `name` as the key. `sub` is opaque and stable; the connector MUST NOT parse it. ### 7.2 Profile fields | Claim | Scope | Product field | Notes | |---|---|---|---| | `sub` | `openid` | External user ID | With `iss`, the key. | | `name` | `profile` | Display name | | | `given_name`, `family_name` | `profile` | First and last name | | | `preferred_username` | `profile` | User name | Display only. | | `updated_at` | `profile` | Profile last changed | A number, seconds since 1970. | | `idp` | `profile` | Sign-in source | `local`, `ldap` or `oidc`. | | `email` | `email` | Email | Not a key. | | `email_verified` | `email` | Email verified | Boolean. The connector MUST NOT treat the address as verified unless this is `true`. | | `phone_number` | `phone` | Phone | | | `phone_number_verified` | `phone` | Phone verified | Boolean. | | `role` (or the configured name) | `roles` | Roles | See 7.3. | Users can untick optional scopes on the consent page. The connector MUST work when any claim other than `sub` is missing. It SHOULD update stored profile fields at each sign-in. The connector MAY call `userinfo_endpoint` with the access token (`GET` or `POST`, `Authorization: Bearer`). If it does, it MUST check that the `sub` in the response equals the ID token's `sub`. In userinfo, `role` is a JSON array. ### 7.3 Roles - In tokens, the role claim can be a single string (one role) or an array (several roles). The connector MUST accept both. - The connector SHOULD map OneiD role names to product roles through the configured role mapping, and SHOULD give no product role for an unmapped OneiD role. - OneiD's built-in administrator roles (`All`, `AllReadOnly`, `UserManager`, `UserManagerReadOnly`, `AuthorizationServerManager`, `AuthorizationServerManagerReadOnly`, `Auditer`) control OneiD itself. The connector MUST NOT give them meaning in the product unless the person configuring it maps them explicitly. ## 8. Sessions ### 8.1 Application session - After sign-in, the connector MUST create its own session (for example an HttpOnly, Secure cookie) and MUST issue a new session identifier at that moment. - The connector cannot read OneiD's own session; it can only end it by sending the user to the end-session endpoint (section 10). OneiD keeps a browser session for 8 hours, extended while the user is active. Within it, other applications sign the user in without a password prompt. - OneiD does not notify the connector when the user signs out elsewhere (no front-channel or back-channel logout). The application session lifetime MUST be configurable and SHOULD be short, for example no longer than OneiD's session. ### 8.2 Renewing a session without user interaction The connector MAY renew a session by repeating the authorisation request with `prompt=none` as a top-level redirect. It MUST NOT use a hidden iframe: OneiD pages cannot be framed. OneiD then either returns a code without showing anything, or returns `login_required`, `consent_required` or `interaction_required`. On any of these errors the connector MUST end the application session or start an interactive sign-in. It MUST NOT retry `prompt=none` in a loop. Refresh tokens (section 9) are the alternative. ### 8.3 Forcing a new sign-in - `prompt=login` (or `prompt=select_account`, which behaves the same; there is no account picker) forces the user to sign in again. - `max_age=N` forces a new sign-in when the user signed in more than N seconds ago. - After either, the connector SHOULD check `auth_time` in the new ID token. ### 8.4 Step-up and MFA OneiD does not enforce `acr_values`. Asking for `urn:oltin:ac:mfa` does not make OneiD require MFA. The connector MUST make the check itself: | `acr` | `amr` | Meaning | |---|---|---| | `urn:oltin:ac:pwd` | `["pwd"]` | Password only. | | `urn:oltin:ac:mfa` | `["pwd","mfa"]` | Password and an authenticator code in OneiD. | | `urn:oltin:ac:external` | `["external"]` | Signed in at an upstream OpenID Connect provider, whose own MFA policy applies. OneiD cannot tell whether MFA happened there. | When an action requires MFA and the ID token does not show it, the connector MUST refuse the action and tell the user why. Whether `urn:oltin:ac:external` counts SHOULD be a configuration choice. To make MFA certain, the OneiD administrator can make MFA mandatory for users. ### 8.5 Pending actions Users who must change their password or enrol MFA are sent to do that during interactive sign-in. The connector needs no handling beyond section 14. ## 9. Refresh tokens 1. The connector MUST request `offline_access` only when refresh tokens are switched on. The client must also have the refresh token grant; if no `refresh_token` is returned, the connector MUST work without one. 2. To refresh, the connector POSTs to `token_endpoint` with `grant_type=refresh_token` and `refresh_token`, authenticating as in section 5. 3. Every refresh returns a new refresh token. The connector MUST store the new one, replacing the old one, before it uses the new access token, and MUST NOT use the old one again. 4. The connector MUST allow at most one refresh at a time per refresh token. Concurrent requests that need a new access token MUST wait for the refresh in progress. A refresh token used again after a short grace period (about 30 seconds) is refused with `invalid_grant`, and OneiD then revokes the whole chain, including newer tokens. 5. The connector SHOULD refresh shortly before the access token expires, using `expires_in`, or once after an API answers 401. 6. Refresh tokens are not sliding. A chain ends when the original refresh token's lifetime runs out (14 days by default), however often it was used. 7. On `invalid_grant`, the connector MUST discard the stored tokens and require an interactive sign-in. Causes include an expired, revoked or reused token, and a user who was deleted or locked, or must change the password or enrol MFA. 8. If a refresh response contains an ID token, the connector MUST validate it as in section 6, except the `nonce` step, and MUST check that `sub` is unchanged. Refresh tokens MUST be stored server-side, or in the operating system's secure storage on mobile and desktop. They SHOULD be encrypted at rest. ## 10. Sign-out The connector MUST sign out in this order: 1. End the application session and delete stored tokens. 2. SHOULD revoke the refresh token, if any, with a POST to `revocation_endpoint` (`token`, `token_type_hint=refresh_token`, client authentication as in section 5). 3. Redirect the browser (GET, or POST with a form) to `end_session_endpoint` with: | Parameter | Rule | |---|---| | `id_token_hint` | SHOULD be sent: the raw ID token from sign-in. | | `post_logout_redirect_uri` | OPTIONAL. MUST equal a registered post-logout redirect URI exactly. | | `client_id` | SHOULD be sent. REQUIRED with `post_logout_redirect_uri` when no `id_token_hint` is sent. | | `state` | SHOULD be sent with `post_logout_redirect_uri`: a fresh random value. | 4. When the browser returns to `post_logout_redirect_uri`, the connector SHOULD check that `state` matches. ```http GET /connect/logout?id_token_hint=eyJhbGciOiJSUzI1NiIs... &post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2F &state=af0ifjsldkj HTTP/1.1 Host: YOUR_ONEID ``` Behaviour the connector must expect: - A `post_logout_redirect_uri` sent with neither `id_token_hint` nor `client_id` is refused with `invalid_request`. - An `id_token_hint` that OneiD did not issue is refused. - If the hint names a different user than the one signed in, OneiD asks the user to confirm. - Without `post_logout_redirect_uri`, the user ends on OneiD's signed-out page. - OneiD revokes the application's tokens for that user. If users sign in at an upstream provider, OneiD continues to the provider's sign-out before returning. - Other applications are not notified. The connector MUST NOT expose or wait for front-channel or back-channel logout endpoints. ## 11. Calling APIs - The connector MUST send the access token as `Authorization: Bearer `. It MUST NOT send the ID token to APIs. - API scopes (for example `orders.read`) are requested in the same `scope` parameter as identity scopes. One access token carries all granted scopes. - Access tokens have no `aud`. The connector MUST NOT send `resource` or `audience` parameters to get a token for a particular API; OneiD does not support resource indicators. - Access tokens live 1 hour by default. The connector SHOULD use `expires_in` from the token response rather than parse the token. - On a 401 from an API, the connector MAY refresh once (section 9) and retry the request once. ## 12. Resource-server mode ### 12.1 Validation For every request, the connector MUST: 1. Read the token from the `Authorization: Bearer` header. Tokens in query strings MUST NOT be accepted. 2. Parse the JWT header and check that `alg` is `RS256`. 3. Read `iss` from the unverified payload only to select a configuration. The issuer MUST be in the configured allowed issuers; otherwise reject. The connector MUST NOT fetch discovery or keys from an issuer that is not configured. 4. Verify the signature with that issuer's JWKS (section 3.2). 5. Check that `iss` equals the issuer exactly, with the trailing slash. 6. Check `exp`, and `nbf` if present, with the configured clock skew. 7. Check that the space-separated `scope` claim contains the scope the route requires. 8. Check the role claim if the route requires a role. The connector MUST NOT require an `aud` claim. Because there is no audience, each API SHOULD have its own scopes so that a token issued for one API's scope is not accepted by another API. ### 12.2 Access token claims | Claim | Meaning | |---|---| | `iss` | The issuer, with the trailing slash. | | `sub` | The user's ID, or the client ID for client credentials tokens. | | `exp`, `iat` | Expiry and issue time. | | `scope` | Granted scopes, separated by spaces. | | `client_id` | The client the token was issued to. | | `role` | Roles, when `roles` was granted. String or array. | Scope-released profile, email and phone claims can also be present. A token ID claim may be present. The connector MUST ignore unknown claims. Access tokens are signed, not encrypted. ### 12.3 Responses | Situation | Status | `WWW-Authenticate` | |---|---|---| | No token | 401 | `Bearer` | | Invalid, expired or untrusted token | 401 | `Bearer error="invalid_token"` | | Required scope missing | 403 | `Bearer error="insufficient_scope", scope=""` | | Role missing | 403 | Not required | ### 12.4 Revocation OneiD revokes tokens in its store, for example when a user signs out or an administrator locks the user. Local validation does not see this before the token expires. Introspection (`introspection_endpoint`) requires a confidential client and only answers for tokens issued to that same client, so it is not suited to general APIs. The RECOMMENDED pattern is local validation with short access token lifetimes. ## 13. Machine-to-machine mode 1. The client MUST be confidential and have the client credentials grant. Public clients cannot use it. 2. The connector POSTs to `token_endpoint` with `grant_type=client_credentials` and `scope`, authenticating with `client_secret_basic` (or `client_secret_post`). 3. The response contains `access_token`, `token_type`, `expires_in` and `scope`. There is no refresh token and no ID token. 4. The connector MUST cache the access token and reuse it until shortly before `expires_in` runs out. It MUST NOT request a token per API call, and SHOULD allow only one token request at a time per client and scope set. 5. The token's `sub` is the client ID and `name` is the client's display name. ```http POST /connect/token HTTP/1.1 Host: YOUR_ONEID Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&scope=orders.read ``` ## 14. Errors to handle | Where | Error | Cause | Connector behaviour | |---|---|---|---| | Callback | `access_denied` | The user refused consent. | Show that access was declined and offer to try again. Do not retry automatically. | | Callback | `login_required`, `consent_required`, `interaction_required` | `prompt=none` needed interaction. | Start an interactive sign-in, or end the session. | | Callback | `invalid_scope` | A scope is not allowed for the client. | Configuration error. Show a generic error and log the scope list. | | Callback | `unauthorized_client` | The client is disabled. | Configuration error. Show a generic error. | | Callback | `request_not_supported`, `request_uri_not_supported` | A request object was sent. | Connector defect. Do not send `request` or `request_uri`. | | Callback | `invalid_request`, other errors | A malformed request. | Show a generic error and log `error` and `error_description`. | | Callback | `state` or `iss` check failed | Forged or stale response. | Reject. Do not call the token endpoint. Offer a new sign-in. | | OneiD error page | `invalid_request` shown by OneiD | Unknown client ID or unregistered redirect URI. | Not visible to the connector. Check configuration. | | Token endpoint | `invalid_grant` | Code or refresh token expired, used or revoked; PKCE mismatch; user deleted, locked, must change the password or must enrol MFA. | Do not retry with the same value. Start a new sign-in. | | Token endpoint | `invalid_client` | Wrong secret, expired secret ("The client secret has expired."), or client disabled. | Configuration error. Alert the operator. Do not retry in a loop. | | Token endpoint | `invalid_scope` | A scope is not allowed for the client. | Configuration error. | | Logout | `unauthorized_client` | The client is disabled. | Shown by OneiD; the connector does not see it. Local sign-out is already done. Check configuration. | | Any endpoint | HTTP 429, `slow_down` | Rate limit reached. | Wait the number of seconds in `Retry-After`, then retry at most once. Never retry in a tight loop. | | Userinfo, APIs | HTTP 401 | Access token expired or revoked. | Refresh once (section 9), then sign in again. | | Any endpoint | Network error or HTTP 5xx | Temporary failure. | Retry back-channel calls with exponential back-off. Do not retry a code exchange. | A 429 response looks like this: ```http HTTP/1.1 429 Too Many Requests Retry-After: 12 Content-Type: application/json {"error":"slow_down","error_description":"Too many requests. Try again in 12 seconds."} ``` ## 15. Security requirements The connector MUST: - use TLS for every call to OneiD and MUST NOT disable certificate validation; - never write tokens, authorisation codes, `code_verifier` values or client secrets to logs, URLs it builds, error pages, analytics or crash reports; - keep client secrets out of browser bundles, mobile apps and desktop apps; - protect the callback against cross-site request forgery with `state`, bound to the browser and used once; - check `iss` in the authorisation response and in every token; - set session cookies `HttpOnly` and `Secure`, with `SameSite=Lax` or stricter except where section 4.3 requires `None`; - issue a new session identifier at sign-in; - validate any return address after sign-in or sign-out against local or allow-listed addresses; - keep tokens server-side in server-side applications, in memory in browser applications, and in secure storage on devices; - keep configurations for different issuers fully separate, including caches and user records. For browser-based connectors: the token, userinfo and revocation endpoints accept cross-origin requests only from origins an administrator registered for an enabled client. Introspection never accepts them. The connector MUST NOT rely on cookies in cross-origin calls. ## 16. Not supported by OneiD The connector MUST NOT depend on any of the following: - Dynamic client registration, or any self-service registration of clients or users. - Implicit and hybrid flows; any `response_type` other than `code`. - The `fragment` response mode (listed in discovery, not recommended). - PKCE `plain`. - The resource owner password credentials grant, device authorization grant, token exchange and CIBA. - Pushed authorisation requests (PAR) and request objects (JAR, `request`, `request_uri`). - DPoP, mutual TLS client authentication and certificate-bound tokens. - `private_key_jwt` client authentication. - Resource indicators (`resource`) and an `audience` parameter; an `aud` claim in access tokens. - Front-channel logout, back-channel logout and the session management iframe (`check_session_iframe`); the `sid` claim. - Enforcement of `login_hint`, `ui_locales`, `display` and `acr_values`. - `essential`, `value` and `values` in the `claims` parameter. - The `address` scope. - An account picker for `prompt=select_account`. - SAML, WS-Federation and SCIM. - Outgoing event webhooks from OneiD. - Social-login buttons, passkeys or WebAuthn, and SMS codes. ## 17. Conformance checklist Tick every item that applies to the modes the connector implements. Each test SHOULD be automated. For unit tests, run a local fake OneiD: serve the discovery document from section 3.1 and a JWKS from a test RSA key, and sign test tokens with it. For integration tests, ask your OneiD administrator to register a test client. ### 17.1 Configuration and discovery - [ ] All inputs from section 2 are configurable; nothing is hard-coded. - [ ] The issuer is derived with exactly one trailing slash and compared exactly with discovery. - [ ] Endpoints come from discovery. - [ ] Discovery and JWKS are cached; an unknown `kid` causes one rate-limited re-fetch. - [ ] Several issuers can be configured side by side (multi-organisation connectors). ### 17.2 Sign-in - [ ] Authorization code flow with PKCE `S256`, `state` and `nonce` on every request. - [ ] The redirect URI is sent exactly as registered. - [ ] `iss` in the authorisation response is required and checked. - [ ] The ID token is validated with every step of section 6. - [ ] Users are keyed by (`iss`, `sub`). - [ ] Missing optional claims do not break sign-in. - [ ] The role claim is accepted as a string or an array. - [ ] `amr`/`acr` checks are applied where the product requires MFA. ### 17.3 Tokens and sign-out - [ ] Confidential clients use `client_secret_basic` by default; public clients send no secret. - [ ] Refresh tokens are rotated, stored and never reused; refreshes are serialised. - [ ] `invalid_grant` on refresh ends the session. - [ ] Sign-out clears the local session, revokes the refresh token and redirects with `id_token_hint`, `post_logout_redirect_uri` and `state`. ### 17.4 Resource server and machine to machine - [ ] `aud` is not required; `iss`, signature, `exp` and scope are. - [ ] Only configured issuers are accepted. - [ ] 401 and 403 responses carry the `WWW-Authenticate` values from section 12.3. - [ ] Client credentials tokens are cached until shortly before expiry. ### 17.5 Security - [ ] No token, code, verifier or secret appears in logs, URLs or error pages. - [ ] Session cookies are `HttpOnly` and `Secure`; a new session ID is issued at sign-in. - [ ] Return addresses are validated. ### 17.6 Tests | Test | Setup | Expected result | |---|---|---| | Happy-path sign-in | Complete a sign-in. | Session created for (`iss`, `sub`); profile fields filled. | | Wrong `state` | Change `state` on the callback. | Rejected; no token request made. | | Replayed callback | Send the same callback twice. | Second one rejected. | | Missing or wrong `iss` in the callback | Remove `iss`, or set another issuer. | Rejected. | | Nonce mismatch | ID token with another `nonce`. | Rejected. | | Expired ID token | `exp` earlier than now minus the skew. | Rejected. | | Wrong algorithm | ID token with `alg` `none` or `HS256`. | Rejected. | | Wrong audience | ID token `aud` is another client ID. | Rejected. | | Issuer without slash | Token `iss` is `https://YOUR_ONEID`. | Rejected. | | Key rotation | Token signed with a new key that is added to the JWKS after the first fetch. | One JWKS re-fetch, then accepted. | | Unknown key flood | Many tokens with random `kid` values. | Rejected; JWKS re-fetches stay within the limit. | | Refresh rotation | Refresh twice. | Each response's new refresh token is stored; the old one is never sent again. | | Concurrent refresh | Two requests need a refresh at the same time. | One refresh request reaches OneiD. | | Refresh failure | Token endpoint answers `invalid_grant`. | Tokens discarded; user must sign in. | | Sign-out round trip | Sign out. | Local session gone; redirect carries `id_token_hint`, `post_logout_redirect_uri` and `state`; `state` checked on return. | | Consent refused | Callback with `access_denied`. | Message shown; no automatic retry. | | Silent sign-in fails | Callback with `login_required` after `prompt=none`. | Interactive sign-in or session end; no loop. | | Rate limited | Token endpoint answers 429 with `Retry-After: 2`. | Waits at least 2 seconds before one retry. | | API without token | Call a protected route without a token. | 401 with `WWW-Authenticate: Bearer`. | | API with expired token | Expired access token. | 401 with `error="invalid_token"`. | | API without scope | Valid token without the route's scope. | 403 with `error="insufficient_scope"`. | | API token without `aud` | Valid token with the scope and no `aud`. | Accepted. | | API with unknown issuer | Token from an issuer that is not configured. | Rejected without any network call to that issuer. | | Client credentials caching | Make several API calls. | One token request until shortly before expiry. | | Log hygiene | Run all tests with debug logging. | No token, code, verifier or secret in the logs. | ## Learn more - [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/) - [Protect an API (validate access tokens)](https://oltinid.com/docs/guides/protect-an-api/) - [Standards support](https://oltinid.com/docs/reference/standards-support/) --- # Run OneiD in your own environment > Compare OneiD hosted by us with running it yourself, and see what your own environment needs for running, scaling and backups. Source: https://oltinid.com/docs/operate/overview/ · Section: Operate OneiD · All OneiD documentation: https://oltinid.com/llms.txt You can use OneiD hosted by us or run it in your own environment. Either way, each customer has its own OneiD deployment: one OneiD address, one database and one sign-in source. This page gives an overview of running OneiD yourself. We provide the installation guide and support. ## Hosted by OneiD or your own environment | | Hosted by OneiD | In your own environment | |---|---|---| | Who runs OneiD | The OneiD team | You | | OneiD address and data store | Your own, provided by us | Your own | | Software and support | The OneiD team | We supply the software and support | | Containers, database, network | The OneiD team | You | | Backups | Ask us | You | For applications, there is no difference: the endpoints, flows and tokens are the same. ## What your environment needs - **Container runtime.** OneiD runs as Docker containers. - **Reverse proxy.** A reverse proxy in front of OneiD that terminates TLS. - **PostgreSQL.** OneiD keeps users, clients, signing keys, tokens and its other state in a PostgreSQL database. - **An https OneiD address.** The address becomes the issuer in every token. Choose it carefully: changing it later changes the issuer that every application and API checks. - **A certificate to protect OneiD's key ring.** OneiD encrypts signing keys, MFA secrets and cookies at rest with keys that this certificate protects. - **Email (SMTP with TLS).** OneiD sends password reset codes, user name reminders and MFA notifications by email. - **An initial administrator password**, set at the first start. - **Your sign-in source.** OneiD accounts, your LDAP or Active Directory server, or an OpenID Connect provider. See [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/). ## Health endpoints | Endpoint | Use | |---|---| | `/health/live` | Liveness: the process is running. | | `/health/ready` | Readiness: OneiD is ready to serve requests. | Both return a status only, with no details. Use them for your load balancer and container orchestrator checks. ## Running several instances You can run several OneiD instances behind a load balancer. They share their state through the database, so a user can start a sign-in on one instance and finish it on another. ## Signing-key rotation OneiD signs tokens with RSA keys and publishes the public keys in its JWKS. 1. An administrator rotates the key in the admin console and chooses an activation time. 2. OneiD publishes the new key in the JWKS early, before it signs anything with it, so that applications and APIs can fetch it in advance. When you run several instances, ask us for the restart sequence. 3. The new key starts signing once the activation time has passed and the instances have restarted. Plan a restart of every instance after the activation time. 4. The retired key stays in the JWKS for 30 days, so tokens it signed still validate. Applications and APIs that cache the JWKS and re-fetch it on an unknown `kid` need no change. ## Backups In your own environment, backups are yours. Back up the PostgreSQL database regularly and test restores. Keep the key-ring certificate with your backups, stored separately and securely. The signing keys and MFA secrets in the database are encrypted with keys that the certificate protects, so a database backup is of little use without it. ## Email OneiD needs a working SMTP server with TLS. Without it, users of OneiD accounts cannot reset passwords or get user name reminders, and do not receive MFA notifications. Test email delivery before you go live. ## Support We provide the installation guide and support for running OneiD in your own environment. [Contact us](https://oltinid.com/contact/?topic=organisation) through the contact page. ## Learn more - [How OneiD works](https://oltinid.com/docs/get-started/overview/) - [Where users come from](https://oltinid.com/docs/sign-in-sources/overview/) - [Automate with the Admin API](https://oltinid.com/docs/operate/admin-api/) - [Security best practices](https://oltinid.com/docs/guides/security-best-practices/) --- # Automate with the Admin API > Get an overview of the OneiD Admin API, how to authenticate to it, what it manages and which administrator roles it respects. Source: https://oltinid.com/docs/operate/admin-api/ · Section: Operate OneiD · All OneiD documentation: https://oltinid.com/llms.txt Everything you can do in the OneiD admin console you can also do through the Admin API. The console itself uses the same API. This page gives an overview; the full description of every operation is in the OpenAPI description on your OneiD instance. ## Base path All Admin API operations live under: ```text https://YOUR_ONEID/api/admin/v1 ``` ## Authentication The Admin API accepts a bearer access token issued by your OneiD deployment. - The token must belong to a **user who holds an administrator role** (see [Administrator roles](#administrator-roles)). - The token must carry the scope **`admin_api`**, or **`admin_api_readonly`** for read-only access. - Bearer tokens are accepted only under `/api`. To get such a token, an administrator with the `All` role allows `admin_api` or `admin_api_readonly` for the client you will use. Only the `All` role can allow these privileged scopes. Your tool then signs the administrator in with the [authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) and requests the scope. OneiD removes these scopes at sign-in for users without an administrator role, so a token for an ordinary user never carries them. > **Warning:** Treat a token with `admin_api` like an administrator password. Do not log it, keep it only as long as the task needs, and use `admin_api_readonly` when your tool only reads. What the token can do also depends on the user's administrator role. A user with a read-only role gets read access only, even with `admin_api`. ## Resources | Resource | What you can do | |---|---| | Clients | Register, change, enable or disable applications. | | Client secrets | Set, replace or disable a confidential client's secret. | | Scopes | Create and manage API scopes. | | Identity resources | Manage identity scopes. | | Claim types | Manage the claim types OneiD knows. | | Users | Create and change users; assign roles and claims; set account requirements such as a password change or MFA; unlock; reset MFA; reset the password. | | Roles | Create and manage roles, their users and their claims. | | Sessions | List active sessions and revoke tokens and authorisations. | | Audit | Read the audit log. | | Signing keys | List and rotate signing keys. | | Import and export | Export and import client definitions. | ## Example: list clients ```http GET /api/admin/v1/clients?page=1&pageSize=20 HTTP/1.1 Host: YOUR_ONEID Authorization: Bearer ADMIN_ACCESS_TOKEN Accept: application/json ``` The response is a page of clients (abridged): ```json { "items": [ { "clientId": "orders-web", "clientName": "Orders", "clientType": "confidential", "enabled": true, "grantTypes": ["authorization_code", "refresh_token"] } ], "totalCount": 1, "page": 1, "pageSize": 20 } ``` ## OpenAPI description Your OneiD instance publishes an OpenAPI description of the Admin API at `https://YOUR_ONEID/swagger`. It is available to signed-in administrators. Use it for the request and response details of every operation. ## Administrator roles OneiD has built-in administrator roles. They decide what a user can do in the admin console and through the Admin API. | Role | Meaning | |---|---| | `All` | Full administration. The only role that can manage privileged roles and allow privileged scopes for clients. | | `AllReadOnly` | Read everything, change nothing. | | `UserManager` | Manage users and roles. | | `UserManagerReadOnly` | Read users and roles. | | `AuthorizationServerManager` | Manage clients and scopes. | | `AuthorizationServerManagerReadOnly` | Read clients and scopes. | | `Auditer` | Read the audit log. | Give each administrator the narrowest role that fits their job. ## Help-desk webhooks OneiD accepts two incoming webhooks, so that a help-desk tool can act on a user's request: | Webhook | Effect | |---|---| | `POST /api/webhook/v1/reset-password` | Starts a password reset for a user, who receives a reset email. | | `POST /api/webhook/v1/reset-mfa` | Resets a user's MFA. | The help-desk tool authenticates with a client credentials token that carries the `admin_console_webhooks` scope. Only an administrator with the `All` role can allow that scope for a client. The webhooks cannot target administrator accounts. Ask your OneiD operator for the request format. > **Not supported:** OneiD does not send outgoing event webhooks. To follow changes, read the audit log through the Admin API. ## Learn more - [Register an application](https://oltinid.com/docs/get-started/register-an-application/) - [Client settings](https://oltinid.com/docs/reference/client-settings/) - [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/) - [Client credentials for services](https://oltinid.com/docs/guides/client-credentials/)