高度なフロー
標準的な認証(Passkey、メールコード、ソーシャル)に加えて、SDKは4つの高度なOAuth/OIDCフローをサポートします:
| フロー | 名前空間 | ユースケース |
|---|---|---|
| Consent | auth.consent | OAuth同意画面 — 要求スコープの表示、許可または拒否 |
| Device Flow | auth.deviceFlow | RFC 8628 — 入力制約デバイス(スマートTV、CLI) |
| CIBA | auth.ciba | Client Initiated Backchannel Auth — 別デバイスからの承認 |
| Login Challenge | auth.loginChallenge | サードパーティIdP連携のカスタムログイン画面 |
Consentフロー
Section titled “Consentフロー”外部アプリケーションがOAuth経由でユーザーデータへのアクセスを要求すると、Authrimはユーザーを同意ページにリダイレクトします。SDKは同意データの取得とユーザーの決定を送信するAPIを提供します。
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ページ例
Section titled “SvelteKitページ例”<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 = 'チャレンジIDがありません'; loading = false; return; }
try { consentData = await auth.consent.getData(challengeId); } catch (e) { error = e instanceof Error ? e.message : '同意データの読み込みに失敗しました'; } 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 : '同意の送信に失敗しました'; submitting = false; } }</script>
{#if loading} <p>同意データを読み込み中...</p>{:else if error} <p class="error">{error}</p>{:else if consentData} <div class="consent-page"> <h1>{consentData.client.client_name} がアクセスを要求しています</h1>
{#if consentData.client.logo_uri} <img src={consentData.client.logo_uri} alt={consentData.client.client_name} /> {/if}
<h2>要求された権限:</h2> <ul> {#each consentData.scopes as scope} <li> <strong>{scope.title}</strong> {#if scope.required}<span>(必須)</span>{/if} <p>{scope.description}</p> </li> {/each} </ul>
<div class="actions"> <button onclick={() => handleSubmit(true)} disabled={submitting}> 許可 </button> <button onclick={() => handleSubmit(false)} disabled={submitting}> 拒否 </button> </div> </div>{/if}または ConsentTemplate を使用:
<script lang="ts"> import { ConsentTemplate } from '@authrim/sveltekit/ui/templates'; // ... 上記と同じセットアップ</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)”ブラウザのないデバイス(スマートTV、CLIツール)向け。ユーザーは別のデバイスでコードを入力します。
interface DeviceFlowNamespace { submit(userCode: string, approve?: boolean): Promise<DeviceFlowSubmitResult>;}
interface DeviceFlowSubmitResult { message: string;}
// Device Flow固有エラークラスclass DeviceFlowVerificationError extends Error { errorCode: string;}SvelteKitページ例
Section titled “SvelteKitページ例”<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 : 'デバイスフローエラー'; } loading = false; }</script>
<h1>デバイスの認可</h1><p>デバイスに表示されているコードを入力してください:</p>
<form onsubmit={handleSubmit}> <input type="text" bind:value={userCode} placeholder="ABCD-1234" maxlength="9" required /> <button type="submit" disabled={loading}> {loading ? '認可中...' : '認可'} </button></form>
{#if result}<p class="success">{result}</p>{/if}{#if error}<p class="error">{error}</p>{/if}または 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により、クライアントアプリケーションがユーザーの代わりに認証を開始し、ユーザーは別のデバイス(例:モバイルプッシュ通知)で承認します。
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ページ例
Section titled “SvelteKitページ例”<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>保留中の認証リクエスト</h1>
{#if loading} <p>読み込み中...</p>{:else if requests.length === 0} <p>保留中のリクエストはありません。</p>{:else} {#each requests as req} <div class="request-card"> <h2>{req.client_name}</h2> <p>スコープ: {req.scope}</p> {#if req.binding_message} <p>メッセージ: {req.binding_message}</p> {/if} <p>リクエスト日時: {new Date(req.created_at * 1000).toLocaleString()}</p> <div class="actions"> <button onclick={() => approve(req)}>承認</button> <button onclick={() => reject(req)}>拒否</button> </div> </div> {/each}{/if}Login Challenge
Section titled “Login Challenge”Login Challenge APIは、Authrimが中間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>{challengeData.client.client_name} にサインイン</h1> <!-- ここにログインフォームを表示 -->{/if}