コンテンツにスキップ

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 公式ガイド を参考に設定します。

  1. Google 管理コンソールでの設定

    • Google 管理コンソール →「アプリケーション」→「ウェブアプリケーションとモバイルアプリケーション」
    • 「アプリケーションを追加」→「カスタムSAMLアプリケーションを追加」を選択
    • アプリケーション名を設定(例:Craft Auth)
  2. Google ID プロバイダーの詳細を取得

    • SSO URL → Craft管理画面の「SSO URL」欄に入力
    • エンティティ ID → Craft管理画面の「IdP Entity ID」欄に入力
    • 証明書 → ダウンロードしてファイル内容をCraft管理画面の「X.509証明書」欄に入力
  3. サービスプロバイダーの詳細を設定

    • ACS URL: https://your-project.firebaseapp.com/__/auth/handler
    • エンティティID: https://your-project.firebaseapp.com
    • Name ID形式:「EMAIL」を選択
    • Name ID:「基本情報 > メインのメールアドレス」
  4. 属性マッピングを設定

    email → 基本情報 > メインのメールアドレス
    firstName → 基本情報 > 名
    lastName → 基本情報 > 姓
  5. ユーザーアクセス:「全員に対してON」を選択

Microsoft Entra ID の設定例

Microsoft Entra ID 公式ガイド を参考に設定します。

  1. Azure Portal での設定

    • Azure Portal →「Microsoft Entra ID」→「エンタープライズアプリケーション」
    • 「新しいアプリケーション」→「独自のアプリケーションの作成」
    • アプリケーション名を入力(例:Craft Auth)
  2. SAML 設定

    • 「シングル サインオン」→「SAML」を選択
    • 基本的なSAML構成:
      • 識別子 (エンティティID): https://your-project.firebaseapp.com
      • 応答URL: https://your-project.firebaseapp.com/__/auth/handler
  3. 証明書と認証情報を取得

    • 証明書 (Base64) をダウンロード → ファイル内容をCraft管理画面の「X.509証明書」欄に入力
    • ログイン URL → Craft管理画面の「SSO URL」欄に入力
    • Microsoft Entra 識別子 → Craft管理画面の「IdP Entity ID」欄に入力
  4. ユーザー属性とクレーム(任意)

    email → user.mail または user.userprincipalname
    firstName → user.givenname
    lastName → user.surname
  5. ユーザーとグループ: アクセスするユーザー・グループを追加

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トークンを取得します。signInsignUp で取得した 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,
},
});
}
}