Skip to main content

Integration Models

Authentication verifies identity, confirming that a user, service, or device is who it claims to be. ThunderID offers three integration models for adding authentication to an application. Each model follows different standards and reflects different assumptions about trust, UI ownership, and control.

Redirect-Based

Why This Matters

ThunderID hosts the identity screens. Your app redirects users there and gets them back signed in.

App-Native

Why This Matters

Your app renders every screen, but ThunderID owns the journey — step ordering, branching, and policy stay on the server.

Direct API

Why This Matters

Your app calls ThunderID's primitive APIs directly — low-level, single-purpose operations with no hosted pages and no journey to configure. You decide what to call, when to call it, and what to do with the result.

Redirect-Based

ThunderID implements the OAuth 2.0 Authorization Framework (RFC 6749) and OpenID Connect Core 1.0. Your application delegates authentication to ThunderID as the authorization server: when a user signs in, your application redirects them to a ThunderID-hosted sign-in page. After authenticating, ThunderID redirects back to your application with an authorization code, which your application exchanges for tokens.

The core principle is delegation: user credentials never cross the application boundary. ThunderID handles credential collection, input validation, and multi-step authentication flows on its own hosted pages. Your application receives only signed tokens (RFC 7519) as proof of authentication.

When to Use

Choose this approach when ThunderID should own the entire authentication surface, including flow orchestration, UI rendering, and credential processing. This model suits applications that should not process sensitive user credentials directly, or that require a fully managed authentication experience with standard OAuth 2.0 token issuance. Any application that can perform a browser redirect can use this model.

Key Features

  • Interoperable: Works with any OAuth 2.0 and OIDC-compliant client library or framework.
  • Managed authentication journeys: ThunderID renders and orchestrates sign-in, sign-up, account recovery, MFA, and consent screens.
  • Customizable hosted pages: Customize and configure the sign-in experience via the Flow Builder.
  • Credential isolation: User passwords and authentication secrets remain within ThunderID. Your application never receives them.

How It Works

  1. Your application redirects the user to ThunderID's authorization endpoint.
  2. ThunderID renders the sign-in page and walks the user through the configured authentication flow (credentials, MFA, consent, etc.).
  3. On completion, ThunderID redirects back to your registered redirect URI with an authorization code.
  4. Your application exchanges the authorization code for an access token, ID token, and optionally a refresh token.

See the API Reference for endpoint specifications.

App-Native

App-native authentication uses the Flow Execution API to drive authentication from your application's own UI. Your application calls the API to start a configured authentication flow, renders each step using its own screens, collects user input, and submits responses back. ThunderID controls the flow server-side: sequencing steps, applying policies, and branching on context. Your application owns every screen the user sees. On successful completion, ThunderID returns an assertion token, which your application exchanges for OAuth 2.0 tokens via the Token Exchange grant (RFC 8693).

The core principle is shared responsibility: ThunderID enforces authentication logic and policies server-side, while your application controls the user experience. Authentication flow changes, such as adding MFA or conditional steps, take effect immediately on ThunderID without modifying or redeploying your application.

When to Use

Choose this approach when your application must render its own authentication screens (no redirects to hosted pages) while ThunderID enforces authentication logic, MFA, and policies server-side. This model suits native mobile and desktop apps, or confidential server-side applications, where the client can be trusted to drive the flow directly.

warning

App-native sign-in is not supported for browser-based single-page applications (SPAs) such as React, Angular, or Vue apps. SPAs must register as the browser application type, which can only sign users in through the Redirect-Based model with the OAuth 2.0 Authorization Code grant and PKCE. Registration and recovery have no redirect-based equivalent today, so SPAs continue to drive those flows through the Flow Execution API.

Direct Initiation Restriction

This restriction applies to new authentication and sign-out flow initiation only (flowType: AUTHENTICATION or SIGN_OUT with no executionId). Continuation requests that carry a valid executionId are always accepted, and registration, recovery, and user onboarding flows are never restricted because they have no redirect-based equivalent.

Whether an application may initiate one of these flows directly depends on its application type:

  • Browser and Backend Service (m2m) applications can never initiate these flows directly. A browser application must sign users in through the Authorization endpoint (GET /oauth2/authorize), which drives the flow internally. A Backend Service application authenticates itself with the client_credentials grant instead of signing in a user.
  • Mobile applications must present a valid platform attestation token. See Attestation.
  • Full-stack, Custom, and MCP Client applications are derived from their OAuth 2.0 configuration. An application with no OAuth 2.0 configuration, or one that is confidential, does not use the authorization_code grant, and is not configured with client_credentials as its only grant, authenticates with a Flow Secret. An application configured with the authorization_code grant, or with client_credentials as its only grant, cannot initiate the flow directly.

