MNTSQ Techブログ

「MNTSQ(モンテスキュー)」のTechブログです。

IAM Identity Center を IdP としてセルフホスト Langfuse に SSO ログインできるようにする

MNTSQ Tech Blog TOP > 記事一覧 > IAM Identity Center を IdP としてセルフホスト Langfuse に SSO ログインできるようにする

はじめに

弊社では LLM を活用した機能開発の観測基盤として Langfuse をセルフホストで運用しています。Langfuse は LLM アプリケーションのトレーシングやプロンプト管理等に活用できるオープンソースの LLM エンジニアリングプラットフォームです。

さてこの種のサービスには認証の課題がつきまといます。Langfuse は標準でメールアドレス / パスワードによるログインが可能ですが、弊社の方針として開発組織内で利用、管理しているアプリケーションやサービス群には IAM Identity Center を IdP とする SSO を採用しています。RedashSendGrid といったサービスで既にこの方式を採用しており、Langfuse でも同様に IAM Identity Center 経由の SSO ログインを実現したいと考えました。

ところが Langfuse は IAM Identity Center を IdP として直接サポートしていません。Langfuse の認証は NextAuth.js ベースであり、対応する認証方式は OAuth / OIDC が中心です*1。では IAM Identity Center の OIDC 機能をそのまま使えるかというと、そう単純ではありません。IAM Identity Center が OIDC を提供するにはアプリケーション側が trusted token issuer に対応している必要があり、Langfuse はこれに該当しないため直接利用できません。

Cognito と Langfuse の SSO 連携については既に優れた先例がありますが、ここに IAM Identity Center を加えた構成についての情報はあまり無いようです。ニッチな話題かもしれませんが、同様の課題を抱えている方もいるのではと考え、本稿にて事例を紹介できればと思います。

構成

前述の事情を踏まえ、間に Amazon Cognito を挟む構成としました。Cognito は SAML によって IdP からの認証情報を受け取り、これを OIDC にてアプリケーション(今回の場合は Langfuse)に提供する役目を担います。IAM Identity Center は SAML による外部アプリケーション(今回の場合は Cognito)のフェデレーションをサポートしているので、これを組み合わせると以下のような構成が実現できます。

IAM Identity Center --- (SAML) ---> Cognito --- (OIDC) ---> Langfuse

認証フローを時系列で描くと以下のようになります。

sequenceDiagram
    autonumber
    participant User as ユーザー (Browser)
    participant LF as Langfuse
    participant Cog as Cognito
    participant IDC as IAM Identity Center

    User->>LF: SSO ログインボタンをクリック
    LF->>User: Cognito のログイン URL へリダイレクト
    User->>Cog: 認証リクエスト (OIDC)
    Cog->>User: IAM Identity Center へリダイレクト
    User->>IDC: SAML 認証リクエスト
    IDC->>User: ログイン画面の表示 / 認証の実施
    IDC->>User: SAML Assertion を発行
    User->>Cog: SAML Assertion をポスト (ACS URL)
    Cog->>Cog: SAML Assertion の検証 / ユーザー情報の処理
    Cog->>User: Langfuse へリダイレクト (認可コード)
    User->>LF: 認可コードを送信
    LF->>Cog: トークン交換リクエスト
    Cog->>LF: ID トークン / アクセストークン (OIDC)
    LF->>User: ログイン完了

要するに Cognito が SAML -- OIDC 間の橋渡し役を担う格好です。ユーザー視点では Langfuse のログイン画面で SSO ボタンを押すと IAM Identity Center のログイン画面に遷移し、認証後 Langfuse に戻ってきます。

これを実現する構成が以下のようになります。本稿の趣旨は Langfuse 利用にかかる SSO ログイン化が主軸につき、Langfuse 本体の構成はかなり端折った図である点ご容赦ください*2

構成図。admin / langfuse / logging / security は AWS アカウント名の意

なお上図における logging / security 各アカウントは以下拙稿における member / security に対応するものです。主に SSO ログイン時の監査に使われる周辺要素であり、本稿では詳細を割愛します。

設定の手順

設定は大きく4つのステップに分かれます。

  1. IAM Identity Center に SAML アプリケーションを登録する(admin アカウント)
  2. Cognito で SAML IdP と OIDC クライアントを構成する(langfuse アカウント)
  3. Langfuse に OIDC 関連の環境変数を設定する(langfuse アカウント)
  4. SSO ログインへの一本化(langfuse アカウント)

以下、各ステップの内容を解説します。

Step 1: IAM Identity Center への SAML アプリケーション登録

