TLS クライアント証明書を結びつけたアクセストークンの発行

はじめに


本記事では、“RFC 8705 OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens” にて規定されている “Mutual-TLS certificate-bound access tokens” を利用するための、Authlete の設定手順を説明します。





動作のしくみ


“Mutual-TLS certificate-bound access tokens” の設定が有効化されている場合、Authlete の API は次の処理を行います。
  • /auth/token API
    • クライアント証明書 (clientCertificate) をリクエストパラメーターとして受け取り、発行するアクセストークンに結びつけます。
    • JWT 形式のアクセストークンが有効化されている場合には、発行するアクセストークンに、クライアント証明書の情報(サムプリント)を含めます。
  • /auth/introspection API
    • アクセストークン (token) とクライアント証明書 (clientCertificate) をリクエストパラメーターとして受け取り、両者が結びついているかを確認します。
  • /auth/introspection/standard API
    • アクセストークン (token) を、リクエストパラメーターのひとつ (parameters) に含まれる値として受け取り、そのトークンに結びついているクライアント証明書の情報(サムプリント)を返却します。
これにより、相互 TLS によってクライアント証明書を提示できたクライアントのみが、そのアクセストークンの所持を証明し、API リクエストに利用できる(言い換えれば、他のクライアントが利用できない)ようになります。

設定


サービス側のアクセストークン設定


Authlete のサービス管理者コンソールにログインし、 「トークン」タブの「アクセストークン」セクションにおいて以下を設定します。
項目
TLS クライアント証明書を紐付けたアクセストークンのサポート
サポートする
サービス設定の「TLS クライアント証明書を紐付けたアクセストークンのサポート」


クライアント側のアクセストークン設定


Authlete のクライアントアプリ開発者コンソールにアクセスし、 「基本情報」タブにおいて以下を設定します。
項目
TLS クライアント証明書を紐付けたアクセストークンの使用
使用する
クライアント設定の「TLS クライアント証明書を紐付けたアクセストークンの使用」


以上により、“Mutual-TLS certificate-bound access tokens” が利用可能となります。

実行例


以下は設定後の、各 API の実行例です。(読みやすさのため折り返しています)

/auth/token API


リクエスト


認可サーバーはクライアントとの相互 TLS 接続から得られたクライアント証明書を "clientCertificate" の値として指定し、Authlete にリクエストを送信します。

$ curl -s -X POST https://api.authlete.com/api/auth/token \
 -u ...:... \
 -H 'Content-type: application/json' \
 -d '{'\
    '"clientCertificate":"-----BEGIN CERTIFICATE-----\n
MIIDPDCCAiQCCQDfkemtGUyxAjANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJK\n
UDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\n
BkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTIwMDUxMTEx\n
MDcyM1oXDTIxMDUxMTExMDcyM1owYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\n
a3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\n
BAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n
AQoCggEBAMu7V2QyQk4iLPry0OsykR/8WO2aNOBNoVZYDexF1TsFv9s2S4PEDFEt\n
1BZrEmBe5HWpGb1iuDGG6wjAanEkea8AIUgslMsOOB0rQnbJA3nI5wktjCG2VzWo\n
zrmxAAaBM7ixaLPcJT1K0FSu4fzse3X9gtA2rtNGwk1JEMNIBpFghxw7zzUZBduS\n
lkVPPcQ3gPGVutiWfNPNPAqb4eMKwtuTPXbZSJ4RO9xGKMIuoaBWO9xPS6rNy+Kr\n
vOckNEsR+8PBYh9vzbmF0mDlk+BMd9sOGyylYBARFdqukMJnSbVcRvr+Gcaf+QSg\n
FRHkVGWXHXtMRI+MsCvOXrhmlJhxbdMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA\n
JfEyXTEGdjchre2dFCa/WLdH5PZBqB+E0Umm+/I+puA+aUpkRaOfmIUPNaCZ1Lgg\n
mOo8BGuEG0oXpOSBoRkxf46d1bxJk5YSe+TgSxS6YX/mE6Lt1Aj7HjojlCuNFGS6\n
vzyEugQSsOjuoIPS32PvqmZ+80jzVNUsMD76xGl32ZsVeJmDnogVPZ4NlolQcmH2\n
bYjK00uPXaUU30IVu+UTrEBhe30My/aW31NbT2I7Ct52wwu67pyP3QOKmuLxMd6N\n
+KkxfIY23zla4herq9vWZejgfBfkAwOapeIIHGYtY7IFxm6nqufb4h71vjeoouOt\n
31J7bEcAU9WRP1Pr/X4hLA==\n-----END CERTIFICATE-----\n",'\
'"parameters"':\
'"redirect_uri=https://client.example.org/cb/example.com'\
'&grant_type=authorization_code'\
'&client_id=...'\
'&code=smA...Gp4"'\
}'

