Advanced Flows
Overview
Section titled “Overview”Beyond standard authentication (passkey, email code, social), the SDK supports four advanced OAuth/OIDC flows:
| Flow | Namespace | Use Case |
|---|---|---|
| Consent | auth.consent | OAuth consent screen — display requested scopes, approve or deny |
| Device Flow | auth.deviceFlow | RFC 8628 — input-constrained devices (smart TV, CLI) |
| CIBA | auth.ciba | Client Initiated Backchannel Auth — approve from another device |
| Login Challenge | auth.loginChallenge | Custom login screen for third-party IdP integration |
Consent Flow
Section titled “Consent Flow”When an external application requests access to user data via OAuth, Authrim redirects the user to a consent page. The SDK provides the API to fetch consent data and submit the user’s decision.
interface ConsentNamespace { getData(challengeId: string): Promise<ConsentScreenData>; submit(challengeId: string, options: ConsentSubmitOptions): Promise<ConsentSubmitResult>;}interface ConsentScreenData { challenge_id: string; client: ConsentClientInfo; scopes: ConsentScopeInfo[]; user: ConsentUserInfo; organizations: ConsentOrgInfo[]; primary_org: ConsentOrgInfo | null; roles: string[]; acting_as: ConsentActingAsInfo | null; target_org_id: string | null; features: ConsentFeatureFlags;}
interface ConsentClientInfo { client_id: string; client_name: string; client_uri?: string; logo_uri?: string; policy_uri?: string; tos_uri?: string; is_trusted?: boolean;}
interface ConsentScopeInfo { name: string; title: string; description: string; required: boolean;}
interface ConsentSubmitOptions { approve: boolean; selectedOrgId?: string | null; actingAsUserId?: string;}
interface ConsentSubmitResult { redirect_url: string;}SvelteKit Page Example
Section titled “SvelteKit Page Example”<script lang="ts"> import { onMount } from 'svelte'; import { page } from '$app/stores'; import { getAuthContext } from '@authrim/sveltekit'; import type { ConsentScreenData } from '@authrim/sveltekit';
const auth = getAuthContext();
let consentData: ConsentScreenData | null = $state(null); let loading = $state(true); let submitting = $state(false); let error = $state('');
const challengeId = $derived($page.url.searchParams.get('challenge') ?? '');
onMount(async () => { if (!challengeId) { error = 'Missing challenge ID'; loading = false; return; }
try { consentData = await auth.consent.getData(challengeId); } catch (e) { error = e instanceof Error ? e.message : 'Failed to load consent data'; } loading = false; });
async function handleSubmit(approved: boolean) { submitting = true; try { const result = await auth.consent.submit(challengeId, { approve: approved }); window.location.href = result.redirect_url; } catch (e) { error = e instanceof Error ? e.message : 'Consent submission failed'; submitting = false; } }</script>
{#if loading} <p>Loading consent data...</p>{:else if error} <p class="error">{error}</p>{:else if consentData} <div class="consent-page"> <h1>{consentData.client.client_name} is requesting access</h1>
{#if consentData.client.logo_uri} <img src={consentData.client.logo_uri} alt={consentData.client.client_name} /> {/if}
<h2>Requested permissions:</h2> <ul> {#each consentData.scopes as scope} <li> <strong>{scope.title}</strong> {#if scope.required}<span>(required)</span>{/if} <p>{scope.description}</p> </li> {/each} </ul>
<div class="actions"> <button onclick={() => handleSubmit(true)} disabled={submitting}> Allow </button> <button onclick={() => handleSubmit(false)} disabled={submitting}> Deny </button> </div> </div>{/if}Or use the ConsentTemplate:
<script lang="ts"> import { ConsentTemplate } from '@authrim/sveltekit/ui/templates'; // ... same setup as above</script>
{#if consentData} <ConsentTemplate client={consentData.client} scopes={consentData.scopes} user={consentData.user} loading={submitting} on:allow={() => handleSubmit(true)} on:deny={() => handleSubmit(false)} />{/if}Device Flow (RFC 8628)
Section titled “Device Flow (RFC 8628)”For devices without a browser (smart TV, CLI tools), the user enters a code on another device.
interface DeviceFlowNamespace { submit(userCode: string, approve?: boolean): Promise<DeviceFlowSubmitResult>;}
interface DeviceFlowSubmitResult { message: string;}
// Error class for device flow specific errorsclass DeviceFlowVerificationError extends Error { errorCode: string;}SvelteKit Page Example
Section titled “SvelteKit Page Example”<script lang="ts"> import { getAuthContext } from '@authrim/sveltekit';
const auth = getAuthContext();
let userCode = $state(''); let loading = $state(false); let result = $state<string | null>(null); let error = $state('');
async function handleSubmit() { loading = true; error = ''; result = null;
try { const res = await auth.deviceFlow.submit(userCode, true); result = res.message; } catch (e) { error = e instanceof Error ? e.message : 'Device flow error'; } loading = false; }</script>
<h1>Authorize Device</h1><p>Enter the code shown on your device:</p>
<form onsubmit={handleSubmit}> <input type="text" bind:value={userCode} placeholder="ABCD-1234" maxlength="9" required /> <button type="submit" disabled={loading}> {loading ? 'Authorizing...' : 'Authorize'} </button></form>
{#if result}<p class="success">{result}</p>{/if}{#if error}<p class="error">{error}</p>{/if}Or use DeviceFlowTemplate:
<DeviceFlowTemplate {loading} {error} on:approve={(e) => handleSubmit(e.detail.userCode)} on:deny={(e) => auth.deviceFlow.submit(e.detail.userCode, false)}/>CIBA (Client Initiated Backchannel Authentication)
Section titled “CIBA (Client Initiated Backchannel Authentication)”CIBA allows a client application to initiate authentication on behalf of a user, who then approves on a separate device (e.g., mobile push notification).
interface CIBANamespace { getData(loginHint: string): Promise<CIBAPendingRequest[]>; approve(authReqId: string, userId: string, sub: string): Promise<CIBAActionResult>; reject(authReqId: string, reason?: string): Promise<CIBAActionResult>;}
interface CIBAPendingRequest { auth_req_id: string; client_name: string; client_id: string; client_logo_uri: string | null; scope: string; binding_message?: string; user_code?: string; created_at: number; expires_at: number;}
interface CIBAActionResult { success: boolean; message?: string;}SvelteKit Page Example
Section titled “SvelteKit Page Example”<script lang="ts"> import { onMount } from 'svelte'; import { getAuthContext } from '@authrim/sveltekit'; import type { CIBAPendingRequest } from '@authrim/sveltekit';
const auth = getAuthContext(); const { user } = auth.stores;
let requests: CIBAPendingRequest[] = $state([]); let loading = $state(true);
onMount(async () => { if ($user?.email) { requests = await auth.ciba.getData($user.email); } loading = false; });
async function approve(req: CIBAPendingRequest) { await auth.ciba.approve(req.auth_req_id, $user!.id, String($user!.sub ?? $user!.id)); requests = requests.filter(r => r.auth_req_id !== req.auth_req_id); }
async function reject(req: CIBAPendingRequest) { await auth.ciba.reject(req.auth_req_id); requests = requests.filter(r => r.auth_req_id !== req.auth_req_id); }</script>
<h1>Pending Authentication Requests</h1>
{#if loading} <p>Loading...</p>{:else if requests.length === 0} <p>No pending requests.</p>{:else} {#each requests as req} <div class="request-card"> <h2>{req.client_name}</h2> <p>Scope: {req.scope}</p> {#if req.binding_message} <p>Message: {req.binding_message}</p> {/if} <p>Requested: {new Date(req.created_at * 1000).toLocaleString()}</p> <div class="actions"> <button onclick={() => approve(req)}>Approve</button> <button onclick={() => reject(req)}>Deny</button> </div> </div> {/each}{/if}Login Challenge
Section titled “Login Challenge”The Login Challenge API provides data for custom login screens when Authrim acts as an intermediary IdP.
interface LoginChallengeNamespace { getData(challengeId: string): Promise<LoginChallengeData>;}
interface LoginChallengeData { challenge_id: string; client: LoginChallengeClientInfo; login_hint?: string; requested_scopes: string[]; prompt?: string; max_age?: number; acr_values?: string[];}
interface LoginChallengeClientInfo { client_id: string; client_name: string; logo_uri?: string; client_uri?: string; policy_uri?: string; tos_uri?: string; redirect_uris: string[]; scope: string; response_type: string;}<script lang="ts"> import { onMount } from 'svelte'; import { page } from '$app/stores'; import { getAuthContext } from '@authrim/sveltekit';
const auth = getAuthContext();
let challengeData = $state(null); const challengeId = $derived($page.url.searchParams.get('challenge') ?? '');
onMount(async () => { if (challengeId) { challengeData = await auth.loginChallenge.getData(challengeId); } });</script>
{#if challengeData} <h1>Sign in to {challengeData.client.client_name}</h1> <!-- Render your login form here -->{/if}Next Steps
Section titled “Next Steps”- Page Templates — Pre-built templates for all flows
- Authentication — Core authentication methods
- Configuration — SDK configuration reference