Key Features

  • Server-side flow execution: Authentication logic, step ordering, and branching live in ThunderID. Changes take effect immediately without redeploying your application.
  • Full UI control: Your application decides how each step looks. No iframe, redirect, or hosted-page constraints.
  • Policy enforcement: MFA, conditional steps, and adaptive prompts apply automatically based on the flow definition.
  • SDK support: ThunderID provides SDKs that manage API calls, state tracking, and response parsing.

How It Works

  1. Your application calls the Flow Execution API to start the configured authentication flow.
  2. ThunderID responds with the next step: inputs to collect, available actions, and optional context.
  3. Your application renders the screen, collects user input, and submits the response back to the Flow Execution API.
  4. Steps 2-3 repeat until the flow completes. ThunderID returns the assertion token.
  5. Your application exchanges the assertion token for an access token, ID token, and optionally a refresh token via the Token Exchange grant (RFC 8693).

See the API Reference for endpoint specifications.

Flow Secret

A confidential server-side application (one with no OAuth 2.0 configuration, or one that is confidential and does not use the authorization_code grant) authenticates at flow initiation with a Flow Secret. ThunderID issues this secret once, when the application is created, and never returns it again; the Console can regenerate it if it's lost. Keep it server-side only, since a browser SPA has nowhere safe to hold it, which is why the browser application type is never issued one (see Direct Initiation Restriction above).

To initiate a new authentication or sign-out flow, the application presents the secret in the Flow-Secret request header:

POST /flow/execute
Content-Type: application/json
Flow-Secret: <flow-secret>

{
"applicationId": "<application-id>",
"flowType": "AUTHENTICATION"
}

An application eligible for a Flow Secret that omits the header is rejected with 401 Unauthorized, and an incorrect value is also rejected with 401 Unauthorized. Flow continuation requests (those carrying an executionId) do not require the header.

Attestation

A confidential server-side application authenticates at flow initiation with a Flow Secret, but a mobile application cannot safely hold one. Instead, a Mobile application type proves its identity through platform attestation. ThunderID supports Google Play Integrity for Android clients and Apple App Attest for iOS clients. A Mobile application must configure attestation and present a valid attestation token to initiate a sign-in flow directly; ThunderID verifies the token against the registered identity before starting the flow.

Attestation is a client-level setting configured at the application level, independent of the OAuth 2.0 protocol. It is optional, and when enabled, an application configures exactly one platform. A Mobile application that has not configured attestation is rejected outright when it attempts to initiate a flow directly. Attestation is only consulted for the Mobile application type: configuring it on any other application type has no effect on direct flow initiation. See Configure Platform Attestation for how to configure it.

To initiate a new flow, the mobile client obtains a platform attestation token and presents it in the Attestation-Token request header of the Flow Execution request:

POST /flow/execute
Content-Type: application/json
Attestation-Token: <attestation-token>

{
"applicationId": "<application-id>",
"flowType": "AUTHENTICATION"
}

A Mobile application that has not configured attestation is rejected with 400 Bad Request. Once attestation is configured, an application that omits the token is rejected with 401 Unauthorized, and an invalid or malformed token is also rejected with 401 Unauthorized. Flow continuation requests (those carrying an executionId) do not require an attestation token.

note

A Mobile application with Dev Mode enabled is an exception to the rejections above. It can initiate a flow without presenting an attestation token, regardless of whether a platform is configured. Dev Mode is intended for testing and trying out sample or development clients, not production use. See Dev Mode.

Integration Modes

App-native authentication supports two modes, Verbose and Non-Verbose, controlled via the verbose field in the Flow Execution API. These modes offer different levels of UI granularity to the application. A ThunderID SDK typically consumes these modes and manages API calls, state, and response parsing. Both modes follow the same request-response cycle: the application calls the Flow Execution API, receives the next step, renders the screen, collects user input, and submits the response back. The difference lies in how much detail ThunderID returns for each step.

Verbose Mode

In verbose mode (verbose: true), each response includes inputs, actions, and a meta component tree describing the screen layout. The application renders the screen exactly as the server specifies.

Using the SDK is Recommended

The SDK renders the component tree automatically. Without the SDK, your application must parse and render the component tree itself.

Request:

curl https://localhost:8090/flow/execute \
--request POST \
--header 'Content-Type: application/json' \
--data '{
"applicationId": "550e8400-e29b-41d4-a716-446655440000",
"flowType": "AUTHENTICATION",
"verbose": true
}'

Response:

