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
- Your application redirects the user to ThunderID's authorization endpoint.
- ThunderID renders the sign-in page and walks the user through the configured authentication flow (credentials, MFA, consent, etc.).
- On completion, ThunderID redirects back to your registered redirect URI with an authorization code.
- 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.
App-native sign-in is not supported for browser-based single-page applications (SPAs) such as React, Angular, or Vue apps. SPAs are public clients that cannot be verified when calling the Flow Execution API directly, so they must 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.
This restriction applies to authentication flows only. The Flow Execution API rejects a new authentication-flow initiation request (flowType: AUTHENTICATION with no executionId) from an application configured with the authorization_code grant type. Such applications must initiate authentication through the Authorization endpoint (GET /oauth2/authorize), which then drives the flow internally.
Registration, recovery, and user onboarding flows are not restricted — they have no redirect-based equivalent and can still be initiated directly through the Flow Execution API regardless of grant type. Continuation requests that carry a valid executionId are also always accepted.
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
- Your application calls the Flow Execution API to start the configured authentication flow.
- ThunderID responds with the next step: inputs to collect, available actions, and optional context.
- Your application renders the screen, collects user input, and submits the response back to the Flow Execution API.
- Steps 2-3 repeat until the flow completes. ThunderID returns the assertion token.
- 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.
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.
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
- Your application calls an authentication endpoint directly with user credentials or input.
- ThunderID validates the input and returns user details and a signed assertion token. See Assertion Tokens.
- 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. 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 — 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/authenticatePOST /auth/otp/sms/verifyPOST /auth/oauth/google/finishPOST /auth/oauth/github/finishPOST /auth/oauth/standard/finish
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-Based | App-Native | Direct API | |
|---|---|---|---|
| Trust model | Delegated: ThunderID is the authority | Shared: ThunderID controls the flow execution sequence, app renders | Application-owned: app controls the sequence |
| Credential exposure | ThunderID only | Passes through the application | Passes through the application |
| Authentication logic | Server-enforced by flow | Server-enforced by flow | Application-enforced |
| Token output | OAuth 2.0 access tokens, ID tokens | Assertion token (JWT); exchange via Token Exchange (RFC 8693) for OAuth 2.0 tokens | Assertion token (JWT); exchange via Token Exchange (RFC 8693) for OAuth 2.0 tokens |
| Flow configuration | Required | Required | Not required |
Related Reference
- Tokens: Access tokens, ID tokens, and refresh tokens
- Authorization: How ThunderID controls access to resources after authentication