تخطَّ إلى المحتوى الرئيسي
友田 陽大
Authentication & authorization
AWS
Cognito
SAML
OIDC
SSO
認証
パスワードレス
Passkey
Azure AD
Okta
Terraform
セキュリティ

AWS Cognito 企業SSO実装ガイド 2026|SAML/OIDC・パスワードレス認証

AWS Cognitoで企業SSO(Azure AD/Okta/GoogleのSAML・OIDC連携)とパスワードレス認証を本番実装する2026年版。機能プラン・マルチテナント・JWT検証・SAML/OIDCの攻撃対策・内製vs SaaSの判断まで、実コードと公式ドキュメント準拠で解説。

Published
Last updated
Updated
Reading time
33 min read
Author
友田 陽大
مشاركة
المحتويات

「エンタープライズ顧客から『Azure AD(Microsoft Entra ID)でのSSOに対応してほしい』と要望が来たが、実装方法が分からない」

「AWS CognitoでSAML/OIDC統合を試みたが、設定項目が多すぎて挫折した」

「マルチテナントSaaSで、顧客ごとに異なるIdentity Providerを統合する方法が知りたい」

「2024年以降に出た『パスキー』『選択式認証』『料金プラン(Lite/Essentials/Plus)』が、古い記事と食い違っていて混乱している」

B2B SaaS開発者が直面するこれらの課題。私自身、経済産業大臣賞を受賞したB2Bサブスクリプション SaaS(木材流通DX)の開発で、Cognitoによる認証基盤をRS256のJWT検証込みで設計・運用しました。同じ壁にぶつかった経験から、この記事を書いています。

この記事は AWS公式ドキュメント(2026年6月時点)に忠実 に、かつ「どの場面で・どう使うか」を実コード付きで解説します。各セクションには参照した公式ドキュメントのリンクを明記しているので、自分の環境で一次情報を確認しながら実装を進められます。

この記事で扱う範囲

  1. 2026年のCognito全体像(料金プラン・マネージドログイン)
  2. 認証方式の選び方(フェデレーション / パスワードレス / カスタムフロー)
  3. SAML(Azure AD)・OIDC(Google / Okta)連携の実装
  4. カスタム認証フロー(3つのLambdaトリガー)
  5. USER_AUTH 選択式・パスワードレス認証(パスキー / OTP)
  6. マルチテナント設計とトークンへのテナント注入(Pre Token Generation V2.0)
  7. JWT検証(aws-jwt-verify)とトラブルシューティング

企業SSOの原理と歴史:なぜ SAML と OIDC が今も併存するのか

実装に入る前に、10分だけ「なぜこの2つの規格が存在し、なぜ片方に統一されないのか」を押さえておくと、設定項目の一つひとつが「作法」ではなく「必然」として腑に落ちます。ここを飛ばすと、属性マッピングや署名検証で詰まったときに手が止まります。

フェデレーションの中核は「信頼の委譲」

企業SSOの本質は、自社(Service Provider=SP)が「誰がログインしたか」の判断を、顧客企業のID基盤(Identity Provider=IdP)に委譲することです。あなたのSaaSは顧客のパスワードを一切持ちません。代わりに、IdPが「このユーザーは確かに認証済みだ」と**署名付きで宣言(アサーション)**し、SPはその署名を検証して信頼します。

この「署名されたアサーションを信頼の根拠にする」という一点が、SSOのセキュリティ設計のすべてを規定します。後半の攻撃対策がすべて「アサーションを偽造・すり替え・再利用させない」に集約されるのは、このためです。

SAML 2.0 — エンタープライズが手放せない理由(2005〜)

SAML(Security Assertion Markup Language)2.0 は、OASIS が2005年(正式承認 2005年3月)に標準化したXMLベースのフェデレーション規格です。前身の SAML 1.x(1.0=2002 / 1.1=2003)、Liberty Alliance の ID-FF、Shibboleth といった実装の系譜を統合し、Web Browser SSO Profile(HTTP-Redirect / HTTP-POST(および HTTP-Artifact)バインディングでアサーションを運ぶ)を確立しました。署名には XML Signature(XML-DSig)、機密化には XML Encryption を用います。

SAMLが20年経った今も企業で現役なのは、Active Directory / ADFS / Microsoft Entra ID(旧 Azure AD)といった既存資産との相性と、「エンタープライズアプリケーション」として厳格に登録・監査するガバナンス文化に深く根を張っているからです。大企業の情シスが「SAMLで来てくれ」と言うのは、彼らの統制プロセスがSAMLを前提に設計されているからです。

OIDC — モバイルとAPIの時代の答え(2014〜)

OpenID Connect(OIDC)1.0 は、OpenID Foundation が2014年(Final 仕様は2014年2月)に公開した、OAuth 2.0(RFC 6749)の上に薄いID層を載せた規格です。XMLではなくJSON/JWTを使い、.well-known/openid-configuration によるディスカバリでエンドポイントを自動発見できます。モバイルアプリ・SPA・API連携との親和性が高く、実装が軽い——これがモダンなIdP(Google、Okta のOIDCアプリ、各種CIAM)がOIDCを選ぶ理由です。

「どちらか」ではなく「両方受けられる」が正解

歴史が示すのは、SAMLとOIDCは世代交代ではなく棲み分けだという事実です。レガシーを抱える大企業はSAML、モダンな顧客はOIDCで来ます。だからこそ Cognito のように1つのユーザープールに複数IdP(SAML/OIDC混在)を同居させ、顧客ごとに出し分けられる設計が、B2B SaaSの現実解になります。次章以降の実装は、この「両方受ける」を前提に組み立てます。


まず押さえる:2026年のCognitoは「料金プラン」で機能が変わる

2024年11月、Cognito User Poolsに 機能プラン(feature plans) が導入され、「どの認証機能が使えるか」がプランで決まるようになりました。ここを理解せずに古い記事のコードをコピーすると「APIは通るのに機能が有効化されない」事故が起きます。 最初に全体像を押さえましょう。

プラン主な対象機能想定ユース
Lite基本認証 + 旧 Hosted UI(クラシック)最小構成・コスト最優先。パスキー/アクセストークンのカスタマイズ不可
Essentials(新規プールの既定)選択式サインイン(USER_AUTH)、パスワードレス(パスキー/OTP)、メールMFA、マネージドログイン、アクセストークンのクレーム拡張大多数のB2B/B2C SaaSの標準解
PlusEssentials + 脅威防御(threat protection):漏洩認証情報の検知・アダプティブ認証・ログエクスポート高セキュリティ要件・規制業種

