Sign-in and Access · 9 min read

How sign-in works

Follow a sign-in from your app through Access and an Identity Orchestration journey and back. Learn which endpoints your app calls, what the session and tokens are, and how logout works.

The parts and who talks to whom

Your app never signs users in itself. It sends them to Access, the tenant's OpenID Connect (OIDC) and OAuth 2.0 provider. Access decides how the user authenticates, runs that authentication, and gives your app tokens.

PartRole in a sign-in
Your appThe relying party. It is registered in Access as a web client and only talks standard OIDC.
AccessThe OIDC provider. It owns the authorize, token, logout and discovery endpoints, the single sign-on session and the tokens.
Identity Orchestration (IO) journeyOptional. The pages and logic of the sign-in: password, passkey, OTP, consent, profile completion. Access hands the user to the journey and gets the result back.
Identity BrokerOptional. Connects external identity providers such as Google, Microsoft Entra ID, SAML IdPs and national eIDs. See External identity providers.
Identity StoreHolds the user. Access and the journey read and update the user there.

Because the app only speaks OIDC, you can change the whole sign-in experience in IO or Access without touching the app.

The authorization code flow with PKCE

Use the authorization code grant with PKCE for every app that signs users in: web apps, single-page apps and mobile apps. PKCE stops a stolen authorization code from being used. Public clients (no secret) must use it; confidential clients should.

  1. 1

    Create a code verifier

    Generate a random code_verifier and derive code_challenge = BASE64URL(SHA-256(verifier)). Use the S256 method, not plain.

  2. 2

    Send the user to Access

    Redirect the browser to the authorization endpoint with response_type=code, your client_id, a registered redirect_uri, scope (include openid), a random state, a nonce and the code_challenge.

  3. 3

    Access authenticates the user

    If the user already has an Access session, this step is silent. Otherwise Access runs the sign-in, often by handing the user to an IO journey (see the next section).

  4. 4

    Access redirects back with a code

    The browser returns to your redirect_uri with code and your state. Check that state matches what you sent.

  5. 5

    Exchange the code for tokens

    Your app posts the code and the code_verifier to the token endpoint, from its backend when it has one. The code is short-lived (30 seconds by default) and works once.

  6. 6

    Validate the ID token

    Check the signature against the JWKS, iss, aud (your client ID), exp and nonce. Then start your app's own session.

GET https://{tenant-host}/oauth/v1/authorize
  ?response_type=code
  &client_id={client-id}
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
  &scope=openid%20profile%20email
  &state={random}
  &nonce={random}
  &code_challenge={challenge}
  &code_challenge_method=S256
curl -s https://{tenant-host}/oauth/v1/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code=$CODE \
  -d redirect_uri=https://app.example.com/callback \
  -d code_verifier=$CODE_VERIFIER
# A public client leaves out -u and sends -d client_id=$CLIENT_ID instead.

Read the endpoints from the discovery document instead of hard-coding them. These are the paths Access uses:

EndpointPath
DiscoveryGET /oauth/.well-known/openid-configuration
AuthorizeGET /oauth/v1/authorize
TokenPOST /oauth/v1/token
Signing keys (JWKS)GET /oauth/v1/keys
User info/oauth/v1/userinfo
RevokePOST /oauth/v1/revoke
End session (logout)/oauth/v1/logout

Your app must be registered as a web client first, with the exact redirect URI and the scopes it asks for. See Web clients and API clients in Access.

Where Identity Orchestration journeys fit

Access does not have to draw the sign-in pages itself. When the identity provider behind a web client is an IO journey, Access hands the user over to the journey and waits for the result. The app does not notice: it still talks only to Access.

  1. 1

    Access starts the journey

    Access redirects the browser to the journey with a short-lived signed context_token. It carries the transaction, the requesting client, and the request context (scopes, acr_values, login_hint, locale). It expires after about 60 seconds, so the journey must read it right away.

  2. 2

    The journey runs

    It starts with the Start (Access) node, which checks the token. Then it shows its pages and calls the platform services: password, passkey, OTP, consent.

  3. 3

    The journey hands the user back

    The End Success (Access) node reports the authenticated user to Access from the backend, with the authentication methods used and the assurance level reached.

  4. 4

    Access finishes the OIDC flow

    Access sets its session and redirects the browser to the app's redirect_uri with the authorization code.

