コンテンツにスキップ

クロスドメインSSO

概要

クロスドメインSSOにより、ユーザーは一度サインインするだけで、異なるドメインのアプリケーションでも自動的に認証されます。

従来のiframeベースのサイレント認証は、モダンブラウザでは信頼性が低くなっています:

  • Safari ITP — サードパーティCookieとiframeストレージアクセスをブロック
  • Chrome サードパーティCookie廃止 — iframe内のサードパーティCookieを制限

Authrimの trySilentLogin() はこの問題をトップレベルナビゲーションで解決します — ページ全体レベルで prompt=none でIdPにリダイレクトするため、サードパーティCookie制限の影響を受けません。

動作原理

sequenceDiagram
    participant App as app.example.com
    participant IdP as auth.example.com

    App->>App: ユーザーがアプリにアクセス
    App->>IdP: リダイレクト(prompt=none, state={returnTo})
    alt IdPにセッションあり
        IdP->>App: 認可コード付きリダイレクト
        App->>App: callback.htmlがコード交換を処理
        App->>App: returnTo URLにリダイレクト
        Note over App: ユーザーはサインイン済み
    else セッションなし
        IdP->>App: error=login_required付きリダイレクト
        App->>App: callback.htmlがsso_error付きでリダイレクト
        Note over App: ログインボタンを表示
    end

セットアップ

1. OAuthを有効にして初期化

const auth = await createAuthrim({
issuer: 'https://auth.example.com',
clientId: 'my-app',
enableOAuth: true,
silentLoginRedirectUri: 'https://app.example.com/callback.html',
});

2. ページ読み込み時にサイレントログインを試行

async function initApp() {
// 既に認証済みか確認
const { data } = await auth.session.get();
if (data) {
showDashboard(data.user);
return;
}
// URLにSSOエラーがあるか確認(コールバックから返された)
const params = new URLSearchParams(window.location.search);
const ssoError = params.get('sso_error');
if (ssoError) {
// SSO失敗 — URLをクリーンにしてログインボタンを表示
window.history.replaceState({}, '', window.location.pathname);
showLoginButton();
return;
}
// SSO試行(初回アクセスのみ — 無限ループ防止)
const ssoAttempted = sessionStorage.getItem('sso_attempted');
if (!ssoAttempted) {
sessionStorage.setItem('sso_attempted', 'true');
await auth.oauth.trySilentLogin({
onLoginRequired: 'return',
returnTo: window.location.href,
});
// この関数はリダイレクトして戻りません
}
// SSO試行済み — ログインボタンを表示
showLoginButton();
}

3. コールバック処理

コールバックページ(callback.html)で:

const auth = await createAuthrim({
issuer: 'https://auth.example.com',
clientId: 'my-app',
enableOAuth: true,
});
// サイレントログインコールバックを処理
const result = await auth.oauth.handleSilentCallback();
// handleSilentCallback() はすべてのリダイレクトロジックを内部で処理:
// - 成功: コード交換、returnToにリダイレクト
// - login_required: sso_errorパラメータ付きでreturnToにリダイレクト
// - エラー: エラー詳細付きでリダイレクト

trySilentLoginオプション

interface TrySilentLoginOptions {
/**
* IdPにセッションがない場合の動作:
* - 'return': アプリに戻る(ログインボタンを表示)
* - 'login': IdPのログイン画面を表示
*
* デフォルト: 'return'
*/
onLoginRequired?: 'return' | 'login';
/** SSO後に戻るURL(デフォルト: 現在のURL) */
returnTo?: string;
/** 追加のOAuthスコープ */
scope?: string;
}

onLoginRequiredの動作

SSO成功時IdPセッションなし時
'return'自動認証sso_error=login_required でアプリに戻る
'login'自動認証IdPのログイン画面を表示

ほとんどの場合は 'return'(デフォルト)を使用してください — SSOをサイレントに確認し、セッションがなければアプリのログインUIを表示します。IdPのログイン画面をユーザーに強制的に表示したい場合は 'login' を使用します。

handleSilentCallbackの戻り値

type SilentLoginResult =
| { status: 'success' }
| { status: 'login_required' }
| { status: 'error'; error: string; errorDescription?: string };

実際には handleSilentCallback() がリダイレクトを内部で処理するため、戻り値を検査する必要はほとんどありません。コールバックページは単にこれを呼び出すだけで、ユーザーはリダイレクトされます。

ループ防止

SSOリダイレクトループは以下の場合に発生します:

  1. アプリがIdPにリダイレクト(prompt=none)
  2. IdPにセッションなし → エラーで戻る
  3. アプリが再びIdPにリダイレクト → 無限ループ

SDKは sessionStorage でこれを防止します:

// SSO試行前にフラグを設定
sessionStorage.setItem('sso_attempted', 'true');
// ページ読み込み時にフラグを確認
if (sessionStorage.getItem('sso_attempted')) {
// SSO再試行しない
showLoginButton();
}
// ログイン成功時にフラグをクリア(再ログイン後のリトライを許可)
sessionStorage.removeItem('sso_attempted');
// 明示的ログアウト時にフラグをクリア(次回訪問時のSSOを許可)
auth.signOut(); // 内部でフラグをクリア

セキュリティ:オープンリダイレクト防止

trySilentLogin()returnTo URLを検証してオープンリダイレクト攻撃を防止します:

// 安全 — 同一オリジン
await auth.oauth.trySilentLogin({
returnTo: 'https://app.example.com/dashboard',
});
// 拒否 — 異なるオリジン(Errorがスロー)
await auth.oauth.trySilentLogin({
returnTo: 'https://evil.com/steal',
});
// Error: "returnTo must be same origin"

マルチアプリSSOアーキテクチャ

Authrim IdPを共有する複数アプリの組織向け:

auth.example.com (Authrim IdP)
├── app1.example.com (アプリ1 — enableOAuth: true)
├── app2.example.com (アプリ2 — enableOAuth: true)
└── admin.example.com (管理 — enableOAuth: true)

各アプリが独立して trySilentLogin() を呼び出します。ユーザーがいずれかのアプリにログインするとIdPセッションが確立されます。その後の他のアプリへのアクセスではSSOでセッションが検出され、自動的に認証されます。

アプリごとの設定

各アプリに必要なもの:

  1. クライアントID — Authrim管理パネルで登録
  2. コールバックURLhttps://app1.example.com/callback.html
  3. 許可されたオリジンhttps://app1.example.com

完全な例

import { createAuthrim } from '@authrim/web';
const auth = await createAuthrim({
issuer: 'https://auth.example.com',
clientId: 'my-app',
enableOAuth: true,
});
// メインページロジック
async function init() {
const { data } = await auth.session.get();
if (data) {
// 認証成功時にSSOフラグをクリア
sessionStorage.removeItem('sso_attempted');
showDashboard(data.user);
return;
}
// SSOエラーを確認
const params = new URLSearchParams(window.location.search);
if (params.get('sso_error')) {
window.history.replaceState({}, '', window.location.pathname);
showLoginButton();
return;
}
// SSOを一度試行
if (!sessionStorage.getItem('sso_attempted')) {
sessionStorage.setItem('sso_attempted', 'true');
await auth.oauth.trySilentLogin({ onLoginRequired: 'return' });
return; // ここには到達しない
}
showLoginButton();
}
// ログアウト — SSOフラグをクリアしてリトライを許可
document.getElementById('logout').addEventListener('click', async () => {
await auth.signOut();
sessionStorage.removeItem('sso_attempted');
showLoginButton();
});
init();

次のステップ