Web clients and API clients in Access
Register an app that signs users in, a backend that calls the platform's service APIs, and a pipeline that configures Access itself. Each needs a different kind of client.
Pick the right kind of client
Access has two kinds of clients, and they are not interchangeable. Pick by what the caller does.
| You want to | Create | Where | It authenticates with |
|---|---|---|---|
| Sign users in to a web, single-page or mobile app | A web client with the Authorization code grant | Access admin → Web clients | Authorization code + PKCE, then the token endpoint |
| Call service APIs (Identity Store, Password Store, FIDO, Consent …) from a backend, with no user | A web client with the Client credentials grant | Access admin → Web clients | POST https://{tenant-host}/oauth/v1/token, then a bearer token |
| Configure Access: web clients, API clients, scopes, IdPs, SAML service providers, web hooks, assurance levels | An API client | Access admin → API clients | Its own credentials on every call. No token request. |
A web client sent to Access's configuration API gets 401 invalid_credentials. An API client's credentials don't get you a token for the service APIs. If a call fails with 401 and the secret looks right, check that you used the right kind of client.
Create a web client for an app
- 1
Open Access admin → Web clients and add a client
Give it a Name (only shown to admins) and a Client ID. Let the console generate the ID unless the app needs a fixed one.
- 2
Choose the authentication method
Client secret or Private key JWT for apps with a backend that can keep a secret. Public for single-page and mobile apps, which can't. See the table below.
- 3
Set the grant type to Authorization code
Leave Implicit and Resource owner password credentials off. Add Device code only for devices without a browser or keyboard.
- 4
Choose the access token format
JWT if your APIs validate tokens themselves. Opaque if they call Access to introspect them.
- 5
Add the redirect URIs
Redirect URL is required for the Authorization code grant. Add other environments under Additional redirect URLs. Access compares them exactly, so include the path.
- 6
Set the lifetimes
Access grant validity (how long the code lives, e.g. 30 seconds) and Access token validity (e.g. 900 seconds) are both required. Turn on Issue refresh token only if the app needs to stay signed in without a browser round trip.
- 7
Assign scopes
Put
openidand the scopes the app always needs under Default scopes. Put scopes the app sometimes asks for under Additional scopes. See Scopes and claims. - 8
Choose the identity provider
Select the Identity provider that signs the users in: an Identity Orchestration journey, the Identity Broker, or another provider set up in Access. This decides which sign-in the user sees.
- 9
Complete the OpenID Connect settings
Because
openidis among the scopes, Access asks for the relying party settings: ID token validity, Post Logout Redirect URL, Front-Channel Logout URL, and Delete access/refresh tokens on logout (turn it on). - 10
Save and hand over the values
Give the app team the client ID, the secret or how to register its key, and the discovery URL
https://{tenant-host}/oauth/.well-known/openid-configuration.
Skip Consent page hides the consent prompt. Use it for your own first-party apps. Leave consent on for third-party apps that get user data.
Grant types and client authentication
| Grant type | Use it for |
|---|---|
| Authorization code | Every app that signs users in. The only grant a public client may use, always with PKCE. |
| Client credentials | Machine-to-machine calls with no user. See the next section. |
| Device code | TVs, consoles and other devices with no browser or limited input. Set the Device Verification URL too. |
| Implicit | Don't. Tokens travel in the browser URL. Use Authorization code with PKCE. |
| Resource owner password credentials | Don't. The app sees the user's password and MFA can't work. Only for migrating legacy apps. |
| Authentication method | How the client proves who it is | Use when |
|---|---|---|
| Client secret | Client ID and secret in an HTTP Basic header (preferred) or in the form body. | A server-side app. Rotate the secret like a password. |
| Private key JWT | The client signs a short JWT with its private key (iss and sub = client ID, aud, exp, jti). Access holds only the public key, from a JWKS URI or as a static key. Keys: EC P-256 or stronger, or RSA 2048–4096. | You want no shared secret. Prefer a JWKS URI so keys can rotate. |
| Public | No client authentication. PKCE protects the code instead. | Single-page and mobile apps, which can't keep a secret. |
Use PKCE with the S256 method for every authorization code client, public or not. It costs nothing and stops stolen codes from being redeemed.
Token lifetimes
| Setting | What it controls | Starting point |
|---|---|---|
| Access grant validity | Seconds the app has to exchange an authorization code. Required with Authorization code. | 30 |
| Access token validity | Seconds an access token is valid. Always required, for user and machine clients. | 300–900 for user apps; up to 3600 for machine clients |
| Refresh token validity | Seconds from the first access token until the refresh token stops working. Empty means it never expires. | Match your app's "stay signed in" promise; never leave it empty for public clients |
| ID token validity | The ID token's exp. Your app checks it once, at sign-in. | Same as the access token |
- Short access tokens limit the damage of a leaked token. Refresh tokens keep the user signed in meanwhile.
- Keep Revoke all active refresh tokens when an old refresh token is used on. A replayed refresh token then revokes the whole chain.
- The Access single sign-on session has its own lifetime, separate from the tokens. See How sign-in works.
Create a machine-to-machine web client
A backend, a pipeline or an Identity Orchestration flow that calls the service APIs uses a web client with the Client credentials grant. Two settings decide what its token can do:
| Setting | Decides | When it's wrong |
|---|---|---|
| Default audiences | Which services accept the token (its aud claim). Add one audience per service, e.g. identity-store. | The service answers 401. |
| Default scopes | What the client may do in those services (its scope claim), e.g. read identities. | The service answers 403 although the audience is there. |
- 1
Add a web client
Access admin → Web clients. Authentication method Client secret or Private key JWT. Grant type Client credentials only.
- 2
Set Access token validity
It is required even though there is no user.
- 3
Add the audiences
Add each service the client calls to Default audiences. Each API page lists the audience it expects.
- 4
Add the scopes
Add the scopes the client always needs to Default scopes. Use the scope builder to pick the scopes for the operations you call. Grant the least you need.
- 5
Check the length
Join the Default scopes with spaces and count: keep the string within 1024 characters.
- 6
Request a token and test
Call the token endpoint, then one read operation of each service.
curl -s https://{tenant-host}/oauth/v1/token \
-d grant_type=client_credentials \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET
# Service APIs live under the tenant host
curl -s https://{tenant-host}/identity-store/api/v1/... \
-H "Authorization: Bearer $ACCESS_TOKEN"If the Default scopes, joined with spaces, pass about 1024 characters, the token endpoint answers HTTP 500 to that client. Access still accepts the client change, so the 500 looks like an outage. Move rarely used scopes to Additional scopes, or split the work over two clients.
Additional scopes are issued only when the token request names them with scope= (space-separated). A request that names scopes gets only those, so one client can hold more scopes than fit in one token.
| Reply | Means | Fix |
|---|---|---|
Token endpoint: 401 invalid_client | Unknown client ID or wrong secret. | Check the client; set the secret again. |
Token endpoint: 400 unauthorized_client | The client doesn't have the Client credentials grant. | Add the grant type. |
Token endpoint: 400 invalid_request with audience= | The client doesn't have that audience. | Add it to Default audiences. Sending audience=<name> is a quick test. |
| Token endpoint: 500 | Default scopes too long. | Trim to 1024 characters. |
| Service: 401 | The token lacks the service's audience. | Add it to Default audiences. |
| Service: 403 | A scope is missing. | Add it to Default scopes, or request it with scope=. |
To see what a client really gets, decode a fresh token's payload and read aud and scope. Never paste a live token into a website.
Create an API client for Access's configuration API
Access's own configuration API, at https://{tenant-host}/oauth/api/v1/configuration, manages web clients, API clients, scopes, identity providers, SAML service providers, web hooks and assurance levels. Use it to keep Access configuration in code. It takes an API client, not a web client.
- 1
Add an API client
Access admin → API clients. Give it a name and a client ID.
- 2
Choose how it authenticates
Client secret (HTTP Basic) or Private key JWT. With Private key JWT, register the public key as a PEM (
-----BEGIN PUBLIC KEY-----), not as a JWK. - 3
Allow only the APIs it needs
Each Access API is allowed separately, as an
onegini_api_*scope. The configuration API isonegini_api_config. Events and end-user APIs are separate permissions. - 4
Call the API directly
There is no token request. Send the client's credentials on every call: Basic, or a freshly signed JWT as a bearer token.
curl -s https://{tenant-host}/oauth/api/v1/configuration/web-clients \
-u "$API_CLIENT_ID:$API_CLIENT_SECRET"| Reply | Means |
|---|---|
401 invalid_credentials | Not an API client (often a web client), wrong secret, or a Private key JWT with the wrong key, client ID or aud. |
403 insufficient_permissions | The API client exists but isn't allowed to use this API. Allow it on the API client. |
- Creating a web client through the API needs
access_token_expires_in, and with the Authorization code grant alsoaccess_grant_expires_in, although the spec doesn't mark them required. - A scope PATCH replaces the whole scope: send every field. A web client PATCH keeps the fields you leave out.
- A hashed client secret must be in PHC form (
$bcrypt$...), not the usual$2b$form. - Reads by ID can show the old version for about two minutes after a change. Tokens reflect the change at once.
All operations and more pitfalls: Access configuration API.