admin アカウントの IAM Identity Center に、Langfuse 用のカスタム SAML アプリケーション(customer-managed application)を Terraform で登録します*3aws_ssoadmin_application リソースで application_provider_arncustom-saml を指定し、IAM Identity Center のポータル上での可視性を有効にします。

あわせて aws_ssoadmin_application_assignment で SSO ログインを許可する対象を紐付けます。グループ単位での割り当ても可能ですが、弊社では職責や担当職務に応じて柔軟にアクセス範囲を制御したかったため、ユーザー単位での割り当てとしました*4

続いてマネジメントコンソールから Application metadata を設定します。これは Cognito が SAML Assertion を正しく受け取るために必要な設定です。

  • Application ACS URL: https://<Cognito ドメイン>.auth.<リージョン>.amazoncognito.com/saml2/idpresponse
  • Application SAML audience: urn:amazon:cognito:sp:<Cognito User Pool ID>

ACS URL は Cognito User Pool のドメイン名から、SAML audience は Cognito User Pool の ID からそれぞれ導出されます。Step 2 で Cognito User Pool を作成した後に設定する、という順序になります*5

同じくマネジメントコンソールから Attribute mappings を設定します。SAML Assertion に含めるユーザー属性と Cognito 側の属性との対応を定義するものです。

User attribute in the application Maps to this string value or user attribute in IAM Identity Center Format
Subject(変更不可) ${user:email} persistent
email ${user:email} basic
email_verified true unspecified
name ${user.name} unspecified

email_verified を固定値 true としているのは弊社の運用上 IAM Identity Center で管理されているユーザーのメールアドレスは検証済みであると見なして差し支えないためです。これは以下の拙稿に詳細を譲りますが、弊社の IAM Identity Center は IdP として Entra ID を参照しており、Entra ID 上のユーザ情報を信頼できるケースに該当する為です。

最後に、マネジメントコンソールから SAML メタデータファイルをダウンロードします。

  1. IAM Identity Center コンソールへ移動
  2. Application assignmentsApplications を選択
  3. Customer managed タブへ移動
  4. 対象の SAML アプリケーションを選択
  5. IAM Identity Center metadata 内の IAM Identity Center SAML metadata file からダウンロード

このメタデータ XML ファイルは次のステップで Cognito に渡す必要があります。

Step 2: Cognito の構成

langfuse アカウント側では Cognito User Pool を作成し、IAM Identity Center を SAML IdP として登録した上で、Langfuse 向けの OIDC クライアントを構成します。

まず Step 1 でダウンロードした SAML メタデータ XML を S3 バケットに手動でアップロードし、Terraform からは data "aws_s3_object" で参照します*6。S3 バケット自体も Terraform で作成し、サーバーサイド暗号化とパブリックアクセスブロックを設定しています。

続いて、この SAML メタデータを使って aws_iam_saml_provideraws_cognito_identity_provider の2つを設定します。前者は IAM レベルでの SAML プロバイダ登録、後者は Cognito User Pool に対する SAML IdP の登録です。aws_cognito_identity_provider では attribute_mapping で Step 1 の Attribute mappings との対応を指定します。

なお provider_details には ignore_changes を設定しています。Cognito は MetadataFile を解釈して内部的にいくつかの属性を展開するため、内容に変更がなくても terraform plan で差分が出続けます。これを抑制するための措置です。