ポイント(公式の仕様):

  • アクセストークンのクレーム拡張は Essentials 以上。IDトークンのクレーム拡張は全プランで可能。
  • パスキー / OTPパスワードレス / メールMFA は Essentials 以上(パスキーは Lite 以外で利用可)。
  • 旧称「高度なセキュリティ機能(Advanced Security Features)」は 「脅威防御(threat protection)」へ製品名が変更。ただし API のenumは従来どおり AdvancedSecurityModeOFF/AUDIT/ENFORCED のままです。ENFORCEDを設定するとプランは自動的に PLUS へ引き上げられます。

出典: Understanding feature plans / Threat protection

マネージドログイン vs クラシック Hosted UI

マネージドログイン(新)クラシック Hosted UI(旧・第一世代)
ブランディングノーコードのビジュアルエディタ(ロゴ・配色・角丸・ライト/ダーク)ロゴ + CSS ファイルのみ
パスキーサインイン✅ 対応❌ 非対応
必要プランEssentials / PlusLite でも可
多言語化

エンドポイントのパス(/oauth2/authorize/oauth2/idpresponse/saml2/idpresponse/saml2/logout/logout)は両者で共通です。新規プールは既定でマネージドログインになります。なお公式はクラシック Hosted UI を「非推奨(deprecated)」とは明言していませんが、「第一世代」と位置づけ、パスキー等の新機能は提供されません。新規実装はマネージドログイン一択です。

出典: Managed login


認証方式の選び方:3つの軸で判断する

Cognitoの認証は大きく3系統あります。まずここを選び違えないことが最重要 です。要望別の早見表を用意しました。

要望・場面選ぶべき方式Cognitoの実装手段
顧客企業のIdP(Azure AD/Okta/ADFS)に認証を委譲したいフェデレーションSAML / OIDC IdP連携
自社でユーザーを持ち、パスワードを廃したい(パスキー・OTP)パスワードレスUSER_AUTH(選択式サインイン)
独自のチャレンジ(CAPTCHA・秘密の質問・外部リスクエンジン照合)を挟みたいカスタム認証フローCUSTOM_AUTH + 3つのLambdaトリガー

重要な判断ポイント:

  • OTPやパスキーが目的なら、もう CUSTOM_AUTH を自作しない。 2024年以降は USER_AUTHEMAIL_OTP/SMS_OTP/WEB_AUTHN)が公式機能として提供され、Lambdaの自前実装が不要になりました。CUSTOM_AUTH は「公式機能で表現できない独自チャレンジ」専用と考えるべきです。
  • USER_AUTHCUSTOM_AUTH を廃止しません。 両者は共存します。置き換わったのは「OTP/パスキーを CUSTOM_AUTH で手組みする旧来のユースケース」だけです。
  • B2B SaaSの現実解は 「フェデレーション(企業顧客)+ パスワードレス or パスワード(個人・小規模顧客)」のハイブリッド。本記事の後半で、ログイン画面でこれを出し分ける設計を示します。

SAML vs OIDC:どちらで連携するか

項目SAML 2.0OIDC (OpenID Connect)
仕様XMLベースJSON + OAuth 2.0
複雑性高い低い
モバイル対応困難容易
主な採用Azure AD(エンタープライズアプリ), Okta, ADFSGoogle, Okta(OIDC App), 各種モダンIdP
Cognito対応ProviderType: SAMLProviderType: OIDC(汎用)/ Google

判断基準

  • SAML:顧客がレガシー(ADFS等)を使う、またはAzure ADで「エンタープライズアプリケーション」として登録するポリシーがある場合。
  • OIDC:モダンIdP、モバイル対応、実装のシンプルさ重視。Oktaは両対応なので、どちらでも可。
  • 現実:顧客のIdP環境に合わせて両方サポートできる設計にしておくのが理想(Cognitoは1つのユーザープールに複数IdPを同居できます)。

実装例1:Azure AD / Microsoft Entra ID(SAML)統合

システム全体像

[ユーザー] → [SaaSフロント(Next.js/Amplify)]
   → マネージドログインへリダイレクト
   → [Cognito User Pool] が SAMLRequest 生成
   → [顧客IdP: Azure AD] で認証 + SAML Assertion
   → [Cognito] が ACS (/saml2/idpresponse) で受領 → ユーザー自動作成 → JWT発行
   → [SaaSバック] が JWT を aws-jwt-verify で検証 → テナントID抽出 → API認可

ステップ1:Cognito側の「SP情報」をIdPに登録する

Azure AD側で必要になるCognitoの値は、公式に決まった2つのパターン です。Cognitoは独自のSPメタデータURLを公開していません ので、以下を手動で登録します(ここを誤解して /saml2/metadata のようなURLを探すと時間を溶かします)。

Azure ADの項目設定値
識別子(エンティティID / Audience)urn:amazon:cognito:sp:<userPoolId>
応答URL(Assertion Consumer Service)https://<your-domain>.auth.<region>.amazoncognito.com/saml2/idpresponse
サインオンURLhttps://<your-app>/login
ログアウトURL(SLO使用時)https://<your-domain>.auth.<region>.amazoncognito.com/saml2/logout

