高度なフロー
概要
標準的な認証(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フロー
外部アプリケーションがOAuth経由でユーザーデータへのアクセスを要求すると、Authrimはユーザーを同意ページにリダイレクトします。SDKは同意データの取得とユーザーの決定を送信するAPIを提供します。
API
interface ConsentNamespace { getData(challengeId: string): Promise<ConsentScreenData>; submit(challengeId: string, options: ConsentSubmitOptions): Promise<ConsentSubmitResult>;}型定義
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[]; // 承認するスコープのサブセット}
interface ConsentSubmitResult { redirectUri: string;}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, { approved }); window.location.href = result.redirectUri; } 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.clientName} がアクセスを要求しています</h1>
{#if consentData.client.logoUri} <img src={consentData.client.logoUri} alt={consentData.client.clientName} /> {/if}
<h2>要求された権限:</h2> <ul> {#each consentData.scopes as scope} <li> <strong>{scope.displayName}</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 clientInfo={consentData.client} scopes={consentData.scopes} userInfo={consentData.user} loading={submitting} on:approve={() => handleSubmit(true)} on:deny={() => handleSubmit(false)} />{/if}Device Flow(RFC 8628)
ブラウザのないデバイス(スマートTV、CLIツール)向け。ユーザーは別のデバイスでコードを入力します。
API
interface DeviceFlowNamespace { submit(userCode: string, approve?: boolean): Promise<DeviceFlowSubmitResult>;}
interface DeviceFlowSubmitResult { success: boolean; message?: string;}
// Device Flow固有エラークラスclass DeviceFlowVerificationError extends Error { code: string;}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); if (res.success) { result = 'デバイスが正常に認可されました。このページを閉じて構いません。'; } else { error = 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:submit={(e) => handleSubmit(e.detail.userCode)}/>CIBA(Client Initiated Backchannel Authentication)
CIBAにより、クライアントアプリケーションがユーザーの代わりに認証を開始し、ユーザーは別のデバイス(例:モバイルプッシュ通知)で承認します。
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ページ例
<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>保留中の認証リクエスト</h1>
{#if loading} <p>読み込み中...</p>{:else if requests.length === 0} <p>保留中のリクエストはありません。</p>{:else} {#each requests as req} <div class="request-card"> <h2>{req.clientName}</h2> <p>スコープ: {req.scope}</p> {#if req.bindingMessage} <p>メッセージ: {req.bindingMessage}</p> {/if} <p>リクエスト日時: {new Date(req.requestedAt).toLocaleString()}</p> <div class="actions"> <button onclick={() => approve(req)}>承認</button> <button onclick={() => reject(req)}>拒否</button> </div> </div> {/each}{/if}Login Challenge
Login Challenge APIは、Authrimが中間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;}使用例
<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.clientName} にサインイン</h1> <!-- ここにログインフォームを表示 -->{/if}