次に aws_cognito_user_pool_client を設定します。これは Langfuse から見た OIDC クライアントに相当するリソースです。OAuth フローには Authorization Code Grant(code)を、スコープには openid / email / profile を指定します。callback_urls には Langfuse の OIDC コールバック URL(https://<Langfuse のドメイン>/api/auth/callback/custom)を設定します。トークンの有効期間やクライアントシークレットの生成など、必要な設定を適宜入れます。

最後に、Cognito が払い出した Client ID / Client Secret / Issuer URL を aws_ssm_parameter(SecureString)で SSM パラメータストアに格納し、Langfuse の ECS タスク定義から参照できるようにします。

Step 3: Langfuse への環境変数設定

Langfuse は Custom OAuth Provider としての設定を環境変数で受け取ります。ECS タスク定義に以下の環境変数を追加しました。

平文で渡すものとして AUTH_CUSTOM_NAMEAUTH_CUSTOM_CHECKSAUTH_CUSTOM_ALLOW_ACCOUNT_LINKING の3つがあります。SSM パラメータストアから取得する秘密情報として AUTH_CUSTOM_CLIENT_IDAUTH_CUSTOM_CLIENT_SECRETAUTH_CUSTOM_ISSUER の3つがあり、これらは Step 2 で格納した値を参照します。

  • AUTH_CUSTOM_NAME は Langfuse のログイン画面に表示される SSO ボタンのラベルです。わかりやすい名称を選ぶと良いでしょう
  • AUTH_CUSTOM_CHECKS には pkce を指定し PKCE (Proof Key for Code Exchange) を有効にしています
  • AUTH_CUSTOM_ALLOW_ACCOUNT_LINKINGtrue にすると、同一メールアドレスの既存アカウントと SSO アカウントが紐付けられます
    • SSO 導入前のアカウントとの互換性のために有効にしています
    • これが無いと後述のトラブルシュート時に Cognito 側で対応する手法が取れず Langfuse 側 DB を直接触ってアカウント管理をする手間が発生します

Step 4: SSO ログインへの一本化

SSO ログインが安定稼動していることを確認した後、環境変数 AUTH_DISABLE_USERNAME_PASSWORDtrue に設定してメールアドレス / パスワードによるログインを無効化しました。これにより Langfuse のログイン画面は SSO ボタンのみが表示される状態になります。アカウント管理を IAM Identity Center に一元化でき、Langfuse 側での個別のアカウント発行 / 削除が不要となります。

サンプルコード

Step 1〜2 で使用した Terraform コードの抜粋を以下に示します。それぞれ collapsed になっていますので、クリックして中身を参照ください。

admin アカウント: IAM Identity Center への SAML アプリケーション登録

# Identity Store からユーザー情報を参照
data "aws_identitystore_user" "main" {
  for_each = toset(local.langfuse_users)

  identity_store_id = tolist(data.aws_ssoadmin_instances.main.identity_store_ids)[0]
  alternate_identifier {
    unique_attribute {
      attribute_path  = "UserName"
      attribute_value = each.key
    }
  }
}

# カスタム SAML アプリケーションの登録
resource "aws_ssoadmin_application" "langfuse" {
  name                     = "Langfuse"
  application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom-saml"
  instance_arn             = tolist(data.aws_ssoadmin_instances.main.arns)[0]

  portal_options {
    visibility = "ENABLED"
    sign_in_options {
      origin = "IDENTITY_CENTER"
    }
  }
}

# SSO ログインを許可するユーザーの割り当て
resource "aws_ssoadmin_application_assignment" "langfuse" {
  for_each = toset(local.langfuse_users) # 職責に応じてフィルタしたユーザーリスト

  application_arn = aws_ssoadmin_application.langfuse.arn
  principal_id    = data.aws_identitystore_user.main[each.key].id
  principal_type  = "USER"
}

langfuse アカウント: Cognito の構成

# ----- SAML メタデータの管理 -----

# メタデータ XML を格納する S3 バケット(暗号化・パブリックアクセスブロックは別途設定)
resource "aws_s3_bucket" "saml_metadata" {
  bucket = "mntsq-${var.env}-saml-metadata"
}

# IAM Identity Center からダウンロードしたメタデータ XML を参照
data "aws_s3_object" "saml_metadata" {
  bucket = aws_s3_bucket.saml_metadata.id
  key    = "langfuse.xml"
}

# ----- SAML プロバイダ・Cognito User Pool -----

resource "aws_iam_saml_provider" "langfuse" {
  name                   = "langfuse"
  saml_metadata_document = data.aws_s3_object.saml_metadata.body
}

resource "aws_cognito_user_pool" "langfuse" {
  name                     = "langfuse"
  auto_verified_attributes = ["email"]
}

resource "aws_cognito_user_pool_domain" "langfuse" {
  user_pool_id = aws_cognito_user_pool.langfuse.id
  domain       = "mntsq-${var.env}-langfuse"
}

# IAM Identity Center を SAML IdP として登録
resource "aws_cognito_identity_provider" "langfuse" {
  user_pool_id  = aws_cognito_user_pool.langfuse.id
  provider_name = "langfuse"
  provider_type = "SAML"

  provider_details = {
    "MetadataFile" = data.aws_s3_object.saml_metadata.body
  }

  attribute_mapping = {
    email          = "email"
    email_verified = "email_verified"
    name           = "name"
  }

  lifecycle {
    # Cognito が MetadataFile を解釈して provider_details を展開するため、
    # 毎回差分が出るのを抑制する
    ignore_changes = [provider_details]
  }
}

# ----- OIDC クライアント (User Pool Client) -----

resource "aws_cognito_user_pool_client" "langfuse" {
  name         = "langfuse"
  user_pool_id = aws_cognito_user_pool.langfuse.id

  allowed_oauth_flows_user_pool_client = true
  allowed_oauth_scopes                 = ["openid", "email", "profile"]
  allowed_oauth_flows                  = ["code"]
  supported_identity_providers         = [aws_cognito_identity_provider.langfuse.provider_name]

  access_token_validity  = 8
  id_token_validity      = 8
  refresh_token_validity = 1

  token_validity_units {
    access_token  = "hours"
    id_token      = "hours"
    refresh_token = "days"
  }

  callback_urls        = ["https://<Langfuse のドメイン>/api/auth/callback/custom"]
  default_redirect_uri = "https://<Langfuse のドメイン>/api/auth/callback/custom"

  generate_secret = true
}

# ----- 認証情報の SSM パラメータストア格納 -----

resource "aws_ssm_parameter" "auth_id" {
  name  = "/${var.env}/langfuse/AUTH_CUSTOM_CLIENT_ID"
  type  = "SecureString"
  value = aws_cognito_user_pool_client.langfuse.id
}

resource "aws_ssm_parameter" "auth_secret" {
  name  = "/${var.env}/langfuse/AUTH_CUSTOM_CLIENT_SECRET"
  type  = "SecureString"
  value = aws_cognito_user_pool_client.langfuse.client_secret
}

resource "aws_ssm_parameter" "auth_issuer" {
  name  = "/${var.env}/langfuse/AUTH_CUSTOM_ISSUER"
  type  = "SecureString"
  value = "https://cognito-idp.${data.aws_region.current.name}.amazonaws.com/${aws_cognito_user_pool.langfuse.id}"
}

トラブルシューティング

運用を開始して以降、SSO ログインが失敗するケースに遭遇しました。とくに初回ログイン時に顕著で、Cognito と Langfuse との間でユーザー情報のフェデレーションがうまくいかない場合に発生します。

リトライで解消するケースもありますが、それでも解決しない場合は Cognito 側のユーザーを一旦削除し、再度 Langfuse へ SSO ログインしてもらう ことで通るようになります。Cognito User Pool 内のユーザーは IAM Identity Center から federate されたものなので、削除しても次回の SSO ログイン時に再作成されます。

手順としては以下の通りです。

  1. Cognito User Pool のマネジメントコンソールへ移動
  2. User managementUsers でログインに失敗しているユーザーを特定
  3. 対象ユーザーを選択し、まず Disable user access を実施
  4. その後 Delete user を実行

ユーザー削除後に改めて SSO ログインを試みてもらえば、Cognito 側にユーザーが再作成されてログインが成功するはずです。

おわりに

IAM Identity Center を IdP として Langfuse に SSO ログインを実現する構成について解説しました。ポイントは Cognito を SAML と OIDC との翻訳役として活用する 点です。

設定にあたっては Terraform とマネジメントコンソールの手作業が混在する点がやや煩雑です。とくに IAM Identity Center 側の Application metadata や Attribute mappings、SAML メタデータの取得はコンソール操作が必要であり、手順として残しておかないと再現が難しくなります。

Cognito + Langfuse の SSO 構成については先例がありますが、IAM Identity Center を加えた構成についてはあまり情報が見当たりませんでした。このパターンは Langfuse に限らず、OIDC のみをサポートするアプリケーションに IAM Identity Center 経由の SSO を提供したい場合に広く応用できるものと考えています。同様の課題を抱えている方の参考になれば幸いです。

文責:MNTSQ 株式会社 SRE 秋本

注記:この記事は文責者の過去記事と弊社内のドキュメントをもとに Claude Opus 4.6 が作成した内容を9割程度そのまま使用しています

*1:https://langfuse.com/self-hosting/security/authentication-and-sso を参照。Langfuse は Google / GitHub / Azure AD (Entra ID) / Okta / Auth0 / Custom OAuth Provider 等をサポートしています

*2:興味のある方向け:ECS で ClickHouse 含めて Langfuse が必要とする諸要素を動作させ、DB は Aurora PostgreSQL、キャッシュ関連は ElastiCache にて Valkey クラスタを組んで動作させています

*3:実際には全ての設定を Terraform でカバーできるわけではないので、適宜 AWS マネジメントコンソールからの設定作業も必要になります

*4:この種の柔軟さをグループ単位で表現できなかったという内部事情もあります

*5:厳密には Cognito User Pool の作成 → IAM Identity Center の Application metadata 設定 → Cognito 側の残りの設定、という順序を踏む必要があります。Terraform で一括管理する場合は初回適用時にこの依存関係を意識する必要があります。要するに IAM Identity Center と Cognito とを行ったり来たりする必要が2026年2月時点で有ります

*6:SAML メタデータはコード管理の対象としていません。IAM Identity Center 側で生成されるものであり、かつ XML の内容が大きいため S3 経由での参照としました。メタデータの取得手順は Step 1 に記載の通りです