Advanced Flows
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
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.
API
interface ConsentNamespace { getData(challengeId: string): Promise<ConsentScreenData>; submit(challengeId: string, options: ConsentSubmitOptions): Promise<ConsentSubmitResult>;}Types
interface ConsentScreenData { client: ConsentClientInfo; scopes: ConsentScopeInfo[]; user?: ConsentUserInfo; org?: ConsentOrgInfo; actingAs?: ConsentActingAsInfo; features?: ConsentFeatureFlags;}
interface ConsentClientInfo { clientId: string; clientName: string; clientUri?: string; logoUri?: string; policyUri?: string; tosUri?: string;}
interface ConsentScopeInfo { name: string; displayName: string; description: string; required: boolean;}
interface ConsentSubmitOptions { approved: boolean; scopes?: string[]; // Approved scope subset}
interface ConsentSubmitResult { redirectUri: string;}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, { approved }); window.location.href = result.redirectUri; } 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.clientName} is requesting access</h1>
{#if consentData.client.logoUri} <img src={consentData.client.logoUri} alt={consentData.client.clientName} /> {/if}
<h2>Requested permissions:</h2> <ul> {#each consentData.scopes as scope} <li> <strong>{scope.displayName}</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 clientInfo={consentData.client} scopes={consentData.scopes} userInfo={consentData.user} loading={submitting} on:approve={() => handleSubmit(true)} on:deny={() => handleSubmit(false)} />{/if}Device Flow (RFC 8628)
For devices without a browser (smart TV, CLI tools), the user enters a code on another device.
API
interface DeviceFlowNamespace { submit(userCode: string, approve?: boolean): Promise<DeviceFlowSubmitResult>;}
interface DeviceFlowSubmitResult { success: boolean; message?: string;}
// Error class for device flow specific errorsclass DeviceFlowVerificationError extends Error { code: string;}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); if (res.success) { result = 'Device authorized successfully. You can close this page.'; } else { error = res.message ?? 'Authorization failed'; } } 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:submit={(e) => handleSubmit(e.detail.userCode)}/>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).
API
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 { authReqId: string; clientName: string; clientId: string; scope: string; bindingMessage?: string; requestedAt: string; expiresAt: string;}
interface CIBAActionResult { success: boolean; message?: string;}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.authReqId, $user!.id, $user!.sub); requests = requests.filter(r => r.authReqId !== req.authReqId); }
async function reject(req: CIBAPendingRequest) { await auth.ciba.reject(req.authReqId); requests = requests.filter(r => r.authReqId !== req.authReqId); }</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.clientName}</h2> <p>Scope: {req.scope}</p> {#if req.bindingMessage} <p>Message: {req.bindingMessage}</p> {/if} <p>Requested: {new Date(req.requestedAt).toLocaleString()}</p> <div class="actions"> <button onclick={() => approve(req)}>Approve</button> <button onclick={() => reject(req)}>Deny</button> </div> </div> {/each}{/if}Login Challenge
The Login Challenge API provides data for custom login screens when Authrim acts as an intermediary IdP.
API
interface LoginChallengeNamespace { getData(challengeId: string): Promise<LoginChallengeData>;}
interface LoginChallengeData { challengeId: string; client: LoginChallengeClientInfo; loginHint?: string; requestedScopes: string[];}
interface LoginChallengeClientInfo { clientId: string; clientName: string; logoUri?: string;}Usage
<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.clientName}</h1> <!-- Render your login form here -->{/if}Next Steps
- Page Templates — Pre-built templates for all flows
- Authentication — Core authentication methods
- Configuration — SDK configuration reference