Azure ADの「アプリのフェデレーション メタデータ URL」(https://login.microsoftonline.com/<tenant-id>/federationmetadata/2007-06/federationmetadata.xml)をコピーしておきます。これをCognitoに渡します。

ステップ2:Cognito User Pool(Terraform / 2026年版)

# --- User Pool 本体(料金プランを明示)---
resource "aws_cognito_user_pool" "main" {
  name = "my-saas-user-pool"

  # 2024/11 導入: 機能プラン。アクセストークン拡張・パスワードレスを使うなら ESSENTIALS 以上。
  user_pool_tier = "ESSENTIALS"

  # マルチテナント用カスタム属性(IdPから受け取るテナントID)
  schema {
    name                = "tenant_id"
    attribute_data_type = "String"
    mutable             = true # IdP マッピング属性は mutable 必須
    string_attribute_constraints {
      min_length = 1
      max_length = 256
    }
  }

  auto_verified_attributes = ["email"]

  # SSO主体でもローカルユーザー用にポリシーは定義しておく
  password_policy {
    minimum_length    = 12
    require_lowercase = true
    require_uppercase = true
    require_numbers   = true
    require_symbols   = true
  }

  account_recovery_setting {
    recovery_mechanism {
      name     = "verified_email"
      priority = 1
    }
  }
}

# --- SAML Identity Provider(Azure AD)---
resource "aws_cognito_identity_provider" "azure_ad_saml" {
  user_pool_id  = aws_cognito_user_pool.main.id
  provider_name = "AzureAD" # supported_identity_providers と app側の指定で使う名前
  provider_type = "SAML"

  provider_details = {
    MetadataURL = "https://login.microsoftonline.com/<tenant-id>/federationmetadata/2007-06/federationmetadata.xml"
    # シングルログアウト(IdP起点・SP起点)を有効化する場合
    IDPSignout = "true"
    # Cognito が送る SAMLRequest の署名アルゴリズム(推奨)
    RequestSigningAlgorithm = "rsa-sha256"
  }

  # SAMLクレーム → Cognito属性 のマッピング
  attribute_mapping = {
    email               = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
    given_name          = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
    family_name         = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
    "custom:tenant_id"  = "http://schemas.microsoft.com/identity/claims/tenantid"
  }
}

# --- ドメイン(マネージドログイン)---
resource "aws_cognito_user_pool_domain" "main" {
  domain       = "my-saas-domain"
  user_pool_id = aws_cognito_user_pool.main.id
}

# --- App Client ---
resource "aws_cognito_user_pool_client" "web_client" {
  name         = "web-client"
  user_pool_id = aws_cognito_user_pool.main.id

  allowed_oauth_flows                  = ["code"]            # 認可コードフロー(推奨)
  allowed_oauth_scopes                 = ["openid", "email", "profile"]
  allowed_oauth_flows_user_pool_client = true
  callback_urls                        = ["https://my-app.com/auth/callback"]
  logout_urls                          = ["https://my-app.com/logout"]
  supported_identity_providers         = ["AzureAD", "COGNITO"]

  # 認証フロー(後述の USER_AUTH を使う場合は ALLOW_USER_AUTH を追加)
  explicit_auth_flows = ["ALLOW_REFRESH_TOKEN_AUTH", "ALLOW_USER_SRP_AUTH"]

  # トークン有効期限(単位を必ず明示する。既定は hours だが意図を明確に)
  access_token_validity  = 1
  id_token_validity      = 1
  refresh_token_validity = 30
  token_validity_units {
    access_token  = "hours"
    id_token      = "hours"
    refresh_token = "days"
  }
}

公式ドキュメントとの整合(重要)SLORedirectBindingURI / SSORedirectBindingURI / ActiveEncryptionCertificateDescribe(取得)時のレスポンス専用フィールド であり、CreateIdentityProvider の入力には指定できません。古い記事でこれらを provider_details に書いている例がありますが、APIエラーになります。 出典: CreateIdentityProvider API / SAML IdP

属性マッピングの落とし穴(公式準拠)

  • NameID は「一意かつ大文字小文字を区別する」フェデレーション識別子 です。可変なemailではなく、安定した不変属性(Azure ADなら objectid/http://schemas.microsoft.com/identity/claims/objectidentifier)から導出するのが鉄則。emailにマッピングすると、ユーザーのメール変更でアカウントが分裂します。
  • マッピングしたemailは既定で「未検証(unverified)」扱い。検証必須のフローでは注意。
  • カスタム属性は mutable かつ writable でないとマッピングできません。

実装例2:OIDC統合(Google / Okta)

Google(専用プロバイダ)

resource "aws_cognito_identity_provider" "google_oidc" {
  user_pool_id  = aws_cognito_user_pool.main.id
  provider_name = "Google"
  provider_type = "Google"

  provider_details = {
    client_id        = var.google_oauth_client_id
    client_secret    = var.google_oauth_client_secret
    authorize_scopes = "openid email profile" # openid は必須
  }

  attribute_mapping = {
    email       = "email"
    given_name  = "given_name"
    family_name = "family_name"
    username    = "sub"
  }
}

Google Cloud Console側の「承認済みリダイレクトURI」には次を登録します: https://<your-domain>.auth.<region>.amazoncognito.com/oauth2/idpresponse

汎用OIDC(Okta など)— issuer自動探索を使う

汎用OIDCプロバイダ(OktaのOIDCアプリ等)は、oidc_issuer を渡せば .well-known/openid-configuration から authorize/token/userInfo/jwks の各エンドポイントが自動探索 されます。エンドポイントを一つずつ書く必要はありません。

resource "aws_cognito_identity_provider" "okta_oidc" {
  user_pool_id  = aws_cognito_user_pool.main.id
  provider_name = "Okta"
  provider_type = "OIDC"

  provider_details = {
    client_id                 = var.okta_client_id
    client_secret             = var.okta_client_secret
    authorize_scopes          = "openid email profile" # openid 必須
    oidc_issuer               = "https://<your-org>.okta.com"
    attributes_request_method = "GET" # userInfo の取得メソッド
  }

  attribute_mapping = {
    email       = "email"
    given_name  = "given_name"
    family_name = "family_name"
    username    = "sub"
  }
}

公式準拠の注意点(多くの記事が省略):

  • Cognitoは client_secret_post を要求し、client_secret_basic には非対応。IdP側のクライアント認証方式を合わせてください。
  • oidc_issuerhttps:// 始まりで、末尾スラッシュは付けない
  • エンドポイントはHTTPS、ポートは 80/443 のみ。
  • sub → username は自動マッピング(usernameにプロバイダ名が前置されます)。

出典: OIDC IdP


カスタム認証フロー(CUSTOM_AUTH):3つのLambdaトリガー

タイトルの核心、カスタム認証フロー です。「CAPTCHA」「秘密の質問」「自社の不正検知エンジンへの照合」など、Cognitoの標準機能で表現できない独自チャレンジ を挟みたいときに使います。

仕組みは 3つのLambdaトリガーが連携してチャレンジのループを回す だけです。InitiateAuth(AuthFlow=CUSTOM_AUTH) で開始し、RespondToAuthChallenge で回答していきます。

InitiateAuth(CUSTOM_AUTH)
        │
        ▼
DefineAuthChallenge ──「次は何のチャレンジ?/合否は?」を判断(司令塔)
        │  challengeName = CUSTOM_CHALLENGE
        ▼
CreateAuthChallenge ──チャレンジの中身を生成(OTPコード生成&送信など)
        │  publicChallengeParameters(クライアントへ)/ privateChallengeParameters(答え)
        ▼
(クライアントが RespondToAuthChallenge で ANSWER を送信)
        │
        ▼
VerifyAuthChallengeResponse ──答え合わせ(answerCorrect: true/false)
        │
        ▼
DefineAuthChallenge ──結果を見て「トークン発行」or「もう一度」or「失敗」を決定

① DefineAuthChallenge(司令塔)

event.request.session に過去のチャレンジ結果(ChallengeResult)の配列が入ります。これを見て次を決めます。issueTokens=true で成功(JWT発行)、failAuthentication=true で失敗、両方falseで継続です。

# define_auth_challenge.py
def lambda_handler(event, context):
    session = event["request"]["session"]

    if len(session) == 0:
        # 初回: 独自チャレンジを提示
        event["response"]["challengeName"] = "CUSTOM_CHALLENGE"
        event["response"]["issueTokens"] = False
        event["response"]["failAuthentication"] = False
    elif (
        len(session) == 1
        and session[0]["challengeName"] == "CUSTOM_CHALLENGE"
        and session[0]["challengeResult"] is True
    ):
        # 1回正解したらトークン発行
        event["response"]["issueTokens"] = True
        event["response"]["failAuthentication"] = False
    else:
        # それ以外は失敗(無限ループ・総当たり防止)
        event["response"]["issueTokens"] = False
        event["response"]["failAuthentication"] = True

    return event

② CreateAuthChallenge(チャレンジ生成)

challengeName === "CUSTOM_CHALLENGE" のときだけ呼ばれます。publicChallengeParameters はクライアントに渡る公開情報、privateChallengeParameters検証用の答え(クライアントには渡らない) です。

# create_auth_challenge.py
import secrets
import boto3

ses = boto3.client("ses")

def lambda_handler(event, context):
    if event["request"]["challengeName"] != "CUSTOM_CHALLENGE":
        return event

    # 例: 6桁のワンタイムコードを生成して送信(独自のCAPTCHA等でも同様)
    code = f"{secrets.randbelow(1_000_000):06d}"
    email = event["request"]["userAttributes"]["email"]

    ses.send_email(
        Source="no-reply@my-saas.com",
        Destination={"ToAddresses": [email]},
        Message={
            "Subject": {"Data": "ログイン確認コード"},
            "Body": {"Text": {"Data": f"確認コード: {code}(5分間有効)"}},
        },
    )

    # クライアントに見せてよい情報のみ public へ
    event["response"]["publicChallengeParameters"] = {"medium": "EMAIL"}
    # 答えは private(応答には絶対に含めない)
    event["response"]["privateChallengeParameters"] = {"answer": code}
    event["response"]["challengeMetadata"] = "EMAIL_ONE_TIME_CODE"
    return event

③ VerifyAuthChallengeResponse(答え合わせ)

# verify_auth_challenge.py
def lambda_handler(event, context):
    expected = event["request"]["privateChallengeParameters"]["answer"]
    provided = event["request"]["challengeAnswer"]
    event["response"]["answerCorrect"] = secrets_equal(expected, provided)
    return event

def secrets_equal(a: str, b: str) -> bool:
    # タイミング攻撃を避けるため定数時間比較
    import hmac
    return hmac.compare_digest(a, b)

設計上の判断:上記は「メールOTP」を例にしていますが、単なるメール/SMSのOTPなら自作せず後述の USER_AUTHEMAIL_OTP/SMS_OTP)を使うべき です。CUSTOM_AUTH の出番は「reCAPTCHA検証」「自社リスクスコアAPI照合」「デバイス信頼度チェック」など、公式機能に無いロジックを挟むときに限定しましょう。 出典: Custom authentication challenge Lambda triggers


USER_AUTH:選択式・パスワードレス認証(パスキー / OTP)

2024年後半に追加された USER_AUTH は、「パスワード」「ワンタイムコード(メール/SMS)」「パスキー(WebAuthn)」を ユーザーに選ばせる(choice-based) 公式フローです。OTPやパスキーのためにLambdaを書く時代は終わりました。

必要な設定

設定箇所
ユーザープールのプランEssentials 以上
App Client の explicit_auth_flowsALLOW_USER_AUTH を追加
ユーザープールの sign_in_policy.allowed_first_auth_factorsPASSWORD / EMAIL_OTP / SMS_OTP / WEB_AUTHN から選択
resource "aws_cognito_user_pool" "main" {
  # ... 既出の設定 ...
  user_pool_tier = "ESSENTIALS"

  # 第一認証要素として許可する方式(WEB_AUTHN は他要素と併用が必須)
  sign_in_policy {
    allowed_first_auth_factors = ["PASSWORD", "EMAIL_OTP", "WEB_AUTHN"]
  }
}

resource "aws_cognito_user_pool_client" "web_client" {
  # ... 既出の設定 ...
  explicit_auth_flows = [
    "ALLOW_USER_AUTH",            # 選択式サインインを有効化
    "ALLOW_REFRESH_TOKEN_AUTH",
  ]
}

フローの仕組み

  1. InitiateAuth(AuthFlow=USER_AUTH)USERNAME 付きで呼ぶ。
  2. PREFERRED_CHALLENGE を指定しない場合、Cognitoは ChallengeName: SELECT_CHALLENGEAvailableChallenges(利用可能な方式の配列) を返す。
  3. クライアントは SELECT_CHALLENGE に対し、選んだ方式を ANSWER に入れて応答。
  4. 以降は方式ごとのチャレンジ(EMAIL_OTP なら EMAIL_OTP_CODEWEB_AUTHN なら CREDENTIAL)に答える。
方式チャレンジ名回答キー
パスキー(WebAuthn)WEB_AUTHNCREDENTIAL(W3C AuthenticationResponseJSON
メールOTPEMAIL_OTPEMAIL_OTP_CODE
SMS OTPSMS_OTPSMS_OTP_CODE
パスワードPASSWORD / PASSWORD_SRP

補足:パスキーは ES256 + RS256 に対応し、1ユーザーあたり最大20個まで登録可。登録状況は GetUserAuthFactors で確認できます。第一要素のOTP(EMAIL_OTP/SMS_OTP)は、第二要素のMFAコード(SMS_MFA/EMAIL_MFA/SOFTWARE_TOKEN_MFA)とは 別物 である点に注意。 出典: Authentication flows (USER_AUTH / choice-based)

フロントエンド(AWS Amplify v6 / Gen 2)

Amplifyはv6で名前空間APIに刷新されました(旧 Auth.signIn 等は廃止)。USER_AUTH を使うパスワードレスはこう書きます。

// パスワードレス・サインイン(メールOTPを選好)
import { signIn, confirmSignIn } from "aws-amplify/auth";

async function startPasswordless(username: string) {
  const { nextStep } = await signIn({
    username,
    options: { authFlowType: "USER_AUTH", preferredChallenge: "EMAIL_OTP" },
  });

  if (nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_EMAIL_CODE") {
    // ユーザーがメールで受け取ったコードを入力 → confirmSignIn で送信
  }
}

async function submitOtp(code: string) {
  const { isSignedIn } = await confirmSignIn({ challengeResponse: code });
  return isSignedIn;
}
// パスキー(WebAuthn)の登録(ログイン後に呼ぶ)
import { associateWebAuthnCredential } from "aws-amplify/auth";

async function registerPasskey() {
  // ブラウザのWebAuthn APIをAmplifyが内部で起動し、資格情報をCognitoに登録
  await associateWebAuthnCredential();
}

マルチテナント設計:顧客ごとにIdPを出し分ける

課題

  • 顧客A → Azure AD(SAML)、顧客B → Okta(OIDC)、顧客C → Google、個人ユーザー → パスワードレス。
  • 1つのログイン画面で、入力メールに応じて適切な認証方式に誘導したい。

解決策:メールドメインでテナント判定 → IdPへ誘導

// LoginPage.tsx(Amplify v6 / Next.js)
import { signInWithRedirect, signIn } from "aws-amplify/auth";
import { useState } from "react";

type Tenant = { id: string; name: string; idp: "AzureAD" | "Google" | "Okta" | null };

export function LoginPage() {
  const [email, setEmail] = useState("");
  const [tenant, setTenant] = useState<Tenant | null>(null);

  // メールドメインからテナントを判定
  async function detectTenant(value: string) {
    const res = await fetch("/api/detect-tenant", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email: value }),
    });
    setTenant((await res.json()).tenant);
  }

  // 企業SSO(フェデレーション)へ
  async function loginWithSSO() {
    if (!tenant?.idp) return;
    await signInWithRedirect({ provider: { custom: tenant.idp } });
  }

  // 個人ユーザーはパスワードレスへ
  async function loginPasswordless() {
    await signIn({
      username: email,
      options: { authFlowType: "USER_AUTH", preferredChallenge: "EMAIL_OTP" },
    });
  }

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        tenant?.idp ? loginWithSSO() : loginPasswordless();
      }}
    >
      <label htmlFor="email">メールアドレス</label>
      <input
        id="email"
        type="email"
        autoComplete="email webauthn"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        onBlur={() => detectTenant(email)}
      />
      <button type="submit">
        {tenant?.idp ? `${tenant.name} のSSOでログイン` : "確認コードでログイン"}
      </button>
    </form>
  );
}

