認証基盤の選定は、一度決めると数年は付き合うことになる「やり直しの効きにくい技術選定」の代表格です。料金体系(MAU課金)、B2BのSSO要件、データ主権、実装速度、そしてベンダーロックインのリスク——これらは後から効いてくるのに、PoC段階では見えにくい。
本記事は、Cognito・Auth0・Clerk・Supabase Authの4つを、意思決定者が「自分たちはどれを選ぶべきか」を判断できる評価軸で整理します。私自身、経済産業大臣賞を受賞したB2B SaaSでAWS Cognitoによるエンタープライズ向けSSO(RS256 JWT・OIDC/SAML)を設計し、サーバーレス決済基盤でCognitoのカスタム認証(Lambdaトリガー)を実装し、サブスクリプション基盤ではtokenVersionによる一括失効を備えたステートレスJWTセッション層を自作しました。その実装経験をもとに、ハイプではなく「どの場面でどれを使うか」に絞って書きます。
注:料金は変動が激しく、ここでは具体的な金額を断定しません。各社の**課金モデル(何で課金されるか)**を理解することが、後悔しない選定の鍵です。最新の金額は必ず各社の公式料金ページで確認してください。
1. TL;DR — どれを選ぶか(一行ずつ)
- AWS Cognito — すでにAWS中心で、エンタープライズSSO(SAML/OIDC連携)と細かいカスタム認証が要るなら第一候補。DXは荒削りだが、データ主権とコスト効率、AWSサービスとの統合は強力。
- Auth0 — B2BマルチテナントSaaSで「顧客企業ごとに別のIdPと federation したい」「Organizations単位で管理したい」要件が中心ならベスト。機能は最も成熟、その分プレミアム価格。
- Clerk — Next.jsでB2C/スタートアップを最速で立ち上げたいなら圧倒的。プリビルトUIとミドルウェアの開発体験(DX)が群を抜く。MAUが増えるとコストは上がりやすい。
- Supabase Auth — すでにSupabase(Postgres)を使う、もしくはDBのRow Level Security(RLS)と認証を密結合させたいなら自然な選択。OSSでセルフホスト可能、コスト効率も良い。
迷ったら次の問いから入ってください——「これはB2Bか、B2Cか」「データはどこに置く必要があるか」「すでにどのクラウド/DBに乗っているか」。この3つでほぼ絞れます。
2. 評価軸 — 何を見て決めるか
認証基盤を「機能の多さ」で比べると失敗します。見るべきは次の7軸です。
- コスト(MAU課金) — 4社とも基本はMAU(Monthly Active User/月間アクティブユーザー)課金です。問題は「何をMAUと数えるか」「SSO/MFAなどの上位機能が別課金か」。Supabaseは通常MAUに加えてSSO MAUやサードパーティMAUを区別して課金します。Cognitoは月次のアクティブユーザー数で段階課金し、脅威対策(Advanced Security / Threat Protection)は別ティアです。ユーザー数が線形に増えるB2Cと、ユーザー数は少ないが単価の高いB2Bでは、最適解が逆転します。
- B2B SSO / SAML / OIDC — 顧客企業が「自社のOktaやEntra ID(旧Azure AD)でログインさせたい」と言ってくる世界か。ここはAuth0とCognitoが厚く、Clerk・Supabaseは上位プラン/アドオンで対応します。
- セルフホスト / データ主権 — ユーザー資格情報を自社/自国のインフラ内に置く必要があるか(金融・医療・公共)。SupabaseはOSSでフルセルフホスト可能、Cognitoは自社AWSアカウント内のマネージドサービス。Auth0/Clerkは基本SaaS(Auth0はPrivate Cloud等の上位形態あり)。
- DX(実装速度) — ローンチまでの速さ。Clerkが頭一つ抜け、Supabaseが続き、Auth0は機能が多い分習熟コスト、Cognitoは最も「組み立てる」必要がある。
- カスタム認証 — 標準フローに乗らない要件(独自の多段認証、カードPIN、レガシーDB照合など)。CognitoのLambdaトリガー、Auth0のActionsが代表。
- ベンダーロックイン — 移行のしやすさ。標準(OIDC)準拠か、独自APIに深く依存するか。
- コンプライアンス — SOC 2 / ISO 27001 / HIPAA / PCI DSS等。CognitoはSOC 1–3・PCI DSS・ISO 27001に準拠し、HIPAA-BAA対象(クラウドのセキュリティ責任分界点)です。
3. 4社+αの比較表
| 観点 | AWS Cognito | Auth0 | Clerk | Supabase Auth |
|---|---|---|---|---|
| 主戦場 | AWSネイティブ/エンタープライズCIAM | B2Bエンタープライズ/成熟SaaS | Next.js B2C/スタートアップ | Postgresアプリ/RLS密結合 |
| 課金モデル | MAU段階課金+脅威対策は別ティア | MAUティア(B2C/B2B別プラン、上位は商談) | 無料MAU枠+従量、B2B/SAMLはアドオン | MAU+SSO MAU等を区別、無料枠あり |
| SAML/OIDC連携 | 標準対応(IdPにもSPにもなれる) | 最も厚い(Enterprise Connections) | 上位プランのアドオン | Pro以上でSAML、SSO MAU課金 |
| セルフホスト | 自社AWS内マネージド | 基本SaaS(上位形態あり) | SaaSのみ | OSS・フルセルフホスト可 |
| カスタム認証 | Lambdaトリガーで自在 | Actionsで拡張 | 限定的(用途特化) | DB/Edge Functionsで拡張 |
| Next.js DX | 普通(要組み立て) | 良 | 最高(専用ミドルウェア) | 良(SSRヘルパ充実) |
| DB/RLS統合 | なし(別途設計) | なし | なし | あり(Postgres RLSと一体) |
| トークン | RS256 JWT(ID/access/refresh) | JWT | JWT | JWT(access/refresh) |
| ロックイン度 | 中(OIDC準拠だがAWS依存) | 中〜高(機能依存しやすい) | 中〜高(UI/SDK依存) | 低〜中(OSS・Postgres標準) |
セルフホスト系・自作という選択肢(と、なぜ避けるべきか)
- Keycloak / Ory — OSSのIdP/認可基盤。データ主権が絶対要件で、運用チームがある組織には有力。ただし可用性・パッチ・スケールの運用責任を自分で負うことになり、TCO(総保有コスト)はマネージドより高くつくことが多い。
- 完全自作(roll-your-own auth) — 原則として勧めません。パスワードハッシュ、トークン署名・検証、リフレッシュローテーション、セッション失効、アカウント列挙対策、レート制限、MFA、監査ログ……ここで一箇所でも間違えると、それは即セキュリティインシデントです。認証は「車輪の再発明」がそのまま脆弱性になる領域です。後述するように、私もJWTセッション層は自作しましたが、それは「ユーザーディレクトリ」ではなく「セッション/失効の制御層」に限定し、署名検証など枯れた部分はライブラリに任せています。自作するなら責務を最小に切るのが鉄則です。
4. プロバイダ別ディープダイブ
4.1 AWS Cognito — エンタープライズ&AWSネイティブ
強み。 User Poolは単体で完結したユーザーディレクトリかつOIDCプロバイダで、ローカルユーザーにもフェデレーションユーザーにも統一されたJWT(RS256署名のID/access/refreshトークン)を発行します。OktaやADFS、Entra IDといったワークフォースIdPとSAML 2.0/OIDCで連携でき、CognitoがSP(サービスプロバイダ)として外部の主張を受け取り、自前のトークン形式に正規化してくれる——つまりアプリは「Cognitoのトークン1種類」だけ理解すればよい。これがエンタープライズSSOで効きます。MFAはTOTPとSMSに対応。Identity PoolはAWS STSと連携し、認証済みユーザーにS3やDynamoDBへの一時クレデンシャルを発行できる——AWSリソースへの認可までを一気通貫で扱える点はCognito独自の強みです。
弱み。 ホスト型UI(Managed Login)はカスタマイズの自由度に癖があり、凝ったデザインには向きません。コンソールの体験やドキュメントの導線も、Clerkのような「数分で動く」洗練さはない。属性スキーマの変更がしづらい等、運用の地雷もあります。
理想の場面。 すでにAWS中心の組織、エンタープライズSSO要件、AWSリソースへの認可連携、そしてカスタム認証が要るとき。
カスタム認証(Lambdaトリガー)。 Cognitoの真価はここにあります。DefineAuthChallenge / CreateAuthChallenge / VerifyAuthChallengeResponseの3トリガーで、標準にない多段フローを組めます。私が決済基盤で実装したのは、まさにこのトリガー連携によるカードPIN認証でした。レガシーDBのユーザーをCognitoへLazy移行するUserMigrationトリガーも実用的です(後述)。
4.2 Auth0 — B2Bエンタープライズの王道
強み。 Organizations機能が秀逸で、B2Bマルチテナント——「顧客企業ごとに、それぞれのブランドとフェデレーションログインを提供する」——を正面からサポートします。顧客企業が自社のIdPを持ち込むEnterprise Connections(SAML、OIDC、Entra ID等)は最も厚く、B2B APIへのマシン間(M2M)アクセスや、顧客が自社組織を自己管理するためのOrganizations APIまで揃います。拡張はActions(認証フローの各ポイントにコードを差し込むサーバーレス関数)で行い、Universal Loginで一貫したログイン体験を提供します。
弱み。 機能が成熟している分、プレミアム価格になりやすい。Organizationsやエンタープライズ機能の可用性はプランや個別契約に依存し、上位は商談ベースです。小規模B2Cにはオーバースペックでコスト過大になりがち。
理想の場面。 顧客が大企業で「自社のIdPでログインさせろ」が当たり前の世界、テナント単位の管理・課金・ブランディングが必要なB2B SaaS。
4.3 Clerk — Next.js DXの王者
強み。 Next.jsとの統合が圧倒的です。clerkMiddleware()でルート保護を宣言的に書け、<SignIn /> <UserButton /> <OrganizationSwitcher />といったプリビルトUIで、認証画面を数分で本番品質に持っていけます。Organizations(B2B)、セッション管理、MFA、ボット対策、Webhookも揃い、B2C/B2Bの課金機能まで提供します。
弱み。 UI/SDKへの依存が深く、後から別基盤へ移すのは相応の作業になります。MAUが増えると料金は上がりやすく、B2BのSAML SSO等は上位/アドオン。データはClerk側にあり、データ主権要件には合いません。
理想の場面。 Next.jsでB2Cプロダクトやスタートアップを最短で立ち上げたいとき。「認証で消耗せず、プロダクトに集中したい」を最も叶えてくれます。
4.4 Supabase Auth — Postgres+RLSとの相乗効果
強み。 ユーザーはPostgresのauth.usersに格納され、発行されるJWTのクレーム(sub等)をRow Level Security(RLS)ポリシーで直接参照できます。つまり「認証」と「行レベルの認可」が同じDBの中で地続きになる。auth.uid()を使ったRLSで、アプリコードを書かずにテナント分離やユーザー単位のアクセス制御が実現します。パスワード、Magic Link、OTP、19以上のソーシャルプロバイダ、任意のOAuth2/OIDCプロバイダ連携に対応。OSSでフルセルフホスト可能な点はデータ主権の観点で大きい。
弱み。 SAML SSOはPro以上でSSO MAU課金。Cognito/Auth0ほどのエンタープライズ統合の厚みはまだなく、大規模ワークフォースSSOが主戦場なら設計上の工夫が要ります。
理想の場面。 すでにSupabase/Postgresを使う、RLSで認可を完結させたい、コストを抑えたい、将来セルフホストの逃げ道を残したいとき。
5. 実装スニペット
5.1 プロバイダ非依存のJWT検証ゲート(Next.js 16 ミドルウェア)
どの基盤を使っても、**「署名を検証したJWTだけを信用する」**のは共通の鉄則です。署名を検証せずクレームを読むのは、改ざんを受け入れるのと同じこと。ここではjoseでJWKS(公開鍵セット)から署名を検証する、最小のゲートを示します。RS256前提(Cognito等)で、公開鍵は発行者のJWKSエンドポイントから取得・キャッシュします。
// lib/auth/verify.ts
import { createRemoteJWKSet, jwtVerify, type JWTPayload } from "jose";
// 環境変数で発行者を切り替え(プロバイダ非依存)。
// Cognito: https://cognito-idp.<region>.amazonaws.com/<userPoolId>
const ISSUER = process.env.AUTH_ISSUER_URL!;
const AUDIENCE = process.env.AUTH_AUDIENCE; // Cognitoのaccess tokenはaud不在のためclient_idで別途検証
// JWKSはモジュールスコープでキャッシュ(リクエスト毎に再取得しない)。
const JWKS = createRemoteJWKSet(new URL(`${ISSUER}/.well-known/jwks.json`));
export async function verifyToken(token: string): Promise<JWTPayload> {
// 署名・iss・exp・nbfを検証。失敗すれば例外。決して握りつぶさない。
const { payload } = await jwtVerify(token, JWKS, {
issuer: ISSUER,
audience: AUDIENCE,
algorithms: ["RS256"], // alg混同攻撃を防ぐため許可アルゴリズムを固定
});
return payload;
}
// proxy.ts(Next.js 16のミドルウェア。15以前は middleware.ts)
import { NextResponse, type NextRequest } from "next/server";
import { verifyToken } from "@/lib/auth/verify";
const PROTECTED = [/^\/app(?:\/|$)/, /^\/api\/(?!auth\/)/];
export async function proxy(req: NextRequest) {
const needsAuth = PROTECTED.some((re) => re.test(req.nextUrl.pathname));
if (!needsAuth) return NextResponse.next();
// トークンはhttpOnly Cookieから読む(localStorageは使わない。後述)。
const token = req.cookies.get("access_token")?.value;
if (!token) return redirectToLogin(req);
try {
const claims = await verifyToken(token);
// 検証済みクレームを下流へ。生トークンは渡さない。
const headers = new Headers(req.headers);
headers.set("x-user-id", String(claims.sub));
return NextResponse.next({ request: { headers } });
} catch {
// 検証失敗=改ざん/失効/期限切れ。一律でログインへ。
return redirectToLogin(req);
}
}
function redirectToLogin(req: NextRequest) {
const url = new URL("/login", req.url);
url.searchParams.set("next", req.nextUrl.pathname);
return NextResponse.redirect(url);
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
ポイントは3つ。(1) JWKSはキャッシュ(毎リクエストで公開鍵を取りに行かない)。(2) algorithmsを固定してalg: noneやHS256混同攻撃を封じる。(3) 検証例外は必ずログインへ倒す——「とりあえず通す」が最悪です。
5.2 プロバイダ固有例:Clerk(Next.js)
公式の作法に乗ると、検証ロジックは基盤側が引き受けてくれます。Clerkなら宣言的にこう書けます。
// proxy.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isProtected = createRouteMatcher(["/app(.*)", "/api(.*)"]);
export default clerkMiddleware(async (auth, req) => {
// protect()は未認証なら自動でサインインへリダイレクト。
if (isProtected(req)) await auth.protect();
});
export const config = {
matcher: [
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|png|jpe?g|svg|woff2?)).*)",
"/(api|trpc)(.*)",
],
};
5.1の手組みと比べると一目瞭然です。B2C/Next.jsでスピードが正義なら5.2、データ主権やマルチプロバイダ対応で抽象境界を自分で持ちたいなら5.1——この使い分けが選定そのものを映しています。
6. 移行(migration)の現実
「既存ユーザーをどう移すか」は、買い手が最も知りたいのに語られない論点です。ユーザーが10万人いれば、パスワードハッシュは移せない/移すべきでないという壁に必ずぶつかります。bcrypt等のハッシュは一方向で、平文は誰も持っていないからです。
現実的な戦略は次の3つです。
(1) Lazy migration(遅延移行)が本命。 新基盤に空のディレクトリを用意し、ユーザーが次にログインした瞬間に旧システムへ照合 → 成功したら新基盤にユーザーを作成、という流れにします。CognitoならUserMigration Lambdaトリガーがこの用途そのものです。
// Cognito UserMigration トリガー(Lazy migration)
import type { UserMigrationTriggerEvent } from "aws-lambda";
import { verifyAgainstLegacy } from "./legacy"; // 旧DBへの照合(外部入力として厳格に扱う)
export async function handler(
event: UserMigrationTriggerEvent,
): Promise<UserMigrationTriggerEvent> {
if (event.triggerSource === "UserMigration_Authentication") {
// 旧システムでパスワードを検証。ここで初めて平文を扱える。
const user = await verifyAgainstLegacy(event.userName, event.request.password);
if (!user) throw new Error("Bad credentials"); // 認証失敗は曖昧なメッセージで
// 検証できたら属性をCognitoへ移植。以後はCognitoが正となる。
event.response.userAttributes = {
email: user.email,
email_verified: "true",
};
event.response.finalUserStatus = "CONFIRMED";
event.response.messageAction = "SUPPRESS"; // 「ようこそ」メールを抑止
}
return event;
}
(2) Dual-run(並行稼働)でダウンタイムを避ける。 一斉切替(big bang)は事故ります。読み取りは新基盤、書き込みは両系——のように一定期間二重に動かし、移行率をメトリクスで監視しながら旧系を畳む。私がCognitoでエンタープライズSSOを設計した際も、フェデレーション経路を先に通し、ローカルユーザーをLazyに寄せる順序でダウンタイムをゼロに保つ設計を採りました。
(3) どうしてもハッシュごと移す場合。 Auth0など一部はbcryptハッシュのバルクインポートに対応します(同じアルゴリズム・コストである必要あり)。これが使えるなら一括移行も選べますが、対象アルゴリズムの制約を必ず確認してください。
移行で本当に守るべきは**「ユーザーにパスワード再設定を強制しない」**こと。それは離脱の最大要因です。Lazy migrationはこの体験を壊さずに移せる唯一の現実解です。
7. セキュリティ要点
認証基盤を「選んだ」だけでは安全になりません。実装の細部で決まります。
- トークンの保管場所:httpOnly Cookie 一択。 access/refreshトークンを
localStorageに置くと、XSS一発で全部抜かれます。httpOnly + Secure + SameSiteのCookieに入れ、JSから読めなくするのが基本。BFFパターン(ブラウザはCookieだけ持ち、トークンはサーバー側で扱う)にすればさらに堅い。 - リフレッシュトークンのローテーション。 リフレッシュのたびに新しいリフレッシュトークンを発行し、旧トークンを無効化する(rotation)。加えてreuse detection——一度使われた古いトークンが再提示されたら「盗まれた」と判断し、そのファミリ全体を失効させる。
- セッション失効(tokenVersionパターン)。 ステートレスJWTの弱点は「発行後の即時失効が難しい」こと。私がサブスクリプション基盤で採ったのは、ユーザーレコードに
tokenVersion整数を持ち、JWTにも同じ値を埋める方式です。パスワード変更・不正検知・一括ログアウト時にtokenVersionをインクリメントすれば、既存トークンは検証時にバージョン不一致で全て無効化されます。DBの数値ひとつで「全デバイス即ログアウト」が効く、運用に効くパターンです。
// tokenVersionによる一括失効(検証側)
async function assertNotRevoked(claims: { sub: string; tokenVersion: number }) {
const current = await getUserTokenVersion(claims.sub); // DBの現在値
if (claims.tokenVersion !== current) {
throw new Error("Session revoked"); // 不一致=失効済み。即拒否。
}
}
- アカウント列挙(account enumeration)防止。 「そのメールは登録されていません」と「パスワードが違います」を区別して返さない。サインアップやパスワードリセットでも、存在の有無を応答時間やメッセージで漏らさない(リセットは常に「送信しました」と返す)。OTPはHMAC-SHA256で署名・有効期限付き・試行回数制限を必ず付ける——これは私がサブスクリプション基盤のOTP(HMAC-SHA256)で実装した通りです。
- 共通の原則。 すべての外部入力(IdPの主張、リダイレクトURL、ステート)を検証する。OIDCではPKCEとstate/nonce検証を省略しない。エラーは握りつぶさず、認証失敗は一律で曖昧なメッセージに倒す。
8. FAQ
Q. 結局、最初に問うべきことは何ですか? A. 「B2BかB2Cか」「データをどこに置く必要があるか」「既存のクラウド/DBは何か」の3つです。B2Bエンタープライズ+外部IdP連携ならAuth0かCognito、Next.js B2Cの速度重視ならClerk、Postgres/RLS前提ならSupabase——ここでほぼ決まります。
Q. 自作(roll-your-own)は本当にダメですか? A. ユーザーディレクトリやトークン署名・検証を丸ごと自作するのは勧めません。脆弱性の温床です。ただし、セッション失効やOTPのような「制御層」を、枯れたライブラリの上で薄く自作するのは現実的で、私もそうしています。責務を最小に切るのが条件です。
Q. MAU課金で予算が読めません。どう見積もればいい? A. 「アクティブの定義」と「上位機能(SSO/MFA/Organizations)が別課金か」を最初に確認してください。B2Cはユーザー数が線形に伸びるのでMAU単価が、B2Cと逆にB2Bはユーザー数より「テナント/SSO機能の単価」が効きます。無料枠の崖(突然の段階課金)も要チェックです。
Q. Cognitoはデザインの自由度が低いと聞きます。 A. ホスト型UIは確かに癖があります。割り切ってホスト型UIを使うか、Cognitoのユーザープール APIを直接叩いて自前UIを作るかの二択です。後者は自由ですが、その分セキュリティ実装の責任を自分で負います。
Q. SAML/OIDCに準拠していれば、後から乗り換えは簡単ですか? A. フェデレーション部分(外部IdP連携)はOIDC準拠なら比較的移しやすい一方、ユーザーディレクトリ・パスワード・独自フローへの依存が深いほど移行は重くなります。だからこそ§6のLazy migrationが効きます。標準準拠は「ロックインを下げる投資」と捉えてください。
Q. 一人+生成AIで、ここまでの認証基盤を本当に作れるのですか? A. 作れます。私は一人 × 生成AI(Claude Code)で開発を加速しつつ、認証のような重要領域には必ず人間の検証ゲートを置いています。署名検証・失効・列挙対策のテストを先に書き、AIが書いたコードをそのテストで縛る。速さと安全はトレードオフではなく、検証可能性で両立させます。
9. ご相談ください
認証基盤の選定・実装・移行は、「やり直しの効かない技術選定」です。私は経済産業大臣賞を受賞したB2B SaaSでCognitoによるエンタープライズSSO(RS256 JWT・OIDC/SAML)を、決済基盤でCognitoカスタム認証(Lambdaトリガー)を、サブスクリプション基盤でtokenVersion一括失効を備えたJWTセッション層を設計・実装してきました。
- 選定:B2B/B2C・データ主権・コスト・既存スタックから、最適な基盤を比較レポートで提示します。
- 実装:セキュアなトークン運用(httpOnly Cookie・ローテーション・失効)、SSO/MFA、RLS連携まで本番品質で。
- 移行:Lazy migration・dual-runで、ユーザーにパスワード再設定を強制せず、ダウンタイムを出さない移行を設計します。
一人 × 生成AI(Claude Code)で、速く・安く・しかし検証ゲートを通して安全に。認証まわりでお困りなら、お気軽にご相談ください。