4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Auth0のユーザ認証でAmazon Bedrock AgentCore Gatewayのインバウンド認証と認可を実施する

Last updated at Posted at 2025-08-14

はじめに

Amazon Bedrock AgentCore考察記事第4弾。

前回の記事では、Amazon Bedrock AgentCore Gatewayの全体像に関して、公式のDeveloper Guideをなぞる形で構築と考察を行った。

公式のDeveloper Guideには、Auth0を用いたインバウンド認証の実施方法が書かれているものの、M2Mアプリケーションであるため、ユーザ認証をしなくてもアプリを実行したら認証を行える内容が書かれている。

実際のユースケースでは、Auth0に限らず、OktaなりAzure ADなりの認証を通したユーザのみ実行できるようにしたいだろう。

また、同じ組織の全員が実行できてしまうと具合が悪いケースがあるので、ユーザ単位のパーミッションを設定可能とし、認可も作り込んだ。

なお、記事中で使用する以下の要素は前回記事で作成したものを流用する。
要は、インバウンド認証に関する差分の身を記載するため、不明なリソースは前回記事を参照していただきたい。

  • IAMロール
  • Amazon Bedrock AgentCore GatewayのTerraformリソース(externalデータソース)
  • Amazon Bedrock AgentCore Gatewayのアウトバウンド認証に使うリソース(externalデータソース)

Auth0

前回の記事では、インバウンドとアウトバウンドの認証で同じリソースサーバ・クライアントを使っていたが、アウトバウンド認証はM2Mでないと動作しない、かつユーザ認証はM2Mでは動作しないため、リソースを分割する。

Resource Server

前回とあまり変更するところはないが、リソースを分ける都合、name, identifierを変えておこう。
また、token_dialectは不要なので、明示的にaccess_tokenを設定しておく。

resource "auth0_resource_server" "amazon_bedrock_agentcore_gateway_ib_auth" {
  name        = "Amazon Bedrock AgentCore Gateway Inbound Resource Server"
  identifier  = "urn:${local.auth0_resource_server_id_ib}:api"
  signing_alg = "RS256"

  allow_offline_access                            = false
  token_lifetime                                  = 3600
  skip_consent_for_verifiable_first_party_clients = true
  enforce_policies                                = true
  token_dialect                                   = "access_token"
}

クライアント

ここは、Resource Server同様にnameの変更もあるが、ユーザ認証をするためにapp_type = "native"を設定しよう。

また、grant_typesについても、client_credentialsではなく"urn:ietf:params:oauth:grant-type:device_codeを設定しておく。
※この後出てくるユーザ認証を呼び出すためのgrant

resource "auth0_client" "amazon_bedrock_agentcore_gateway_ib_auth" {
  name        = "Amazon Bedrock AgentCore Gateway Inbound API"
  description = "API for Amazon Bedrock AgentCore Gateway Inbound authorization"
  app_type    = "native"

  custom_login_page_on                = false
  is_first_party                      = true
  is_token_endpoint_ip_header_trusted = false
  oidc_conformant                     = true
  require_proof_of_possession         = false

  grant_types = [
    "urn:ietf:params:oauth:grant-type:device_code",
  ]

  jwt_configuration {
    alg                 = "RS256"
    lifetime_in_seconds = 3600
    secret_encoded      = false
  }

  refresh_token {
    leeway          = 0
    token_lifetime  = 31557600
    rotation_type   = "non-rotating"
    expiration_type = "non-expiring"
  }
}

ロール関連

さて、今回のキモになるのがロール関連のリソースだ。
invoke:mcpという権限を持ったロールを作り、ログインしたユーザにそのロールがアタッチされていたら認証を通す、という作りにする。

リソースがたくさんあって分かりにくいが、ざっくり以下の内容だ。

  • Resource Serverにスコープを追加する
  • クライアントにスコープを追加する
  • ロールを作成する
  • ↑で作成したスコープを同じものをロールに権限として付与する
  • ロールをユーザ(example@gmail.comで作成している前提でデータソースにしている)にアタッチする
resource "auth0_resource_server_scopes" "amazon_bedrock_agentcore_gateway_ib_auth" {
  resource_server_identifier = auth0_resource_server.amazon_bedrock_agentcore_gateway_ib_auth.identifier

  scopes {
    name = "invoke:mcp"
  }
}