アクセシビリティの要点:labelinputhtmlFor/id で関連付け、autoComplete="email webauthn" を付けると、対応ブラウザがパスキーをオートフィル候補に出します。送信は<form>onSubmit で受けると Enter キー操作にも対応できます。

テナント検出API(FastAPI)

# api/detect_tenant.py
from fastapi import APIRouter
from pydantic import BaseModel, EmailStr
import boto3

router = APIRouter()
tenants = boto3.resource("dynamodb").Table("tenants")

class Req(BaseModel):
    email: EmailStr

@router.post("/api/detect-tenant")
async def detect_tenant(req: Req):
    domain = req.email.split("@")[1]
    res = tenants.query(
        IndexName="domain-index",
        KeyConditionExpression="#d = :d",
        ExpressionAttributeNames={"#d": "domain"},
        ExpressionAttributeValues={":d": domain},
    )
    if not res["Items"]:
        return {"tenant": None}  # 未登録 → 個人ユーザー扱い
    t = res["Items"][0]
    return {"tenant": {"id": t["tenant_id"], "name": t["company_name"], "idp": t.get("idp")}}

セキュリティ補足:テナント検出APIは「このドメインにIdPがあるか」を返すだけに留め、ユーザーの存在有無を漏らさない(user enumeration対策)。Cognito側も PreventUserExistenceErrors=ENABLED を設定しておきます。


