初版: 2020/12/16
著者: 田畑義之, 株式会社日立製作所 (GitHubアカウント: @y-tabata)
はじめに
OSSのIAM(Identity and Access Management)製品で、OAuthの認可サーバとしても使えるKeycloakが、バージョン10.0.0でToken Revocation (RFC 7009)をサポートしました1。(ちなみに筆者がPR #6704でコミットしました。)
エンハンスリクエスト用のJIRAチケット2にも多数のVoteが集まっており、このToken Revocationがいかに渇望されていたかがわかります。
今回は、OAuthの認可サーバにとって、このToken Revocationがいかに重要かということを説明しようと思います。Keycloakに関する詳細は、Think ITの連載「Keycloakで実現するAPIセキュリティ」をご参照ください。
本記事は、あくまで執筆者の見解であり、日立製作所及びRed Hatの公式なドキュメントではありません。
Token Revocation (RFC 7009)とは
Token Revocationとは、RFC 7009で定義されているトークン無効化方法です。認可サーバがtokenパラメータで渡されたトークンを無効にすることで、アプリケーション(RP)は以降当該トークンを用いたAPIコールないしトークンリフレッシュができなくなります。
POST /revoke HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
ではログアウトとはどう違うのでしょうか。Keycloakは初期のころから、Logout Endpointを通じたログアウトをサポートしています。ログアウトした場合も、認可サーバがログアウトしたユーザのトークンを無効にすることで、アプリケーションは以降当該トークンを用いたAPIコールないしトークンリフレッシュができなくなります。
以降、ログアウトを引き合いに出しつつ、Token Revocationの必要性を説明していきます。
Token Revocationとログアウトの違い
近年、様々なアプリケーションのログイン画面で、「GitHubアカウントでログイン」や「Twitterアカウントでログイン」といったようなものをよく見かけるかと思います。
出典: https://qiita.com/
これは、アプリケーションがGitHubやTwitterの認可サーバに認証を委譲していることを意味します。つまり、今や1つのユーザアカウントで複数のアプリケーションにログインするということは日常的に行われています。
この状態で、ユーザAがアプリケーション1からログアウトすることを考えます。
ユーザAによるログアウト操作で、アプリケーション1がLogout Endpointをコールしたとしましょう。
すると、ユーザAのトークンが無効になります。このとき問題が発生します。ユーザAは、アプリケーション1のみからログアウトしたつもりが、なぜかアプリケーション2からもログアウトされてしまいます。1回のログアウトですべてのアプリケーションからログアウトできるというのは、SSO用途を考えれば便利な機能ですが、今回のようなユースケースでは、ユーザビリティに課題があります。
一方、ユーザAによるログアウト操作で、アプリケーション1がToken Revocation Endpointをコールした場合はどうでしょうか。
この場合は、Token Revocation Endpointコール時にtokenパラメータに指定したトークン、つまりアプリケーション1に払い出されたトークンのみが無効になるため、アプリケーション2からログアウトされることはありません。
以上より、昨今のこのようなユースケースにおいて、このToken Revacationがいかに重要な機能かをお分かりいただけたかと思います。
KeycloakのToken Revocationを使ってみる
Keycloakを用いて、Token Revocationを試してみましょう。Keycloakは2020/11/27時点のmasterブランチのものを使用します。
まずは2つのアプリケーションに同一ユーザでログインします。今回はsample_applicationというアプリケーションとaccount-consoleというアプリケーションにログインしました。
まずは、Logout Endpointを叩いてみましょう。
$ curl -i http://keycloak.example.com:8080/auth/realms/
sample_service/protocol/openid-connect/logout -d "refresh_token=$refresh_token&c
lient_id=sample_application&client_secret=***"
HTTP/1.1 204 No Content
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Referrer-Policy: no-referrer
Content-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';
Date: Fri, 04 Dec 2020 01:56:49 GMT
X-Robots-Tag: none
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
想定通り、セッションが空になり、2つのアプリケーションの両方からログアウトされました。
次に、再度2つのアプリケーションに同一ユーザでログインし、今度はToken Revocation Endpointを叩いてみましょう。ここでは、sample_applicationに発行したトークンを無効にします。
$ curl -i http://keycloak.example.com:8080/auth/realms/sample_service/protocol/openid-connect/revoke -d "token=$refresh_token&client_id
=sample_application&client_secret=***"
HTTP/1.1 200 OK
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Referrer-Policy: no-referrer
Content-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';
Date: Fri, 04 Dec 2020 02:02:30 GMT
Connection: keep-alive
X-Robots-Tag: none
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Content-Length: 0
想定通り、sample_applicationのセッションのみが削除され、account-consoleのセッションが残りました。
ちなみに、KeycloakはRFC 8414、RFC 8615に準拠したAuthorization Server Metadataを/auth/realms/{realm}/.well-known/openid-configurationというエンドポイントで公開しており、そのエンドポイントからも今回使用した2つのエンドポイントを確認できます。(Authorization Server MetadataへのToken Revocation Endpointの追加も筆者がPR #7106でコミットしました。)
$ curl http://keycloak.example.com:8080/auth/realms/sample_service/.well-known/openid-configuration | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 3371 100 3371 0 0 219k 0 --:--:-- --:--:-- --:--:-- 235k
{
...
"end_session_endpoint": "http://keycloak.example.com:8080/auth/realms/sample_service/protocol/openid-connect/logout",
...
"revocation_endpoint": "http://keycloak.example.com:8080/auth/realms/sample_service/protocol/openid-connect/revoke",
"revocation_endpoint_auth_methods_supported": [
"private_key_jwt",
"client_secret_basic",
"client_secret_post",
"tls_client_auth",
"client_secret_jwt"
],
"revocation_endpoint_auth_signing_alg_values_supported": [
"PS384",
"ES384",
"RS384",
"HS256",
"HS512",
"ES256",
"RS256",
"HS384",
"ES512",
"PS256",
"PS512",
"RS512"
],
...
}
おわりに
本稿では、Token Revocationについてご紹介しました。Token Revocationをサポートしたことで、KeycloakはOAuth周りの機能を一通りそろえたとともに、現在はFAPI (Financial-grade API)への対応も絶賛推進中です3。是非この機会にKeycloakを使ってみてはいかがでしょうか。