Connector specification
The normative rules a OneiD connector for any framework, product, gateway or SaaS must follow, with a conformance checklist and tests.
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
audclaim. - 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
- The connector MUST fetch the discovery document from
{issuer}.well-known/openid-configuration, which ishttps://YOUR_ONEID/.well-known/openid-configuration. - The connector MUST check that the
issuerin the document equals the issuer derived from the configured OneiD address, character for character. If not, it MUST stop and report a configuration error. - The connector MUST use the
issuervalue from the document, including the trailing slash, for everyisscomparison. - The connector MUST take endpoint addresses from the document:
authorization_endpoint,token_endpoint,userinfo_endpoint,jwks_uri,end_session_endpoint,revocation_endpointandintrospection_endpoint. It MUST NOT build them from the address. - 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):
{
"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)
- The connector MUST cache the JWKS from
jwks_uri. - When a token’s
kidis 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-upkidvalues cannot cause a fetch per request. - If the
kidis still unknown, the connector MUST reject the token. - 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:
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:
- Find the stored request by
state. Ifstateis missing, unknown, already used or expired, the connector MUST reject the response and MUST NOT call the token endpoint. - Check
iss. OneiD includesissin every authorisation response (RFC 9207). The connector MUST reject the response ifissis missing or does not equal the issuer exactly. - If the response contains
error, handle it as in section 14 and stop. - Mark the stored request as used.
- Exchange
codeat the token endpoint once (section 5). - Validate the ID token (section 6).
- 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: anAuthorization: Basicheader withbase64(urlencode(client_id) + ":" + urlencode(client_secret)). They MAY useclient_secret_post. - Public clients MUST send
client_idin 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.
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:
- The token is a signed JWT in compact form.
- The header
algisRS256. Any other value, includingnone, is rejected. - The header
kidnames a key from the JWKS (section 3.2) and the signature verifies with it. issequals the issuer from discovery exactly, including the trailing slash.audequals the client ID, or is an array that contains only the client ID.expis later than the current time minus the clock skew.iatis not later than the current time plus the clock skew.nonceequals the stored nonce for this sign-in.subis present and not empty.- If
max_agewas sent,auth_timeis present and the current time minusauth_timeis no more thanmax_ageplus the clock skew. - If the connector requires a level of authentication (section 8.4),
acroramrmeets 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:
{
"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 |
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(orprompt=select_account, which behaves the same; there is no account picker) forces the user to sign in again.max_age=Nforces a new sign-in when the user signed in more than N seconds ago.- After either, the connector SHOULD check
auth_timein 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
- The connector MUST request
offline_accessonly when refresh tokens are switched on. The client must also have the refresh token grant; if norefresh_tokenis returned, the connector MUST work without one. - To refresh, the connector POSTs to
token_endpointwithgrant_type=refresh_tokenandrefresh_token, authenticating as in section 5. - 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.
- 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. - The connector SHOULD refresh shortly before the access token expires, using
expires_in, or once after an API answers 401. - 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.
- 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. - If a refresh response contains an ID token, the connector MUST validate it as in section 6, except the
noncestep, and MUST check thatsubis 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:
- End the application session and delete stored tokens.
- 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). - Redirect the browser (GET, or POST with a form) to
end_session_endpointwith:
| 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. |
- When the browser returns to
post_logout_redirect_uri, the connector SHOULD check thatstatematches.
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_urisent with neitherid_token_hintnorclient_idis refused withinvalid_request. - An
id_token_hintthat 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 <access_token>. It MUST NOT send the ID token to APIs. - API scopes (for example
orders.read) are requested in the samescopeparameter as identity scopes. One access token carries all granted scopes. - Access tokens have no
aud. The connector MUST NOT sendresourceoraudienceparameters 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_infrom 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:
- Read the token from the
Authorization: Bearerheader. Tokens in query strings MUST NOT be accepted. - Parse the JWT header and check that
algisRS256. - Read
issfrom 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. - Verify the signature with that issuer’s JWKS (section 3.2).
- Check that
issequals the issuer exactly, with the trailing slash. - Check
exp, andnbfif present, with the configured clock skew. - Check that the space-separated
scopeclaim contains the scope the route requires. - 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="<required 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
- The client MUST be confidential and have the client credentials grant. Public clients cannot use it.
- The connector POSTs to
token_endpointwithgrant_type=client_credentialsandscope, authenticating withclient_secret_basic(orclient_secret_post). - The response contains
access_token,token_type,expires_inandscope. There is no refresh token and no ID token. - The connector MUST cache the access token and reuse it until shortly before
expires_inruns 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. - The token’s
subis the client ID andnameis the client’s display name.
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/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_verifiervalues 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
issin the authorisation response and in every token; - set session cookies
HttpOnlyandSecure, withSameSite=Laxor stricter except where section 4.3 requiresNone; - 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_typeother thancode. - The
fragmentresponse 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_jwtclient authentication.- Resource indicators (
resource) and anaudienceparameter; anaudclaim in access tokens. - Front-channel logout, back-channel logout and the session management iframe (
check_session_iframe); thesidclaim. - Enforcement of
login_hint,ui_locales,displayandacr_values. essential,valueandvaluesin theclaimsparameter.- The
addressscope. - 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
kidcauses 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,stateandnonceon every request. - The redirect URI is sent exactly as registered.
issin 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/acrchecks are applied where the product requires MFA.
17.3 Tokens and sign-out
- Confidential clients use
client_secret_basicby default; public clients send no secret. - Refresh tokens are rotated, stored and never reused; refreshes are serialised.
invalid_granton refresh ends the session.- Sign-out clears the local session, revokes the refresh token and redirects with
id_token_hint,post_logout_redirect_uriandstate.
17.4 Resource server and machine to machine
audis not required;iss, signature,expand scope are.- Only configured issuers are accepted.
- 401 and 403 responses carry the
WWW-Authenticatevalues 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
HttpOnlyandSecure; 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. |