トークンへテナントを注入する:Pre Token Generation V2.0

マルチテナントの肝は 「アクセストークンに tenant_id を載せ、バックエンドの認可で使う」 ことです。フェデレーションで来たユーザーは属性マッピングで custom:tenant_id を持ちますが、アクセストークンのクレーム拡張は Pre Token Generation トリガーの V2.0 以降(Essentials以上) が必要です。

バージョン対象トークン必要プラン
V1_0IDトークンのみ全プラン(Liteも可)
V2_0ID + アクセストークン(ユーザーID)Essentials / Plus
V3_0ID + アクセス(M2M クライアントクレデンシャル含むEssentials / Plus
resource "aws_cognito_user_pool" "main" {
  # ... 既出の設定 ...
  lambda_config {
    pre_token_generation_config {
      lambda_arn     = aws_lambda_function.pre_token.arn
      lambda_version = "V2_0" # アクセストークンに載せるなら V2_0 以上
    }
  }
}
# pre_token_generation.py (V2_0)
def lambda_handler(event, context):
    attrs = event["request"]["userAttributes"]
    tenant_id = attrs.get("custom:tenant_id", "")

    event["response"]["claimsAndScopeOverrideDetails"] = {
        # アクセストークンにテナントIDとスコープを注入
        "accessTokenGeneration": {
            "claimsToAddOrOverride": {"tenant_id": tenant_id},
            "scopesToAdd": [f"tenant/{tenant_id}"],
        },
        # 必要ならIDトークンにも
        "idTokenGeneration": {
            "claimsToAddOrOverride": {"tenant_id": tenant_id},
        },
        # cognito:groups を上書きしてテナント別ロールを反映することも可能
        # "groupOverrideDetails": {"groupsToOverride": [f"tenant-{tenant_id}-member"]},
    }
    return event

公式の制約(事故りやすい点):

  • sub / iss / token_use / cognito:* 等の 予約クレームは追加・変更・抑制できません
  • 例外的に、アクセストークンへ aud を追加することは可能ですが、その値は event.callerContext.clientId と一致している必要があります。
  • groupOverrideDetails を空/nullにすると cognito:groups抑制(消える) されるので、上書き時は全グループを明示すること。
  • V1.0コンテナ名は claimsOverrideDetails、V2.0以降は claimsAndScopeOverrideDetails名前が異なります

出典: Pre token generation Lambda trigger


JWT検証:公式推奨の aws-jwt-verify を使う

CognitoのJWTは RS256 で署名され、検証では以下を必ずチェックします。手書きのJWKSパースは事故の温床なので、AWS公式が推奨する aws-jwt-verify を使う のが2026年の標準です。

検証項目
isshttps://cognito-idp.<region>.amazonaws.com/<userPoolId>
対象クライアントIDトークンは aud、アクセストークンは client_id(どちらもApp Client ID)
token_use"id" または "access"(用途に合致しているか)
exp失効していないか
alg / kidRS256 / JWKSの鍵IDと一致

注:Cognitoは1プールにつき RSA鍵ペアを2つ(アクセス用・ID用)持ちます。kid でキャッシュし、未知の kid が来たらJWKSを再取得する設計に。aws-jwt-verify はこれを自動で行います。

TypeScript(バックエンド / Next.js Route Handler や Lambda)

import { CognitoJwtVerifier } from "aws-jwt-verify";

// アクセストークン用検証器(client_id と token_use を自動チェック)
const verifier = CognitoJwtVerifier.create({
  userPoolId: process.env.COGNITO_USER_POOL_ID!,
  tokenUse: "access",
  clientId: process.env.COGNITO_CLIENT_ID!,
});

export async function authorize(authHeader: string | null) {
  const token = authHeader?.replace(/^Bearer\s+/i, "");
  if (!token) throw new Response("Unauthorized", { status: 401 });

  try {
    const payload = await verifier.verify(token); // 署名 + iss + exp + client_id + token_use
    return {
      userId: payload.sub,
      tenantId: payload["tenant_id"] as string | undefined, // Pre Token Gen で注入
      groups: (payload["cognito:groups"] as string[] | undefined) ?? [],
    };
  } catch {
    throw new Response("Invalid token", { status: 401 });
  }
}

Python(FastAPI など)

# middleware/auth.py
from fastapi import HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from jwt import PyJWKClient
import os

security = HTTPBearer()
REGION = os.environ["COGNITO_REGION"]
POOL_ID = os.environ["COGNITO_USER_POOL_ID"]
CLIENT_ID = os.environ["COGNITO_CLIENT_ID"]
ISSUER = f"https://cognito-idp.{REGION}.amazonaws.com/{POOL_ID}"
jwks = PyJWKClient(f"{ISSUER}/.well-known/jwks.json")

def verify_access_token(creds: HTTPAuthorizationCredentials = Security(security)) -> dict:
    token = creds.credentials
    try:
        key = jwks.get_signing_key_from_jwt(token).key
        payload = jwt.decode(token, key, algorithms=["RS256"], issuer=ISSUER)
        # アクセストークンは aud を持たないため client_id / token_use を手動検証
        if payload.get("token_use") != "access" or payload.get("client_id") != CLIENT_ID:
            raise jwt.InvalidTokenError("token_use / client_id mismatch")
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError as e:
        raise HTTPException(status_code=401, detail=f"Invalid token: {e}")

出典: Verifying a JSON Web Token


トラブルシューティング

SAML Assertion 署名検証失敗

Invalid SAML response: Signature validation failed
  • 原因:IdP側の署名証明書がローテーションされた/CognitoのメタデータURLが古い。
  • 対処MetadataURL を使っていれば再 terraform apply で最新化。手動メタデータ(MetadataFile)運用なら証明書を差し替える。MetadataURL 運用を強く推奨(証明書の自動追従)。

属性が空 / email が無いと言われる

InvalidParameterException: User does not have a valid email
  • 原因:SAML AssertionにマッピングしたクレームURIが実際の送信内容と違う。
  • デバッグ:ブラウザ拡張「SAML-tracer」で実Assertionを確認 → どのクレーム名で来ているかを見て attribute_mapping を修正。Azure ADはデフォルトで .../emailaddress を使うが、設定により .../name のこともある。

リダイレクトループ

  • 原因callback_urls とフロントの設定が不一致。
  • 対処:Amplify v6の設定で redirectSignIn完全一致 させる。
import { Amplify } from "aws-amplify";

Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId: "ap-northeast-1_XXXXXXXXX",
      userPoolClientId: "xxxxxxxxxxxxxxxxxxxx",
      loginWith: {
        oauth: {
          domain: "my-saas-domain.auth.ap-northeast-1.amazoncognito.com",
          scopes: ["openid", "email", "profile"],
          redirectSignIn: ["https://my-app.com/auth/callback"], // 末尾スラッシュ含め完全一致
          redirectSignOut: ["https://my-app.com/logout"],
          responseType: "code",
        },
      },
    },
  },
});

