Connector specification

The normative rules a OneiD connector for any framework, product, gateway or SaaS must follow, with a conformance checklist and tests.

View as Markdown

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):

{
  "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:

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.
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:

{
  "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.
  1. When the browser returns to post_logout_redirect_uri, the connector SHOULD check that state matches.
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 <access_token>. 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="<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

  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.
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_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