{
"executionId": "2c6d4c45-3de9-4a70-ae6b-ba1d034af6bc",
"flowStatus": "INCOMPLETE",
"challengeToken": "a3f2e1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2",
"type": "VIEW",
"data": {
"inputs": [
{
"ref": "input_phone",
"identifier": "phone",
"type": "TEL_INPUT",
"required": true
}
],
"actions": [
{
"ref": "action_submit",
"nextNode": "node_bs12"
}
],
"meta": {
"components": [
{
"id": "main-block",
"type": "BLOCK",
"components": [
{
"id": "field-phone",
"ref": "input_phone",
"type": "TEL_INPUT",
"label": "Phone Number",
"required": true
},
{
"id": "action-submit",
"ref": "action_submit",
"type": "ACTION",
"label": "Continue"
}
]
}
]
}
}
}

Non-Verbose Mode

In non-verbose mode (verbose: false), each response includes only inputs and actions, without the meta component tree. Your application decides how to render each screen and capture input using its own components.

Request:

curl https://localhost:8090/flow/execute \
--request POST \
--header 'Content-Type: application/json' \
--data '{
"applicationId": "550e8400-e29b-41d4-a716-446655440000",
"flowType": "AUTHENTICATION"
}'

Response:

{
"executionId": "2c6d4c45-3de9-4a70-ae6b-ba1d034af6bc",
"flowStatus": "INCOMPLETE",
"challengeToken": "a3f2e1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2",
"type": "VIEW",
"data": {
"inputs": [
{
"ref": "input_phone",
"identifier": "phone",
"type": "TEL_INPUT",
"required": true
}
],
"actions": [
{
"ref": "action_submit",
"nextNode": "node_bs12"
}
]
}
}

Example

The following shows a username and password flow. Your application starts the flow, ThunderID responds with the first step describing what to collect, and your application renders the form.

Request:

curl https://localhost:8090/flow/execute \
--request POST \
--header 'Content-Type: application/json' \
--data '{
"applicationId": "550e8400-e29b-41d4-a716-446655440000",
"flowType": "AUTHENTICATION"
}'

Response:

{
"executionId": "2c6d4c45-3de9-4a70-ae6b-ba1d034af6bc",
"flowStatus": "INCOMPLETE",
"challengeToken": "a3f2e1b0c9d8e7f6a5b4c3d2e1f0a9b8...",
"type": "VIEW",
"data": {
"inputs": [
{ "ref": "input_username", "identifier": "username", "type": "TEXT_INPUT", "required": true },
{ "ref": "input_password", "identifier": "password", "type": "PASSWORD_INPUT", "required": true }
],
"actions": [
{ "ref": "action_submit", "nextNode": "node_bs12" }
]
}
}

Your application renders the username and password fields, collects input, and submits them back using the executionId and challengeToken.

Request:

curl https://localhost:8090/flow/execute \
--request POST \
--header 'Content-Type: application/json' \
--data '{
"executionId": "2c6d4c45-3de9-4a70-ae6b-ba1d034af6bc",
"challengeToken": "a3f2e1b0c9d8e7f6a5b4c3d2e1f0a9b8...",
"action": "action_submit",
"inputs": {
"input_username": "thor",
"input_password": "odinson123"
}
}'

Response:

