Craft Auth で認証機能を実装する
Craft Auth はCraft Functions内で利用できる認証機能です。次の機能を提供します。
- メールアドレスとパスワードを使用したユーザー認証
- 新規ユーザー登録
- トークン検証
- ロールベースアクセス制御
- 外部プロバイダー認証(OIDC、SAML)
基本的な使い方
Craft Auth API
Craft AuthのコアAPIを使用したシンプルな認証実装です。
ユーザーのサインイン
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const { email, password } = req.body; const result = await auth.signIn({ email, password });
return res.status(200).json({ message: "OK", result, }); } catch (error) { return res.status(500).json({ message: "Error", result: { message: error.message, status: error.status || null, }, }); }}
新規ユーザーの登録
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const { email, password } = req.body; const result = await auth.signUp({ email, password });
return res.status(200).json({ message: "OK", result, }); } catch (error) { return res.status(500).json({ message: "Error", result: { message: error.message, status: error.status || null, }, }); }}
トークンの検証
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const { idToken } = req.body; const result = await auth.verify({ idToken });
return res.status(200).json({ message: "OK", result, }); } catch (error) { return res.status(500).json({ message: "Error", result: { message: error.message, status: error.status || null, }, }); }}
パスワードリセット
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const { email } = req.body; const result = await auth.sendPasswordResetEmail({ email });
return res.status(200).json({ message: "Password reset email sent", result, }); } catch (error) { return res.status(500).json({ message: "Error", result: { message: error.message, status: error.status || null, }, }); }}
トークンのリフレッシュ
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const { refreshToken } = req.body; const result = await auth.getIdToken({ refreshToken });
return res.status(200).json({ message: "Token refreshed", result, }); } catch (error) { return res.status(500).json({ message: "Error", result: { message: error.message, status: error.status || null, }, }); }}
ロールの割り当て
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const { uid, roles } = req.body; const result = await auth.assignRoles({ uid, roles });
return res.status(200).json({ message: "Roles assigned successfully", result, }); } catch (error) { return res.status(500).json({ message: "Error", result: { message: error.message, status: error.status || null, }, }); }}
Firebase Client SDK との連携
Craft AuthのAPIに加えて、Firebase Client SDKを直接使用してクライアントサイドで認証も可能です。この方法では、より柔軟な認証フローを実装できます。
基本設定
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";import { getAuth, signInWithPopup, signOut, onAuthStateChanged,} from "https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js";
const firebaseConfig = { apiKey: "your-api-key", authDomain: "your-project.firebaseapp.com", projectId: "your-project-id", storageBucket: "your-project.appspot.com", messagingSenderId: "123456789", appId: "your-app-id",};
const app = initializeApp(firebaseConfig);const auth = getAuth(app);
Craft Auth API との連携
Firebase Client SDKで取得したIDトークンをCraft Auth APIで活用する例です。
onAuthStateChanged(auth, async (user) => { if (user) { try { const idToken = await user.getIdToken();
const craftAuthResult = await fetch( "https://xxxxxx.yyy.karte.io/functions/zzzzzzzzzzz", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ idToken: idToken, }), } );
const result = await craftAuthResult.json();
if (result.message === "OK") { console.log("Craft Auth検証成功:", result.result); const userRoles = result.result.user.roles; handleUserRoles(userRoles); } else { console.error("Craft Auth検証失敗:", result); } } catch (error) { console.error("検証処理エラー:", error); } }});
function handleUserRoles(roles) { if (roles && roles.length > 0) { console.log("ユーザーロール:", roles); roles.forEach((role) => { document .querySelector(`[data-role="${role}"]`) ?.classList.remove("hidden"); }); }}
外部プロバイダー認証
OIDC 認証
Google、Microsoft等のOIDCプロバイダーを使用した認証を実装できます。
プロバイダー設定
import { OAuthProvider } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js";
const providers = { google: (() => { const provider = new OAuthProvider("oidc.google"); provider.addScope("profile"); provider.addScope("email"); provider.setCustomParameters({ prompt: "select_account", }); return provider; })(),
microsoft: (() => { const provider = new OAuthProvider("oidc.microsoft"); provider.addScope("openid"); provider.addScope("profile"); provider.addScope("email"); provider.setCustomParameters({ prompt: "select_account", }); return provider; })(),
github: (() => { const provider = new OAuthProvider("oidc.github"); provider.addScope("user:email"); return provider; })(),};
OIDC 認証の実装
async function signInWithProvider(providerKey) { const providerNames = { google: "Google", microsoft: "Microsoft", github: "GitHub", };
const providerName = providerNames[providerKey] || providerKey;
try { const result = await signInWithPopup(auth, providers[providerKey]); const idToken = await result.user.getIdToken();
console.log(`${providerName}認証成功:`, { user: result.user, idToken: idToken, });
return { result, idToken }; } catch (error) { let errorMessage = "認証に失敗しました"; let details = "";
switch (error.code) { case "auth/popup-closed-by-user": errorMessage = "サインインがキャンセルされました"; break; case "auth/popup-blocked": errorMessage = "ポップアップがブロックされました"; details = "ブラウザの設定でポップアップを許可してください"; break; case "auth/account-exists-with-different-credential": errorMessage = "別の認証方法で既に登録されたアカウントです"; details = "他の認証方法でサインインしてください"; break; case "auth/unauthorized-continue-uri": errorMessage = "リダイレクトURIの設定に問題があります"; details = "Firebase Consoleの設定を確認してください"; break; case "auth/invalid-oauth-provider": errorMessage = "OAuth設定に問題があります"; details = `${providerName}認証の設定を確認してください`; break; default: errorMessage = `${providerName}認証エラー`; details = `エラーコード: ${error.code}`; }
console.error(`${errorMessage}: ${details}`, error); alert(`${errorMessage}${details ? `\n${details}` : ""}`); throw error; }}
try { const { result, idToken } = await signInWithProvider("google");} catch (error) {}
SAML 認証
エンタープライズ環境でのSAML 2.0プロバイダーを使用した認証を実装できます。
SAML プロバイダーの設定
1. 管理画面での設定
「認証」>「プロバイダー」>「SAMLプロバイダー」から新規作成します。たとえば、次のような項目を設定します。
- プロバイダー ID:
saml.
で始まる一意のID(例:saml.company
) - 表示名: 管理画面での表示用名称
- IdP Entity ID: Identity ProviderのEntity ID
- SSO URL: Single Sign-OnエンドポイントURL
- X.509証明書: Identity Providerの公開鍵証明書
- RP Entity ID:
https://your-project.firebaseapp.com
(自動設定)
2. Identity Provider 側の設定
Google Workspace の設定例
Google Workspace 公式ガイド を参考に設定します。
-
Google 管理コンソールでの設定
- Google 管理コンソール →「アプリケーション」→「ウェブアプリケーションとモバイルアプリケーション」
- 「アプリケーションを追加」→「カスタムSAMLアプリケーションを追加」を選択
- アプリケーション名を設定(例:Craft Auth)
-
Google ID プロバイダーの詳細を取得
- SSO URL → Craft管理画面の「SSO URL」欄に入力
- エンティティ ID → Craft管理画面の「IdP Entity ID」欄に入力
- 証明書 → ダウンロードしてファイル内容をCraft管理画面の「X.509証明書」欄に入力
-
サービスプロバイダーの詳細を設定
- ACS URL:
https://your-project.firebaseapp.com/__/auth/handler
- エンティティID:
https://your-project.firebaseapp.com
- Name ID形式:「EMAIL」を選択
- Name ID:「基本情報 > メインのメールアドレス」
- ACS URL:
-
属性マッピングを設定
email → 基本情報 > メインのメールアドレスfirstName → 基本情報 > 名lastName → 基本情報 > 姓 -
ユーザーアクセス:「全員に対してON」を選択
Microsoft Entra ID の設定例
Microsoft Entra ID 公式ガイド を参考に設定します。
-
Azure Portal での設定
- Azure Portal →「Microsoft Entra ID」→「エンタープライズアプリケーション」
- 「新しいアプリケーション」→「独自のアプリケーションの作成」
- アプリケーション名を入力(例:Craft Auth)
-
SAML 設定
- 「シングル サインオン」→「SAML」を選択
- 基本的なSAML構成:
- 識別子 (エンティティID):
https://your-project.firebaseapp.com
- 応答URL:
https://your-project.firebaseapp.com/__/auth/handler
- 識別子 (エンティティID):
-
証明書と認証情報を取得
- 証明書 (Base64) をダウンロード → ファイル内容をCraft管理画面の「X.509証明書」欄に入力
- ログイン URL → Craft管理画面の「SSO URL」欄に入力
- Microsoft Entra 識別子 → Craft管理画面の「IdP Entity ID」欄に入力
-
ユーザー属性とクレーム(任意)
email → user.mail または user.userprincipalnamefirstName → user.givennamelastName → user.surname -
ユーザーとグループ: アクセスするユーザー・グループを追加
SAML 認証の実装
import { SAMLAuthProvider } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js";
const samlProviders = { google: new SAMLAuthProvider("saml.google"), microsoft: new SAMLAuthProvider("saml.microsoft"), company: new SAMLAuthProvider("saml.company"),};
async function signInWithSAMLProvider(providerKey) { const providerNames = { google: "Google SAML", microsoft: "Microsoft SAML", company: "Company SAML", };
const providerName = providerNames[providerKey] || providerKey;
try { const result = await signInWithPopup(auth, samlProviders[providerKey]); const idToken = await result.user.getIdToken();
console.log(`${providerName}認証成功:`, { user: { uid: result.user.uid, email: result.user.email, displayName: result.user.displayName, }, idToken: idToken, });
return result; } catch (error) { let errorMessage = "認証に失敗しました";
switch (error.code) { case "auth/popup-closed-by-user": errorMessage = "サインインがキャンセルされました"; break; case "auth/popup-blocked": errorMessage = "ポップアップがブロックされました"; break; case "auth/account-exists-with-different-credential": errorMessage = "別の認証方法で既に登録されたアカウントです"; break; default: errorMessage = `${providerName}認証エラー: ${error.message}`; }
console.error(errorMessage, error); alert(errorMessage); throw error; }}
ロール機能
Craft Authでは、ユーザーにロール(役割)を割り当てて、ファンクション内でロールベースのアクセス制御を実装できます。
ロール情報の取得
認証APIの応答にロール情報が含まれます。
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const { email, password } = req.body; const authResult = await auth.signIn({ email, password });
const userRoles = authResult.user.roles || [];
return res.status(200).json({ message: "認証成功", user: authResult.user, hasRoles: userRoles.length > 0, roles: userRoles, }); } catch (error) { return res.status(500).json({ message: "認証エラー", error: error.message, }); }}
ロールベースアクセス制御の実装
トークン検証とロール情報を使って権限制御を実装できます。
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith("Bearer ")) { return res.status(401).json({ error: "Authorization token required" }); }
const idToken = authHeader.replace("Bearer ", "");
const authResult = await auth.verify({ idToken }); const userRoles = authResult.user.roles || [];
if (userRoles.includes("admin")) { return res.json({ message: "Admin access granted", data: await getAdminData(), }); } else if (userRoles.includes("editor")) { return res.json({ message: "Editor access granted", data: await getEditorData(), }); } else if (userRoles.includes("viewer")) { return res.json({ message: "Viewer access granted", data: await getPublicData(), }); } else { return res.status(403).json({ error: "No valid role assigned", message: "Please contact administrator to assign appropriate roles", }); } } catch (error) { return res.status(500).json({ error: "Authentication verification failed", message: error.message, }); }}
複数ロールの管理
ユーザーには複数のロールを同時に割り当てることができます。
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const idToken = req.headers.authorization?.replace("Bearer ", ""); const authResult = await auth.verify({ idToken }); const userRoles = authResult.user.roles || [];
const hasAdminRole = userRoles.includes("admin"); const hasEditorRole = userRoles.includes("editor");
const canEdit = userRoles.includes("admin") || userRoles.includes("editor"); const canViewReports = userRoles.includes("admin") || userRoles.includes("analyst");
const response = { user: authResult.user, permissions: { canEdit, canViewReports, canManageUsers: hasAdminRole, canCreateContent: hasEditorRole || hasAdminRole, }, };
return res.json(response); } catch (error) { return res.status(500).json({ error: error.message }); }}
ロール階層の実装例
ロールに階層を持たせた権限管理も実装できます。
function hasPermission(userRoles, requiredRole) { const roleHierarchy = { viewer: 1, editor: 2, admin: 3, };
const userLevel = Math.max( ...userRoles.map((role) => roleHierarchy[role] || 0) ); const requiredLevel = roleHierarchy[requiredRole] || 0;
return userLevel >= requiredLevel;}
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const idToken = req.headers.authorization?.replace("Bearer ", ""); const authResult = await auth.verify({ idToken }); const userRoles = authResult.user.roles || [];
if (!hasPermission(userRoles, "editor")) { return res.status(403).json({ error: "Insufficient permissions", required: "editor or higher", }); }
return res.json({ message: "Access granted", data: await getEditableData(), }); } catch (error) { return res.status(500).json({ error: error.message }); }}
リファレンス
メソッド
signIn
メールアドレスとパスワードを使用してユーザーを認証します。
インターフェース:
auth.signIn({ email: string, // メールアドレス password: string, // パスワード(6文字以上)});
戻り値:
{ user: { uid: string, // ユーザーID email: string, // メールアドレス roles?: string[] // ユーザーのロール(存在する場合) }, idToken: string, // 認証トークン refreshToken: string, // リフレッシュトークン(トークン更新用) expiresIn: string // トークンの有効期限(秒)}
使用例を以下に示します。
const result = await auth.signIn({ email: "user@example.com", password: "password123",});// refreshTokenは後でトークン更新時に使用const refreshToken = result.refreshToken;
signUp
新規ユーザーを登録します。
インターフェース:
auth.signUp({ email: string, // メールアドレス password: string, // パスワード(6文字以上)});
戻り値:
{ user: { uid: string, // ユーザーID email: string, // メールアドレス roles?: string[] // ユーザーのロール(存在する場合) }, idToken: string, // 認証トークン refreshToken: string, // リフレッシュトークン(トークン更新用) expiresIn: string // トークンの有効期限(秒)}
使用例を以下に示します。
const result = await auth.signUp({ email: "newuser@example.com", password: "securePassword123",});// refreshTokenは後でトークン更新時に使用const refreshToken = result.refreshToken;
verify
認証トークンを検証し、ユーザー情報を取得します。
インターフェース:
auth.verify({ idToken: string, // 検証するIDトークン});
戻り値:
{ user: { uid: string, // ユーザーID email: string, // メールアドレス roles?: string[] // ユーザーのロール(存在する場合) }, idToken: string // 検証済みのIDトークン}
使用例を以下に示します。
const result = await auth.verify({ idToken: "eyJhbGciOiJ...",});
sendPasswordResetEmail
指定したメールアドレスにパスワードリセットメールを送信します。
インターフェース:
auth.sendPasswordResetEmail({ email: string, // パスワードリセットメールを送信するメールアドレス});
戻り値:
{ success: boolean, // 成功フラグ(true) message: string, // 処理結果メッセージ email: string // パスワードリセットメールを送信したメールアドレス}
使用例を以下に示します。
const result = await auth.sendPasswordResetEmail({ email: "user@example.com",});
assignRoles
指定したユーザーにロールを割り当てます。管理者権限が必要です。
インターフェース:
auth.assignRoles({ uid: string, // ロールを割り当てるユーザーのID roles: string[] // 割り当てるロールの配列})
戻り値:
{ uid: string, // ユーザーID roles: string[] // 割り当てられたロールの配列}
使用例を以下に示します。
const result = await auth.assignRoles({ uid: "user123", roles: ["admin", "editor"],});
getIdToken
リフレッシュトークンを使用して新しいIDトークンを取得します。signIn
や signUp
で取得した refreshToken
を使用して、期限切れになったIDトークンを更新できます。
インターフェース:
auth.getIdToken({ refreshToken: string, // signInやsignUpで取得したリフレッシュトークン});
戻り値:
{ idToken: string, // 新しい認証トークン refreshToken: string, // 新しいリフレッシュトークン expiresIn: string, // トークンの有効期限(秒) tokenType: string, // トークンタイプ(通常は "Bearer") uid: string, // ユーザーID roles?: string[] // ユーザーのロール(存在する場合)}
使用例を以下に示します。
// signInやsignUpで取得したrefreshTokenを保存しておくconst loginResult = await auth.signIn({ email: "user@example.com", password: "password123",});const savedRefreshToken = loginResult.refreshToken;
// IDトークンの期限が切れたら、refreshTokenで新しいトークンを取得const newTokens = await auth.getIdToken({ refreshToken: savedRefreshToken,});
エラーハンドリング
認証処理でエラーが発生した場合、次のようなエラー情報を含むオブジェクトが返却されます。
{ error: { message: "エラーメッセージ", status: HTTPステータスコード }}
主なエラーケース
パラメータエラーには、たとえば次のようなケースがあります。
'email is required'
: メールアドレスが未入力。'invalid email'
: 無効なメールアドレス形式。'password is required'
: パスワードが未入力。'password must be at least 6 characters'
: パスワードが6文字未満。'idToken is required'
: IDトークンが未入力。
認証エラーには、たとえば次のようなケースがあります。
'Authentication failed'
: 認証に失敗(メールアドレスまたはパスワードが正しくない)。
サーバーエラーには、たとえば次のようなケースがあります。
'Service Unavailable'
: 認証サービスが利用できない。'Internal Server Error'
: 内部サーバーエラー。
推奨されるエラーハンドリングパターン
export default async function (data, { MODULES }) { const { req, res } = data; const { auth } = MODULES;
try { const { email, password } = req.body; const result = await auth.signIn({ email, password });
return res.status(200).json({ message: "OK", result, }); } catch (error) { const statusCode = error.status || 500; return res.status(statusCode).json({ message: "Error", result: { message: error.message, status: error.status || null, }, }); }}