resource "auth0_client_grant" "amazon_bedrock_agentcore_gateway_ib_auth" {
  client_id = auth0_client.amazon_bedrock_agentcore_gateway_ib_auth.id
  audience  = auth0_resource_server.amazon_bedrock_agentcore_gateway_ib_auth.identifier

  scopes = [
    "invoke:mcp",
  ]
}

resource "auth0_role" "amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp" {
  name        = "amazon-bedrock-agentcore-gateway-ib-auth-invoke-mcp"
  description = "Can invoke mcp"
}

resource "auth0_role_permissions" "amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp" {
  depends_on = [auth0_resource_server_scopes.amazon_bedrock_agentcore_gateway_ib_auth]

  role_id = auth0_role.amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp.id

  permissions {
    name                       = "invoke:mcp"
    resource_server_identifier = auth0_resource_server.amazon_bedrock_agentcore_gateway_ib_auth.identifier
  }
}

data "auth0_user" "sample_user" {
  query = "email:example@gmail.com"
}

resource "auth0_user_roles" "assign_reader_to_user" {
  user_id = data.auth0_user.sample_user.id
  roles = [
    auth0_role.amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp.id,
  ]
}

さらに、Amazon Bedrock Agentcore Gatewayのインバウンド認証では、認可情報の確認はしてくれないので、Auth0のpost-loginのアクションを追加して自前で認可を行う。
※この部分がトイルになるので、将来の機能拡張で対応してもらえると大変助かる……

post-loginのアクションは以下のように定義する。
どのクライアントに何のロールが設定されていれば良いかという認可のための情報は、ここでアクションシークレットとして保持する。

resource "auth0_action" "amazon_bedrock_agentcore_gateway_ib_auth" {
  name    = "Amazon Bedrock AgentCore Gateway Inbound Action"
  runtime = "node18"
  deploy  = true
  code    = file("./auth0_action.js")

  supported_triggers {
    id      = "post-login"
    version = "v3"
  }

  secrets {
    name = "CLIENT_${auth0_client.amazon_bedrock_agentcore_gateway_ib_auth.client_id}"
    value = jsonencode([
      auth0_role.amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp.name
    ])
  }
}

resource "auth0_trigger_action" "amazon_bedrock_agentcore_gateway_ib_auth" {
  trigger   = "post-login"
  action_id = auth0_action.amazon_bedrock_agentcore_gateway_ib_auth.id
}

呼び出すアクションスクリプトは以下のような感じだ。
内容が拙くて申し訳ないが……。
期待するロールが認証したユーザにアタッチされていない場合は、access_denied: missing required role for this applicationのエラーとなるようにしている。

auth0_action.js
exports.onExecutePostLogin = async (event, api) => {
  const clientId = event.client?.client_id || null;
  if (!clientId) {
    api.access.deny("access_denied: missing client_id");
    return;
  }

  const normalize = (s) => String(s).replace(/[^A-Za-z0-9_]/g, "_");
  const secretName = `CLIENT_${normalize(clientId)}`;

  const raw = event.secrets?.[secretName];
  if (!raw) {
    return;
  }

  let requiredRoles = [];
  try {
    const parsed = JSON.parse(raw);
    if (Array.isArray(parsed)) requiredRoles = parsed.filter(Boolean).map(String);
  } catch (e) {
    console.error(`Invalid JSON in secret ${secretName}:`, e);
    api.access.deny("access_denied: invalid role config");
    return;
  }

  // Empty array allowed.
  if (requiredRoles.length === 0) return;

  // Check for roles
  const userRoles = Array.isArray(event.authorization?.roles) ? event.authorization.roles : [];

  // At least one match is allowed.
  const hasIntersection = userRoles.some((r) => requiredRoles.includes(r));
  if (!hasIntersection) {
    console.error("Role check failed", { clientId, requiredRoles, userRoles });
    api.access.deny("access_denied: missing required role for this application");
    return;
  }
};

Amazon Bedrock AgentCore Gateway

実はAmazon Bedrock AgentCore Gatewayのリソースはあまり修正する必要がない
audienceの向き先を、先に作ったインバウンド用のResource Serverに合わせておけば良い。