{
"executionId": "2c6d4c45-3de9-4a70-ae6b-ba1d034af6bc",
"flowStatus": "COMPLETE",
"assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

ThunderID validates the credentials and returns either the next step or the final assertion token. See Assertion Tokens.

Direct API

Direct API authentication exposes individual, single-purpose authentication endpoints. Each endpoint performs exactly one operation: verify credentials, send a one-time password (OTP), verify an OTP, or initiate a social login. Your application calls these endpoints directly and controls the entire authentication sequence itself.

The core principle is composable operations: authentication consists of building blocks your application assembles in any order, according to its own logic. No server-side journey or policy enforcement applies. No flow configuration or application registration is required.

When to Use

Choose this approach when your application needs direct control over individual authentication operations without flow configuration or application registration. This model suits headless or scripted scenarios such as CLI tools, automation scripts, or server-to-server authentication. For authentication scenarios that require server-enforced policies or multi-step orchestration, use the App-Native or Redirect-Based model instead.

Key Features

  • Composable operations: Call individual authentication endpoints in any order. No flow execution layer required.
  • No configuration required: No application registration or flow setup needed. Call the endpoints directly. The client orchestrates the entire authentication journey and ensures correct implementation.
  • Step-up authentication: Chain methods by passing assertion tokens between calls to build a stronger authentication context progressively.
  • Assertion tokens: Each successful call returns a signed JWT (RFC 7519) recording completed methods, user identity, and assurance level. You can enrich an existing assertion by including it in the next authentication request.

How It Works

  1. Your application calls an authentication endpoint directly with user credentials or input.
  2. ThunderID validates the input and returns user details and a signed assertion token. See Assertion Tokens.
  3. To chain methods, include the assertion token from the previous call in the next request.

See the API Reference for endpoint specifications.

Direct Auth Secret

Because Direct API endpoints take no application identifier and require no application registration, ThunderID gates them with a server-level secret. These endpoints are secure by default: they are blocked until you set server.security.direct_auth_secret in the deployment configuration. Once set, every call to a Direct API endpoint (/auth/** and /register/passkey/**) must include that value in the Direct-Auth-Secret header. Protected AuthZEN PDP endpoints also use this header for PEP and adapter calls. A request with a missing or incorrect value, or any request while no secret is configured, is rejected with 401.

On a fresh setup, the setup.sh / setup.ps1 script generates a secret, writes it to deployment.yaml, and prints it once. Use that value, override it with --direct-auth-secret, or set your own in the configuration.

This suits the intended Direct API callers, such as CLI tools, automation scripts, and server-to-server integrations, which can hold a configured secret. Browser and mobile applications, which cannot safely store a static secret, should use the Redirect-Based or App-Native model instead.

curl -X POST https://localhost:8090/auth/credentials/authenticate \
-H 'Content-Type: application/json' \
-H 'Direct-Auth-Secret: <direct-auth-secret>' \
-d '{
"identifiers": { "username": "thor" },
"credentials": { "password": "<password>" }
}'

See Security Configuration for details.

Step-Up Authentication

Direct API authentication supports step-up authentication. Pass an existing assertion token to any supported endpoint to enrich it with the new authentication method. Each additional method strengthens the authentication context.

The following endpoints support step-up authentication:

  • POST /auth/credentials/authenticate
  • POST /auth/otp/sms/verify
  • POST /auth/oauth/google/finish
  • POST /auth/oauth/github/finish
  • POST /auth/oauth/standard/finish
Skipping Assertion Tokens

If you do not need an assertion token, set skip_assertion: true in the request body:

curl -X POST https://localhost:8090/auth/credentials/authenticate \
-H 'Content-Type: application/json' \
-d '{
"identifiers": { "username": "thor" },
"credentials": { "password": "<password>" },
"skip_assertion": true
}'

Example

The following example demonstrates step-up authentication by chaining password authentication with SMS OTP verification to build a stronger authentication context.

Password + SMS OTP

Step 1: Authenticate with credentials

Request:

curl -X POST https://localhost:8090/auth/credentials/authenticate \
-H 'Content-Type: application/json' \
-d '{
"identifiers": { "username": "thor" },
"credentials": { "password": "<password>" }
}'

Response:

{
"id": "<user_id>",
"type": "person",
"ouId": "<org_unit_id>",
"assertion": "<assertion_token>"
}

Step 2: Send OTP

Request:

curl -X POST https://localhost:8090/auth/otp/sms/send \
-H 'Content-Type: application/json' \
-d '{
"senderId": "<sender_id>",
"recipient": "+1234567890"
}'

Response:

{
"status": "SUCCESS",
"sessionToken": "<session_token>"
}

Step 3: Verify OTP with the assertion from Step 1

Request:

curl -X POST https://localhost:8090/auth/otp/sms/verify \
-H 'Content-Type: application/json' \
-d '{
"sessionToken": "<session_token_from_step_2>",
"otp": "123456",
"assertion": "<assertion_token_from_step_1>"
}'

Response:

{
"id": "<user_id>",
"type": "person",
"ouId": "<org_unit_id>",
"assertion": "<enriched_assertion_token>"
}

The final response contains an enriched assertion token that reflects both authentication methods.

Assertion Tokens

Both App-Native and Direct API return an assertion token on successful authentication. This token is a signed JWT (RFC 7519) containing:

  • Authentication methods completed: Which mechanisms the user passed (credentials, OTP, social login).
  • Assurance level: The authentication strength based on the methods completed.
  • User identity: Details about the authenticated user.

To obtain OAuth 2.0 access tokens, exchange the assertion token at the token endpoint using the Token Exchange grant (RFC 8693).

Design Comparison

Redirect-BasedApp-NativeDirect API
Trust modelDelegated: ThunderID is the authorityShared: ThunderID controls the flow execution sequence, app rendersApplication-owned: app controls the sequence
Credential exposureThunderID onlyPasses through the applicationPasses through the application
Authentication logicServer-enforced by flowServer-enforced by flowApplication-enforced
Token outputOAuth 2.0 access tokens, ID tokensAssertion token (JWT); exchange via Token Exchange (RFC 8693) for OAuth 2.0 tokensAssertion token (JWT); exchange via Token Exchange (RFC 8693) for OAuth 2.0 tokens
Flow configurationRequiredRequiredNot required
  • Tokens: Access tokens, ID tokens, and refresh tokens
  • Authorization: How ThunderID controls access to resources after authentication

Explore with AI

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.