SAML/OIDC の攻撃面と防御:Cognito は何を守り、あなたは何を守るか

フェデレーションの信頼はすべて「署名付きアサーション」に乗っています。裏を返せば、アサーションを偽造・すり替え・再利用できれば、なりすましログインが成立します。ここでは代表的な攻撃面を、OWASPのチートシート(SAML Security / XXE Prevention / OAuth)に沿って整理し、**「Cognitoを使う場合にAWSが担う部分」と「それでもあなたに残る責任」**を切り分けます。責任分界を誤解すると、「マネージドだから安全」と過信して残存リスクを放置することになります。

SAML 側の代表的な攻撃

攻撃何が起きるか防御の原則
XML Signature Wrapping(XSW)正規の署名付き要素は残したまま、偽の Assertion を XML の別位置に「包んで」注入。署名は正規要素に対して有効なのに、処理系が偽要素を読むズレを突く署名を検証した「まさにその要素」だけを処理する。堅牢なSAMLライブラリ+スキーマ検証。手組みのXMLパースは厳禁
XXE(XML外部実体注入)SAMLのXML解析時に外部実体を展開させ、ファイル読み出しやSSRFを誘発XMLパーサでDTD・外部実体を無効化する(OWASP XXE Prevention Cheat Sheet)
署名スコープ外のAssertionを信頼(署名の被覆不足)署名が「信頼するその Assertion」を覆っていないのに信頼すると、別の(偽の)Assertion にすり替えられる(XSW/置換の本質)各 Assertion または Response 全体を署名し、署名の <ds:Reference URI> が消費する <saml:Assertion> を確かに覆っていることを検証。Response のみ署名する場合も、その署名が消費 Assertion を覆うことを確認
リプレイ(再利用)傍受した正規アサーションを使い回すConditionsNotBefore/NotOnOrAfter を厳格に評価し、アサーションIDを一度きりに(InResponseTo 検証・ID キャッシュ)
Audience 誤り / Recipient 混同別SP向けのアサーションを転用AudienceRestriction が自SPのエンティティID(urn:amazon:cognito:sp:<poolId>)と一致することを検証。ACS(Recipient) の一致も確認

Cognitoを SP として使う場合の責任分界:XSW・XXE・署名検証・Conditions/Audience の評価といった**「SP側のアサーション処理」はAWS(Cognito)が実装・運用**します。あなたがSAMLライブラリを手で書くわけではありません。それでもあなたに残る責任は次のとおりで、ここが実務の勘所です。

  • MetadataURL 運用で IdP の署名証明書ローテーションに自動追従する(手動 MetadataFile は失効事故の温床)。
  • NameID は不変・一意な識別子(Azure ADなら objectid)にマッピングする。可変なemailにするとメール変更でアカウントが分裂し、アカウント乗っ取りの温床になる。
  • マッピングした email は既定で未検証。検証必須のフローでは扱いに注意。
  • IDPSignout / SLO を使うなら sessionIndex の一致まで含めて挙動を検証する。

