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

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 を使用するために 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(オーディエンス)は、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 トークンの 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 の設定方法に進む前に、トークンの発行とデコード方法を紹介します。

方法は2つあります。

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

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

Section titled “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"
}

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

先ほど 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"
}

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

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

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

参考:Audience support - Keycloak Documentation

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

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

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 が自動で追加されることを確認する

Section titled “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 が追加されることを確認する

Section titled “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つあります。
    • クライアントロールを使用した自動追加
    • ハードコード