レスポンス


クライアント証明書に結びつけられたアクセストークンが発行されます。

{
  "type": "tokenResponse",
  "resultCode": "A050001",
  "resultMessage": 
    "[A050001] The token request (grant_type=authorization_code)
      was processed successfully.",
  "accessToken": "AHu...MMs",
  "accessTokenDuration": 86400,
  "accessTokenExpiresAt": 1591935416474,
  "action": "OK",
  "clientId": 17566160603766,
  "clientIdAliasUsed": false,
  "grantType": "AUTHORIZATION_CODE",
  "refreshToken": "_zkZeqy6pLdFerpwqJPh0S0gEZsSj9EbmwVsN92WNsE",
  "refreshTokenDuration": 864000,
  "refreshTokenExpiresAt": 1592713016474,
  "responseContent": 
    "{\"access_token\":\"AHu...MMs\",
      \"refresh_token\":\"_zk...NsE\",
      \"scope\":null,
      \"token_type\":\"Bearer\",
      \"expires_in\":86400}",
  "subject": "testuser01"
}

JWT 形式のアクセストークンの場合には、以下のようなアクセストークンになります。

eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.
eyJzdWIiOiJ0ZXN0dXNlcjAxIiwic2NvcGUiOm51bGwsImlzcyI6Imh0dHBzOi8
vYXMuZXhhbXBsZS5jb20iLCJjbmYiOnsieDV0I1MyNTYiOiJPSURfU2MyeVJlVE
R4OVFTN2YxU01Vek54c2g3a2hKWW1hSXdxWHc4WXV3In0sImV4cCI6MTU5MTkzO
TMyNiwiaWF0IjoxNTkxODUyOTI2LCJjbGllbnRfaWQiOiIxNzU2NjE2MDYwMzc2
NiIsImp0aSI6InN6dDVnV0ZTb3ZGSVU1Ti13amRMeUZELUVpUzNyZEcyS1IzMTF
CMkY2RmMifQ.
Pw3IntOrXDqW3Z5eoGSeeJaPG5n1aKEVokgbUh_BJpqzuZ6F6GDDLhj98XBvjii
x9oAmaTpy39ijl1vcq3r1BA

このアクセストークンのペイロード部には、以下のように、証明書のサムプリントが含まれます。

{
  "sub": "testuser01",
  "scope": null,
  "iss": "https://as.example.com",
  "cnf": {
    "x5t#S256": "OID_Sc2yReTDx9QS7f1SMUzNxsh7khJYmaIwqXw8Yuw"
  },
  "exp": 1591939326,
  "iat": 1591852926,
  "client_id": "17566160603766",
  "jti": "szt5gWFSovFIU5N-wjdLyFD-EiS3rdG2KR311B2F6Fc"
}

/auth/introspection API


リクエスト


リソースサーバーはクライアントとの相互 TLS 接続から得られたクライアント証明書を "clientCertificate" の値として指定し、Authlete にリクエストを送信します。