data "external" "bedrock_agentcore_gateway" {
  program = ["python3", "./agentcore_gateway.py"]

  query = {
    aws_region    = data.aws_region.current.region
    gateway_name  = "${local.bac_gateway_name}"
    role_arn      = aws_iam_role.bac_gateway.arn
    discovery_url = "https://${var.auth0_domain}/.well-known/openid-configuration"
    audience      = auth0_resource_server.amazon_bedrock_agentcore_gateway_ib_auth.identifier
  }
}

いざ、動かす!

さて、これでterraform applyすれば準備はできているが、クライアント側にも細工が必要になる。
こんな関数を作って組み込もう。

前回の記事のスクリプトでは、払い出したトークンをBEARER_ACCESS_TOKENの環境変数に入れる前提にしていたので、今回もこの関数の中でBEARER_ACCESS_TOKENを更新している。
streamablehttp_clientを作る前に呼び出しておこう。

当然のことながら、払い出し前にはトークンのJWT検証など、必要な処理が他にもあるので、それは適宜追加していただきたい。

def get_token():
    """
    Get Auth0 access token.
    """

    device_code_response = requests.post(
        f"https://{os.environ.get("AUTH0_DOMAIN")}/oauth/device/code",
        data={
            "client_id": os.environ.get("AUTH0_CLIENT_ID"),
            "scope": "openid profile email offline_access invoke:gateway",
            "audience": os.environ.get("AUTH0_AUDIENCE"),
        },
    ).json()
    print(
        "Open URL to complete verification:",
        device_code_response["verification_uri_complete"],
    )

    interval = device_code_response.get("interval", 5)
    while True:
        token_response = requests.post(
            f"https://{os.environ.get("AUTH0_DOMAIN")}/oauth/token",
            data={
                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                "device_code": device_code_response["device_code"],
                "client_id": os.environ.get("AUTH0_CLIENT_ID"),
            },
        )
        if token_response.status_code == 200:
            tokens = token_response.json()
            access_token = str(tokens["access_token"])
            print(f"Auth0 access token has issued.")
            os.environ["BEARER_ACCESS_TOKEN"] = f"{access_token}"
            return

        err = token_response.json().get("error")
        if err in ("authorization_pending", "slow_down"):
            print(f"Getting token... reason: {token_response}")
            time.sleep(interval + (2 if err == "slow_down" else 0))
        else:
            raise RuntimeError(f"Device flow failed: {err}")

これを動かすと、

$ python main.py "neruneruoについて教えてください"
Auth0 access token is not set. Refreshing...
Open URL to complete verification: https://xxxxxxxxxx.us.auth0.com/activate?user_code=XXXX-XXXX
Getting token... reason: <Response [403]>
Getting token... reason: <Response [403]>
Getting token... reason: <Response [403]>

という出力がされるので、ブラウザでURLを開こう。

image.png

「確認」を押下すると、ログイン画面が表示されるので、事前に登録しているユーザでログインしよう

image.png

ログインすると、以下の画面が出て、

image.png

コンソール上では処理が進んで、前回記事と同じような結果が得られる。

Auth0 access token has issued.
neruneruoという名前のプロフィール情報を調べたいようですね。その人物に関する情報を取得するために、プロフィール検索ツールを使用します。
Tool #1: xxx-bac-gw-mcps-example-gateway-target___getProfile
neruneruoさんのプロフィール情報が見つかりました。以下が情報です:

- 年齢: 30歳
- 部署: 開発部門 (development)

この情報によると、neruneruoさんは30歳で、会社の開発部門に所属していることがわかります。他に何か具体的に知りたい情報がありましたら、お気軽にお尋ねください。

次に、HCLでauth0_user_roles.assign_reader_to_userを消して(要はロールを外して)apply後に再度認証をしてみよう。

image.png

すると、しっかりエラーになってくれる。
以下のように、Auth0のログでも、事前に設定したワードがdescriptionに表示されている。

image.png

これで、Amazon Bedrock AgentCoreでも外部IdPを使ったユーザ認証認可ができるようになった!
GAまでにもう少し使いやすくなることを期待する!

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?