Skip to content

【Keycloak】アクセストークンの Audience を変更する

はじめに

Keycloak のアクセストークンにある aud クレームがデフォルトで account になっています。この aud が何者か気になったので、調べてみました。
その結果、アクセストークンの aud を変更する方法がわかりましたので、まとめます。

成果物

https://github.com/kntks/blog-code/tree/main/2024/09/keycloak-token-audience

環境構築

バージョン

バージョン
MacSonoma 14.5
Keycloak25.0.6
Docker26.0.0
Docker Composev2.24.5
Terraform1.9.5

Keycloak のセットアップ

compose.yaml ファイルを作成し、Keycloak を定義します。

compose.yaml
services:
keycloak:
image: quay.io/keycloak/keycloak:25.0.6
container_name: keycloak
ports:
- target: 8080
published: 8080
protocol: tcp
mode: host
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
command: ["start-dev"]
volumes:
- ./keycloak:/opt/keycloak/data

docker compose コマンドを実行して Keycloak を起動します。

Terminal window
docker compose up

Terraformを実行する

Terraform を使用するために Keycloak に terraform クライアント を作成します。

Keycloak で terraform クライアントを作成したら、クライアントシークレットをコピーし、以下ように、client_id, client_secret, url を設定します。

terraform/terraform.tfvars
client_id = "terraform"
client_secret = "xxxxxxxxxxxxxxxxx"
url = "http://localhost:8080"

terraform apply で Keycloak の設定を行います。

Terminal window
cd terraform
terraform init
terraform plan
terraform apply -auto-approve

Audience とは

アクセストークンの Audience

Audience(オーディエンス)は、JWT(JSON Web Token)のクレームの1つで、JWT が意図されている受取人を識別します。

The “aud” (audience) claim identifies the recipients that the JWT is intended for.

「aud」(オーディエンス)クレームは、JWTが意図されている受取人を識別します。

引用:“aud” (Audience) Claim - RFC 7519

ID トークンの Audience

ID トークンの aud は、OAuth 2.0 クライアント ID である必要があります。

REQUIRED. Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value.

(訳)必須。このIDトークンが意図されているオーディエンス。オーディエンスの値として、必ずRelying Party(依存するサービス)のOAuth 2.0 client_idを含める必要があります。

引用:ID Token - OpenID Connect Core 1.0 incorporating errata set 2

Audience の確認方法

Audience の設定方法に進む前に、トークンの発行とデコード方法を紹介します。

方法は2つあります。

  1. Direct access grants を使用してトークンを取得する
  2. Evaluate を使用する

Direct access grants を使用してトークンを取得する

myapp クライアントの Settings タブを開き、Direct access grants を確認します。
チェックが入っていない場合は、チェックを入れて保存します。 keycloak-clients-myapp-client-details keycloak-clients-myapp-client-details-2

Credentials タブからクライアントシークレットをコピーしてください。

keycloak-clients-myapp-client-credentials

先ほどコピーしたクライアントシークレットを以下の curl コマンドに設定し、実行すると、アクセストークンと ID トークンを取得できます。

Terminal window
curl -s \
-d "client_id=myapp" \
-d "client_secret=xxxxxxxxxx" \
-d "username=myuser" \
-d "password=myuser" \
-d "grant_type=password" \
-d "scope=openid" \
"http://localhost:8080/realms/myrealm/protocol/openid-connect/token"

今回は以下のスクリプトを実行することで、アクセストークンと ID トークンをデコードします。

decode.sh
#!/bin/bash
# ref: https://gist.github.com/angelo-v/e0208a18d455e2e6ea3c40ad637aac53?permalink_comment_id=3920605#gistcomment-3920605
res=$(curl -s \
-d "client_id=myapp" \
-d "client_secret=QlaUgKTjZBgHr1vTM8K3clcsOb4IAxdc" \
-d "username=myuser" \
-d "password=myuser" \
-d "grant_type=password" \
-d "scope=openid test-client-scope" \
"http://localhost:8080/realms/myrealm/protocol/openid-connect/token")
access_token=$(jq -r '.access_token' <<< $res)
id_token=$(jq -r '.id_token' <<< $res)
echo "decode access_token"
jq -R 'split(".") | select(length > 0) | .[0],.[1] | @base64d | fromjson' <<< $access_token
echo "decode id_token"
jq -R 'split(".") | select(length > 0) | .[0],.[1] | @base64d | fromjson' <<< $id_token

このスクリプトを実行すると、以下のような結果が得られます。

Terminal window
$ bash decode.sh

アクセストークン

audaccount になっていることがわかります。さらに、realm_accessresource_access にデフォルトロールが含まれていることも確認できます。