OIDC / OAuth2 側の代表的な攻撃

攻撃何が起きるか防御の原則
CSRF(stateなし)攻撃者のコードを被害者のセッションに紐付け認可リクエストに**state** を付与し、コールバックで一致検証(RFC 6749)
IDトークン・リプレイ別の認可で得たIDトークンを流用nonce を認可リクエストに含め、IDトークンの nonce と突合(OIDC Core)
認可コード横取り(特にSPA/モバイル)リダイレクト経由でコードを傍受し交換PKCE(RFC 7636) を使う(code_challenge/code_verifier
Mix-Up 攻撃(複数IdP構成)どのIdP(認可サーバ)が応答したかを取り違え応答元 issuer を厳格に識別(RFC 9207 の iss 応答パラメータを期待値と突合)
トークン置換 / audience 誤用別クライアント向けトークンを転用aud/client_idtoken_useissexp・署名(RS256/kid)を検証(Node.jsは aws-jwt-verify、他言語は信頼できるJWTライブラリに委譲)

Cognitoを使う場合の責任分界:Cognito は**認可コードフロー(code)**を提供し、state/nonce/PKCE をサポートします。IDトークン・アクセストークンは RS256 で署名されます。あなたに残る責任は、

  • アプリ↔Cognito の OAuth レグで 認可コードフロー + PKCE を使い、Implicit フローは使わない
  • コールバックで state を検証(多くのSDKは自動だが、自前実装なら必須)。
  • API 認可ではアクセストークンを検証し(Node.jsは aws-jwt-verify、他言語は信頼できるJWTライブラリ)、token_use/client_id を必ず突合する(前章のコード)。
  • PreventUserExistenceErrors=ENABLED でユーザー列挙を防ぐ(API/IaC 作成時の既定は LEGACY=無効なので、明示的に有効化する)。
  • トークン有効期限を短く保ち、token_validity_units を明示する。

核心の一言:マネージドサービスは「規格レベルの攻撃(XSW/XXE/署名偽造)」を肩代わりしてくれますが、「設定と識別子設計のミス(可変NameID・未署名許容・Implicit・state欠落・長すぎるトークン寿命)」はあなたの責任のまま残ります。侵入の大半は前者ではなく後者から起きます。


セキュリティ・運用のチェックリスト

  • 認可コードフロー(code)+ PKCE を使う(Implicitは使わない)。
  • PreventUserExistenceErrors=ENABLED:ユーザー列挙攻撃を防ぐ。
  • 脅威防御(threat protection / Plus):漏洩認証情報の検知・アダプティブ認証。API上は AdvancedSecurityMode=ENFORCED(設定するとプランは自動でPLUS)。
  • トークン有効期限:アクセス/IDは短く(例:1時間)、リフレッシュは要件に応じて。token_validity_units を必ず明示。
  • グローバルログアウトsignOut({ global: true }) で全デバイスのリフレッシュトークンを失効。
  • SAML SLOIDPSignout=true 設定時、IdPの SingleLogoutService へ署名付き SAMLRequest が飛び、sessionIndex の一致が必要。

コスト試算:2026年の料金プラン前提で考える

旧来の「最初の50,000 MAUは無料/Advanced Securityは+$0.05/MAU」という説明は 現在のプラン制では不正確 です。現在は 選んだプラン(Lite/Essentials/Plus)× MAU で課金されます(Lite < Essentials < Plus)。

判断の指針:

  • 個人/小規模中心、UIは最小で良い → Lite。ただしパスキー・パスワードレス・アクセストークン拡張は不可。
  • B2B SaaSの標準(マネージドログイン・パスワードレス・テナント注入が欲しい) → Essentials。本記事の構成はこれが前提。
  • 規制業種・高セキュリティ(漏洩検知・アダプティブ認証・ログ輸出) → Plus。

正確な単価はリージョン・時期で変わるため、必ず公式の料金ページで最新値を確認 してください。 出典: Amazon Cognito Pricing

Lambdaトリガー(PreSignUp / Pre Token Generation / カスタム認証3種)は通常、無料枠〜ごく少額に収まります(ログインのたびに数十〜百ms程度の実行)。


内製 vs 専業SaaS vs Cognito:認証基盤の意思決定フレーム

「そもそも Cognito で自前実装すべきか、Auth0 や WorkOS のような専業を買うべきか」——エンタープライズ案件で必ず問われる分岐です。認証は一度作ると差し替えコストが極めて高い基盤なので、ここでの判断が数年効きます。技術的な優劣ではなく、5つの軸で自社の状況に当てはめて決めます。

判断軸Cognito(AWSネイティブで内製)が有利専業SaaS(Auth0/Okta・WorkOS 等)が有利
スケール時のコストMAUが増えるほど単価優位を出しやすい小〜中規模で立ち上げ速度を買う
AWS親和性・IaC既にAWS/Terraform中心。VPC・IAM・Lambdaと地続きにしたいクラウド非依存で使いたい
エンタープライズ機能標準機能で足りる要件SCIM による自動ユーザープロビジョニングや、顧客IdP接続を大量・高速に増やす管理UI/ディレクトリ同期が要る
チームの習熟Cognitoの癖(プラン制・トリガー・属性マッピング)を扱える認証に人手を割かず、DXの良い抽象に任せたい
市場投入速度設計を作り込む時間がある「来月のエンタープライズ商談まで」に間に合わせたい

実務での勘所

  • Cognito は AWS ネイティブでコスト効率と IaC 親和性に優れる一方、外部IdPからの SCIM による自動ユーザープロビジョニング(入社・退職の自動反映)はユーザープールにネイティブ機能が無く、大企業の要件では追加設計(API Gateway + Lambda 等)が要ることがあります。ここが「買う」判断の分岐点になりやすいポイントです。
  • WorkOS のような**「SSO/SCIM をAPIで売る」専業**は、B2Bの「エンタープライズ対応を最短で」というニーズに刺さります。逆に、既にワークロードがAWSに寄っていて長期のコストと統制を重視するなら Cognito の内製が効きます。
  • **正解は「どちらか」ではなく「いつ・どの順で」**です。PoC〜初期は素早い選択肢で立ち上げ、スケールと単価が効く段階で AWS ネイティブへ寄せる、という移行設計まで含めて決めるのが、やり直しコストを最小化します。

この意思決定は、要件(顧客IdPの数と種類・SCIM要否・規制・既存スタック)を一枚の表に落として初めて決まります。判断を誤ったまま実装に入ると、前述の「差し替えコスト」を丸ごと払うことになります。迷ったら、実装前に第三者の設計レビューを一度挟むのが最も安い保険です。


まとめ:エンタープライズ認証基盤の設計指針

  1. まずプランを決める:B2B SaaSの標準は Essentials。これでマネージドログイン・パスワードレス・アクセストークン拡張が揃う。
  2. 方式を選び違えない:企業顧客はフェデレーション(SAML/OIDC)、個人はパスワードレス(USER_AUTH)、独自要件のみ CUSTOM_AUTH
  3. テナント分離はトークンに載せる:Pre Token Generation V2.0 で tenant_id をアクセストークンへ注入し、バックエンドの認可で一貫して使う。
  4. 検証は aws-jwt-verify に任せるiss/audorclient_id/token_use/署名を自前で書かない。
  5. 公式ドキュメントを一次情報にする:Cognitoは更新が速い。本記事の各リンクから最新を確認する習慣を。

設計・運用で得た実感(木材流通DXプロジェクト)

経済産業大臣賞を受賞したB2BサブスクリプションSaaSでは、Cognitoを認証基盤として採用し、RS256でのJWT検証・マルチテナント前提の認可・複数IdPの受け入れを前提に設計しました。経験上、つまずきの大半は「属性マッピングの食い違い」と「プラン/トークン種別の理解不足」 に集約されます。本記事の早見表とチェックリストは、その実地の知見を反映したものです。


次のステップ:企業SSO対応を、確実に前に進める

エンタープライズ顧客からの「SSO対応してほしい」は、受注拡大の合図であると同時に、設計を誤ると数年ひきずる技術的負債の入口でもあります。SAML/OIDCの選定、NameID と属性マッピングの設計、マルチテナントのトークン設計、そして「内製か・専業を買うか」の判断——ここを最初に正しく組めるかで、その後の運用コストが大きく変わります。

私は経済産業大臣賞を受賞したB2BサブスクリプションSaaS(木材流通DX)で、Cognitoによる認証基盤を RS256のJWT検証・マルチテナント前提の認可・複数IdP受け入れを前提に設計・運用しました。同じ壁の越え方を、あなたのプロダクトの文脈に合わせて一気通貫で支援します。

  • 企業SSO導入の設計レビュー / 技術顧問:SAML・OIDCの選定、IdP連携、テナント設計、内製 vs 専業の意思決定を、実装前に整理します。
  • 実装・移行の伴走:既存のパスワード認証からフェデレーションへの移行、マルチテナント認可の本番投入まで。
  • 認可・RLSのセキュリティレビュー:発行後のトークンをバックエンドでどう認可に使うか(テナント分離の検証)まで踏み込みます。

「自社のIdP要件(Azure AD/Okta/Google/SCIM)で、Cognito 内製と専業SaaS のどちらが最適か」を整理したい方は、お気軽にご相談ください。要件を伺った上で、意思決定フレームに沿って一次見解をお返しします。

الأسئلة الشائعة

CUSTOM_AUTH と USER_AUTH は何が違う?どちらを使う?
USER_AUTH はパスキー/OTP/パスワードをユーザーに選ばせる公式のパスワードレス基盤(Essentials以上)。CUSTOM_AUTH は3つのLambdaで独自チャレンジを組む仕組みです。OTP・パスキー目的なら USER_AUTH、公式機能に無い独自ロジック(reCAPTCHA検証・自社リスク照合)なら CUSTOM_AUTH を使います。両者は共存でき、USER_AUTH が CUSTOM_AUTH を廃止したわけではありません。
アクセストークンにテナントIDを入れたいのにクレームが反映されない。
アクセストークンのクレーム拡張は Essentials 以上 + Pre Token Generation トリガー V2_0 が必要です。Lite や V1_0 では ID トークンしか拡張できません。sub/iss/token_use などの予約クレームは追加・変更・抑制できない点にも注意してください。
SAML の SP メタデータ URL はどこにある?
Cognito は SP メタデータ URL を公開していません。エンティティID urn:amazon:cognito:sp:<userPoolId> と ACS URL https://<domain>/saml2/idpresponse を手動で IdP に登録します。/saml2/metadata のような URL を探すと時間を溶かします。
ID トークンとアクセストークン、どちらを API 認可に使う?
API 認可はアクセストークンを使います(scope/cognito:groups/client_id を持つ)。ID トークンはユーザー属性の伝達用で aud を持ちます。検証時は token_use の一致(access か id か)を必ず確認してください。
認証基盤は Cognito で内製すべき?Auth0 や WorkOS を買うべき?
AWS ネイティブでコストを抑えたい・IaC で全て管理したい・スケール時の単価を重視するなら Cognito が有力です。一方、SCIM による自動ユーザープロビジョニングや、顧客ごとのIdP接続を素早く増やす管理UIを重視するなら Auth0/Okta や WorkOS などの専業SaaSが早い場合があります。判断軸はスケール時のコスト・AWS親和性・エンタープライズ機能(SCIM等)・チームの習熟・市場投入速度の5つです。本文の意思決定フレームで整理します。
企業SSO対応にはどれくらいの期間がかかる?
最初の1社(例:Azure AD の SAML 連携)を本番投入するまでが設計・実装・IdP側調整込みで最も時間がかかり、2社目以降は属性マッピングとテナント判定の横展開が中心になり短縮されます。期間は顧客IdPの数と種類(SAML/OIDC混在か)、マルチテナント要件、既存のパスワード認証からの移行有無で大きく変わるため、要件を確認した上で見積もるのが確実です。

المراجع

友田

友田 陽大

مطوّر منتج حائز على جائزة الوزير (METI). باستخدام TypeScript + Python + AWS أقدّم حلول SaaS والتحول الرقمي الصناعي والذكاء الاصطناعي التوليدي (RAG) الجاهز للإنتاج من تحديد المتطلبات إلى البنية التحتية والتشغيل، بمفردي.

Stuck on your auth platform or enterprise SSO design?

Auth platform & enterprise SSO (SAML/OIDC) technology selection & design review

Choosing among Cognito / Auth0 / Clerk / Supabase, SAML/OIDC federation with Azure AD, Okta, and Google, JWT (RS256) verification, and multi-tenant token design. With experience designing and running a Cognito auth foundation (RS256 JWT verification, multi-tenant authorization, multiple IdPs) on an award-winning B2B SaaS, I help you lock down the design before implementation, as a technical advisor.

متاح للعمل بالمشروع وللاستشارات التقنية على حد سواء. ابدأ باستشارة مجانية مدتها 30 دقيقة.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

يستحق القراءة أيضًا