$ curl -s -X POST https://api.authlete.com/api/auth/introspection \
-u ...:... -H 'Content-type: application/json' \
-d '{'\
'"token":"AHu...MMs",'\
'"clientCertificate":"-----BEGIN CERTIFICATE-----\n
MIIDPDCCAiQCCQDfkemtGUyxAjANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJK\n
UDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\n
BkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTIwMDUxMTEx\n
MDcyM1oXDTIxMDUxMTExMDcyM1owYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\n
a3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\n
BAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n
AQoCggEBAMu7V2QyQk4iLPry0OsykR/8WO2aNOBNoVZYDexF1TsFv9s2S4PEDFEt\n
1BZrEmBe5HWpGb1iuDGG6wjAanEkea8AIUgslMsOOB0rQnbJA3nI5wktjCG2VzWo\n
zrmxAAaBM7ixaLPcJT1K0FSu4fzse3X9gtA2rtNGwk1JEMNIBpFghxw7zzUZBduS\n
lkVPPcQ3gPGVutiWfNPNPAqb4eMKwtuTPXbZSJ4RO9xGKMIuoaBWO9xPS6rNy+Kr\n
vOckNEsR+8PBYh9vzbmF0mDlk+BMd9sOGyylYBARFdqukMJnSbVcRvr+Gcaf+QSg\n
FRHkVGWXHXtMRI+MsCvOXrhmlJhxbdMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA\n
JfEyXTEGdjchre2dFCa/WLdH5PZBqB+E0Umm+/I+puA+aUpkRaOfmIUPNaCZ1Lgg\n
mOo8BGuEG0oXpOSBoRkxf46d1bxJk5YSe+TgSxS6YX/mE6Lt1Aj7HjojlCuNFGS6\n
vzyEugQSsOjuoIPS32PvqmZ+80jzVNUsMD76xGl32ZsVeJmDnogVPZ4NlolQcmH2\n
bYjK00uPXaUU30IVu+UTrEBhe30My/aW31NbT2I7Ct52wwu67pyP3QOKmuLxMd6N\n
+KkxfIY23zla4herq9vWZejgfBfkAwOapeIIHGYtY7IFxm6nqufb4h71vjeoouOt\n
31J7bEcAU9WRP1Pr/X4hLA==\n-----END CERTIFICATE-----\n"'\
'}'

レスポンス


アクセストークン (token) とクライアント証明書 (clientCertificate) の結びつきが確認できた場合には、以下のようなレスポンスが返却されます。このレスポンスには、クライアント証明書のサムプリント(certificateThumbprint) が含まれています。

{
  "type": "introspectionResponse",
  "resultCode": "A056001",
  "resultMessage": "[A056001] The access token is valid.",
  "action": "OK",
  "certificateThumbprint": "OID_Sc2yReTDx9QS7f1SMUzNxsh7khJYmaIwqXw8Yuw",
  "clientId": ...,
  "clientIdAliasUsed": false,
  "existent": true,
  "expiresAt": 1591936228000,
  "refreshable": true,
  "responseContent": "Bearer error=\"invalid_request\"",
  "subject": "testuser01",
  "sufficient": true,
  "usable": true
}

/auth/introspection/standard API


リクエスト


リソースサーバーは認可サーバーのトークンイントロスペクションエンドポイントに、対象のアクセストークンを送信します。認可サーバーはそのリクエスト内容を Authlete に転送します。

$ curl -s -X POST https://api.authlete.com/api/auth/introspection/standard \
-u ...:... -H 'Content-type: application/json' \
-d '{ "parameters":"token=AHu...MMs" }'

レスポンス


アクセストークンに結びつくクライアント証明書が確認できた場合には、Authlete はその証明書のサムプリントを含む responseContent を返却します。認可サーバーはこの responseContent をリソースサーバーに、トークンイントロスペクションエンドポイントからのレスポンスとして返却します。

{
  "type": "standardIntrospectionResponse",
  "resultCode": "A145001",
  "resultMessage": "[A145001] Introspection was performed successfully
    (type=access_token, active=true).",
  "action": "OK",
  "responseContent": 
    "{\"sub\":\"testuser01\",
      \"scope\":null,
      \"iss\":\"https://as.example.com\",
      \"active\":true,
      \"cnf\":{\"x5t#S256\":\"OID_Sc2yReTDx9QS7f1SMUzNxsh7khJYmaIwqXw8Yuw\"},
      \"token_type\":\"Bearer\",
      \"exp\":1591936228,
      \"client_id\":\"17566160603766\"}"
}
How did we do with this article?