{
"alg": "RS256",
"typ": "JWT",
"kid": "pwQ16gsNlgXi_D9Y1eK9oIhDOlAyMw9h00WG0ouKoKo"
}
{
"exp": 1726991696,
"iat": 1726991396,
"jti": "690709a1-d72e-464f-b900-e8a60bdd68d1",
"iss": "http://localhost:8080/realms/myrealm",
"aud": "account",
"sub": "c1189b01-77cd-4c18-8b9b-f4647b99c9d8",
"typ": "Bearer",
"azp": "myapp",
"sid": "10531aec-20ff-4eba-8a6e-6454b22a7aa4",
"acr": "1",
"realm_access": {
"roles": [
"default-roles-myrealm",
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid profile email",
"email_verified": false,
"name": "foo bar",
"preferred_username": "myuser",
"given_name": "foo",
"family_name": "bar",
"email": "myuser@exmple.com"
}

ID トークン

aud はクライアント ID である myapp になっていることがわかります。

{
"alg": "RS256",
"typ": "JWT",
"kid": "pwQ16gsNlgXi_D9Y1eK9oIhDOlAyMw9h00WG0ouKoKo"
}
{
"exp": 1726991696,
"iat": 1726991396,
"jti": "e6d2dddc-815b-47d1-a733-8cda8c3dd238",
"iss": "http://localhost:8080/realms/myrealm",
"aud": "myapp",
"sub": "c1189b01-77cd-4c18-8b9b-f4647b99c9d8",
"typ": "ID",
"azp": "myapp",
"sid": "10531aec-20ff-4eba-8a6e-6454b22a7aa4",
"at_hash": "8uGfup0t4q2e_W8H7_cKmA",
"acr": "1",
"email_verified": false,
"name": "foo bar",
"preferred_username": "myuser",
"given_name": "foo",
"family_name": "bar",
"email": "myuser@exmple.com"
}

Evaluate を使用する

Client scopes タブを開き、Evaluate をクリックすると、クライアントスコープを評価でき、実施にどのようなクレームが含まれるかを確認できます。 keycloak-clients-myapp-client-scopes-evaluate

Keycloak のクライアント設定

Full scope allowed を OFF にする

先ほど access_token のデコード結果から使わないデフォルトのロールがありました。

Keycloak はデフォルトで Full scope allowed が ON になっているため、クライアントに設定されたデフォルトロールがアクセストークンに含まれます。
そのため、Full scope allowed を OFF にすることで、デフォルトのロールを取り除きます。

myapp のクライアントページに移動し、Client scopes タブを開くと、myapp-dedicated があるのでクリックします。 keycloak-clients-myapp-client-scopes-dedicated

Scope タブに切り替えて Full scope allowed を OFF にして保存します。 keycloak-clients-myapp-client-scopes-dedicated-scope

再度、decode.sh を実行すると access_token から audrealm_accessresource_access が無くなったことを確認できます。

{
"alg": "RS256",
"typ": "JWT",
"kid": "pwQ16gsNlgXi_D9Y1eK9oIhDOlAyMw9h00WG0ouKoKo"
}
{
"exp": 1727142673,
"iat": 1727142373,
"jti": "f0429ab4-f22f-4ce5-aeaf-f2d273c4e885",
"iss": "http://localhost:8080/realms/myrealm",
"sub": "c1189b01-77cd-4c18-8b9b-f4647b99c9d8",
"typ": "Bearer",
"azp": "myapp",
"sid": "d0b2aea8-cbfb-4e06-b969-962f9c99c490",
"acr": "1",
"scope": "openid profile email",
"email_verified": false,
"name": "foo bar",
"preferred_username": "myuser",
"given_name": "foo",
"family_name": "bar",
"email": "myuser@exmple.com"
}

Audiance の設定方法

ここではアクセストークンの aud を変更する方法を説明します。

audience に値をセットする方法は2つあります。

  1. クライアントロールを使用した自動追加
  2. ハードコード

参考:Audience support - Keycloak Documentation

クライアントロールを使用した自動追加

Client scopes ページに roles という名前のクライアントロールがデフォルトで存在します。 keycloak-client-scopes-list

roles をクリックし、 Mappers タブを開くと、audience resolve が設定されています。 keycloak-client-scopes-roles

このマッパーは、自分自身(クライアント)とは別クライアントのロールを持っている場合、別クライアント ID を自動的に aud に追加します。

An Audience Resolve protocol mapper is defined in the default client scope roles.
Audience Resolveプロトコルマッパーは、デフォルトのクライアントスコープ「roles」に定義されています。

The mapper checks for clients that have at least one client role available for the current token.
このマッパーは、現在のトークンに対して少なくとも1つのクライアントロールが利用可能なクライアントをチェックします。

The client ID of each client is then added as an audience, which is useful if your service clients rely on client roles.
そして、各クライアントのクライアントIDがオーディエンスとして追加されます。これは、サービスクライアントがクライアントロールに依存している場合に役立ちます。

引用:Automatically add audience - Keycloak Documentation

aud にクライアント ID が自動で追加されることを確認する

実際に、myapp クライアントに realm-management クライアントのロールを追加して、audrealm-management クライアント ID が追加されることを確認します。

今回は、realm-management クライアントにあるロール view-client を myapp クライアントに追加します。 keycloak-client-realm-management-roles

myapp クライアントの Client scopes タブを開き、myapp-dedicated クライアントスコープをクリックします。
※ 新規にクライアントスコープを作成しても構いません。 keycloak-clients-myapp-client-scopes-dedicated

Scope タブに切り替えて、view-client を追加して保存します。 keycloak-clients-myapp-client-scopes-dedicated-scope-assign-role

アクセストークンにロールを反映するためには、ユーザーにもロールを割り当てる必要があります。 keycloak-myuser-role-mapping-assign-role

これで、myapp クライアントと myuser に view-client ロールが割り当てられました。アクセストークンを確認してみましょう。

意図した通り、view-clientrealm-management クライアントのロールなので、audrealm-management クライアント ID が自動で追加されることを確認できました。

{
"alg": "RS256",
"typ": "JWT",
"kid": "pwQ16gsNlgXi_D9Y1eK9oIhDOlAyMw9h00WG0ouKoKo"
}
{
"exp": 1727186929,
"iat": 1727186629,
"jti": "d1fcb2b0-f278-4b4d-b320-ed705f476d16",
"iss": "http://localhost:8080/realms/myrealm",
"aud": "realm-management",
"sub": "c1189b01-77cd-4c18-8b9b-f4647b99c9d8",
"typ": "Bearer",
"azp": "myapp",
"sid": "d69f1f0d-a1cb-48a9-8341-983c48443153",
"acr": "1",
"resource_access": {
"realm-management": {
"roles": [
"view-clients",
"query-clients"
]
}
},
"scope": "openid profile email",
"email_verified": false,
"name": "foo bar",
"preferred_username": "myuser",
"given_name": "foo",
"family_name": "bar",
"email": "myuser@exmple.com"
}

先ほど、クライアントとユーザーに紐付けた view-client ロールは外します。

ハードコード

クライアントロールを使用した aud の自動追加では、自分自身のクライアント ID をアクセストークンの aud に追加することはできません。 そのためもし、自分自身のクライアント ID をアクセストークンの aud に追加したい場合は、ハードコードする必要があります。

そのほかにも、URLなどのカスタム値を設定するときに使用できます。

aud にクライアント ID が追加されることを確認する

今度は、アクセストークンの aud を自分自身のクライアント ID(myapp)に変更します。

再度、myapp クライアントの Client scopes タブを開き、myapp-dedicated クライアントスコープをクリックします。 keycloak-clients-myapp-client-scopes-dedicated

Mappers タブから Configure a new mapper をクリックしてください。 keycloak-clients-myapp-client-scopes-dedicated-mappers

Audience を選択します。 keycloak-clients-myapp-client-scopes-dedicated-new-mapper

Include Client Audience にクライアント ID である myapp を入力して保存します。 keycloak-clients-myapp-client-scopes-dedicated-new-mapper-detail

設定が完了しました。アクセストークンを確認してみましょう。

アクセストークンをデコードした結果、audmyapp クライアント ID が追加されることを確認できました。

{
"alg": "RS256",
"typ": "JWT",
"kid": "pwQ16gsNlgXi_D9Y1eK9oIhDOlAyMw9h00WG0ouKoKo"
}
{
"exp": 1727190112,
"iat": 1727189812,
"jti": "bbe17c2c-4ad5-4b2d-9435-70c77ea7048b",
"iss": "http://localhost:8080/realms/myrealm",
"aud": "myapp",
"sub": "c1189b01-77cd-4c18-8b9b-f4647b99c9d8",
"typ": "Bearer",
"azp": "myapp",
"sid": "0bb0aa25-82d4-4257-918b-ead49129fc1f",
"acr": "1",
"scope": "openid profile email",
"email_verified": false,
"name": "foo bar",
"preferred_username": "myuser",
"given_name": "foo",
"family_name": "bar",
"email": "myuser@exmple.com"
}

まとめ

  • Audience は JWT のクレームの1つです、
  • アクセストークンの aud は JWT が意図されている受取人です。
  • ID トークンの aud は OAuth 2.0 クライアント ID である必要があります。
  • Keycloak でアクセストークンの aud を変更する方法は2つあります。
    • クライアントロールを使用した自動追加
    • ハードコード

参考