Which journey runs depends on the web client's Identity provider setting and on the assurance level the app asks for with acr_values (or the client's Default ACRs). An app that needs a stronger sign-in, for example before a payment, asks for a higher level. If the current session doesn't meet it, Access sends the user through a step-up.

A journey that someone opens directly, from a bookmark for example, has no context_token. It cannot sign anyone in. Show a page that tells the user to start again from the app.

To build the journey itself, start with Getting started with Identity Orchestration.

Sessions

There are two sessions. Keep them apart in your head.

SessionOwned byWhat it does
Access sessionAccess (a cookie on the tenant host)Single sign-on. While it is valid, a new authorization request from any app signs the user in without asking again, unless step-up is needed.
App sessionYour appYour own cookie or token store. Access doesn't see it or end it, except through front-channel logout.

Set the Access session lifetime in the Access admin console, under the system settings for sessions:

  • Inactivity timeout: each interaction extends the session by this amount.
  • Maximum session length: after this, the user signs in again, whatever their activity.
  • Changes apply to new sessions and to sessions updated after you save. Existing idle sessions keep their old limits.

On the web client you can also limit parallel sessions. Allow simultaneous sessions off means a new sign-in cleans up the user's older sessions. With it on, Maximum simultaneous sessions (2 to 25) invalidates the oldest session when the limit is passed.

Tokens

TokenForFormatLifetime setting
ID tokenYour app: who signed in, when and how (sub, aud, acr, profile claims from the granted scopes).Signed JWT (RS256 by default).ID token validity on the web client's OpenID Connect settings.
Access tokenAPIs your app calls on the user's behalf. Carries aud, scope, sub, client_id.JWT or opaque, set per web client with Access token format.Access token validity, in seconds.
Refresh tokenGetting a new access token without sending the user back to Access.Treat it as an opaque string. Store it like a password.Refresh token validity, counted from the first access token. Empty means no expiry.
  • Validate JWTs locally: verify the signature with the keys from /oauth/v1/keys, then check iss, aud and exp. Access rotates its signing keys, so look keys up by kid; don't pin one key.
  • Opaque access tokens can't be read by the API. The API asks Access through token introspection.
  • Refresh tokens are issued only with the authorization code grant and only when Issue refresh token is on. Each refresh returns a new refresh token and invalidates the old one.
  • Replay protection: with Revoke all active refresh tokens when an old refresh token is used on, reusing an old refresh token revokes the whole chain and the user must sign in again. Keep it on.

What ends up inside the tokens depends on scopes and claims mapping. See Scopes and claims.

Logout

Clearing your app's cookie is not enough: the Access session would sign the user straight back in. Send the browser to the end session endpoint.

GET https://{tenant-host}/oauth/v1/logout
  ?id_token_hint={id-token}
  &post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2Fsigned-out
  &state={random}
  1. 1

    Access ends its session

    This signs the user out of every app that shares the Access session.

  2. 2

    Front-channel logout

    Access returns a page with one hidden iframe per app in the session that has a Front-Channel Logout URL. Each app clears its own session there.

  3. 3

    Redirect

    The browser goes to post_logout_redirect_uri. It must be the client's Post Logout Redirect URL or one of its Additional Post Logout Redirect URLs. Without one in the request, Access uses the default, or stays on its logged-out page if none is set.

  • Turn on Delete access/refresh tokens on logout for the web client, especially for public clients using PKCE. Otherwise tokens stay valid until they expire.
  • To drop one token without ending the session, call POST /oauth/v1/revoke with token and token_type_hint.
  • Users who signed in through an external identity provider are signed out there only if single logout is set up for that provider in the Identity Broker.

When sign-in fails

SymptomLikely causeFix
Access shows an error instead of redirectingThe redirect_uri doesn't exactly match a registered Redirect URL.Register the exact URI, including path and trailing slash, on the web client.
Request refused because of the scopeThe app asked for a scope that isn't in the client's Default or Additional scopes, or doesn't exist.Add it to the client. See Scopes and claims.
Request fails with no scope sentThe client has no Default scopes.Add openid (and what the app needs) to Default scopes.
invalid_grant on the code exchangeThe code expired, was used twice, or the code_verifier or redirect_uri differs from the authorize request.Exchange the code at once, only once, with the same values.
The journey says to start from the applicationIt was opened without a context_token, or the token expired.Always start from the app. Don't link to the journey URL.
ID token lacks a claimThe scope wasn't granted or the attribute has no value.Check the scope's claims mapping and the user's data.

For journey-side problems, see Troubleshooting Identity Orchestration.

Related