Table of Contents
RFC 7523 JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants の Section 2.1. Using JWTs as Authorization Grants では、RFC 6749 で定義されている OAuth 2.0 標準フロー群とは異なる、アクセストークン発行のためのフローを定義しています。我々はそれを JWT 認可グラントフローと呼んでいます。
そのフローでは、JWT (RFC 7519) が認可グラントとして使用されます。認可グラントは、その保持者がアクセストークンを取得する認可を得ていることを示すものです。認可グラントとしての JWT は、認可コードフロー (RFC 6749 Section 4.1) の認可コードと同じ概念です。
アクセストークン要求者は、トークンエンドポイント (RFC 6749 Section 3.2) で JWT を提示することによりアクセストークンを取得できます。次の図はそのフローを示しています。
**認可グラントとしての JWT が誰によってどのように生成されるかに関する詳細を RFC 7523 では定義していません。**従って、JWT の署名を検証するのに用いる鍵の入手方法が仕様では定義されていません。そのため、署名検証用鍵を特定するのに必要な独自規則をそれぞれの運用が定義しなければなりません。
例えば、システムは 「JWT は https://example.com
が発行した ID トークンでなければならない」 という規則を定めるかもしれません。JWT が ID トークンであれば、認可サーバーの実装は標準的な仕組み (つまりディスカバリーエンドポイントと jwks_uri
サーバーメタデータ) を使って署名検証用鍵を見つけることができます。他のシステムは、OpenID Connect Federation 1.0 のエンティティーステートメントと同じように、JWT 自身に署名検証用鍵を埋め込むことを選択するかもしれません。
いずれにしても、RFC 7523 を採用する際は、同仕様を補完するための追加規則を定義しなければなりません。
JWT 認可グラントフローと他のフローを区別するため、新しいグラントタイプ urn:ietf:params:oauth:grant-type:jwt-bearer
が仕様で定義されています。この値はトークンリクエストの grant_type
リクエストパラメーターの値として使用されます。
仕様は、トークンエンドポイントにおける**クライアント認証を要求しておらず、さらにはクライアントの特定**すら要求していません。仕様は次のように述べています。
JWT authorization grants may be used with or without client authentication or identification.
JWT 認可グラントの使用に際し、クライアント認証やクライアント特定が伴いうる。
技術的には 『クライアント認証が伴いうる』 とは、クライアントアプリケーションのクライアントタイプ (RFC 6749 Section 2.1) がパブリックなのかコンフィデンシャルなのかについて仕様は気にしないということを意味しています。また 『クライアント未特定』 とは、トークンリクエストがクライアントを特定するための情報を含んでいないことを意味します (例えば client_id
リクエストパラメーターの欠如など)。
OAuth 2.0 標準フロー群でも可能なように、JWT 認可グラントフローのトークンリクエストにおいても、要求するスコープ群を scope
リクエストパラメーターで指定することができます。
認可グラントとして用いる JWT は assertion
リクエストパラメーターで指定されます。このリクエストパラメーターは、RFC 7521 Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants の Section 4.1. Using Assertions as Authorization Grants で定義されています。
次の表は、JWT クレーム群の要否を要約したものです。クレーム群に対する詳細な要求事項は RFC 7523 の Section 3 で説明されています。
クレーム | 要否 |
---|---|
iss |
必須 |
sub |
必須 |
aud |
必須 |
exp |
必須 |
nbf |
任意 |
iat |
任意 |
jti |
任意 |
次のものは、RFC 7523 の Section 2.1 に掲載されている例です。
POST /token.oauth2 HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
eyJpc3Mi[...omitted for brevity...].
J9l-ZhwP[...omitted for brevity...]
JWT 認可グラントフローのトークンレスポンスは RFC 6749 に準拠します。RFC 7523 では追加のレスポンスパラメーターは定義されていません。
唯一の注意点は、与えられた JWT が無効の際、error
レスポンスパラメーターの値として invalid_grant
を用いなければならないということです。
JWT 認可グラントフローは Authlete 2.3 以降でサポートされます。
/auth/token
API のレスポンスJWT 認可グラントフローをサポートするため、新しいアクション値 JWT_BEARER
が追加されました。トークンリクエストの grant_type
パラメーターの値が urn:ietf:params:oauth:grant-type:jwt-bearer
で、当リクエストが Authlete サーバー側で実行される基本的なバリデーション処理をパスした場合、Authlete の /auth/token
API からのレスポンス内の action
レスポンスパラメーターの値は JWT_BEARER
になります。
JWT 認可グラントフローをサポートするためには、認可サーバーの実装は JWT_BEARER
アクションを処理しなければなりません。次に示す switch
文は authlete-java-jaxrs ライブラリの TokenRequestHandler.java から抜粋したもので、JWT_BEARER
アクション処理の一つの例です。当 switch
文には JWT_BEARER
用の case
エントリーがあります。
// Dispatch according to the action.
switch (action)
{
case INVALID_CLIENT:
// 401 Unauthorized
return ResponseUtil.unauthorized(content, CHALLENGE);
case INTERNAL_SERVER_ERROR:
// 500 Internal Server Error
return ResponseUtil.internalServerError(content);
case BAD_REQUEST:
// 400 Bad Request
return ResponseUtil.badRequest(content);
case PASSWORD:
// Process the token request whose flow is "Resource Owner Password Credentials".
return handlePassword(response);
case OK:
// 200 OK
return ResponseUtil.ok(content);
case TOKEN_EXCHANGE:
// Process the token exchange request (RFC 8693)
return handleTokenExchange(response);
case JWT_BEARER:
// Process the token request which uses the grant type
// urn:ietf:params:oauth:grant-type:jwt-bearer (RFC 7523).
return handleJwtBearer(response);
default:
// This never happens.
throw getApiCaller().unknownAction("/api/auth/token", action);
}
次の表は /auth/token
API からのレスポンスに含まれる JWT 認可グラント関連のレスポンスパラメーターのリストです。
レスポンスパラメーター | 型 | 説明 |
---|---|---|
assertion |
文字列 | トークンリクエストの assertion リクエストパラメーターの値。 |
scopes |
文字列配列 | トークンリクエストの scope リクエストパラメーターの値。 |
トークンリクエストのグラントタイプが urn:ietf:params:oauth:grant-type:jwt-bearer
のとき、Authlete は (正確には Authlete の /auth/token
API の実装は) 下記に示すバリデーション処理をこの順番で実施します。そのため、認可サーバーの実装は同じバリデーション処理を省略できます。
番号 | バリデーション |
---|---|
1 | assertion リクエストパラメーターが与えられていること、その値が空でないことを確認する。 |
2 | フォーマットが JWT 仕様 (RFC 7519 JSON Web Token (JWT)) に従っていることを確認する。 |
3 | 当 JWT が暗号化されている場合、(a) サービスの jwtGrantEncryptedJwtRejected フラグが true であればトークンリクエストを拒否し、(b) 当フラグが false であれば以降のバリデーション処理をスキップする。 |
4 | 当 JWT が iss クレームを含んでおり、その値が JSON 文字列であることを確認する。 |
5 | 当 JWT が sub クレームを含んでおり、その値が JSON 文字列であることを確認する。 |
6 | 当 JWT が aud クレームを含んでおり、その値が JSON 文字列または JSON 文字列の配列であることを確認する。 |
7 | サービスの発行者識別子 (issuer サーバーメタデータ) またはトークンエンドポイントの URL (token_endpoint サーバーメタデータ) が aud クレームにリストされていることを確認する。 |
8 | 当 JWT が exp クレームを含んでおり、現在時刻が当クレームで示される時刻に達していないことを確認する。 |
9 | 当 JWT が iat クレームを含んでいる場合、現在時刻が当クレームで示される時刻以降であることを確認する。 |
10 | 当 JWT が nbf クレームを含んでいる場合、現在時刻が当クレームで示される時刻以降であることを確認する。 |
11 | 当 JWT が無署名の場合、(a) サービスの jwtGrantUnsignedJwtRejected フラグが true であればトークンリクエストを拒否し、(b) 当フラグが false であれば入力トークンに対するバリデーション処理を終了する。 |
Authlete は、JWT 認可グラントに関連する設定項目を幾つか提供しています。認可サーバー実装者はこれらを活用してもよいですし、独自規則を実装してもかまいません。
設定項目 | 型 | 説明 |
---|---|---|
特定可能なクライアントのみ | 特定不能クライアントによるトークンリクエストを拒否するか否か - Service.jwtGrantByIdentifiableClientsOnly (JavaDoc) |
|
暗号化された JWT を拒否 | 真偽値 | 暗号化された JWT を認可グラントとして使うトークンリクエストを拒否するか否か - Service.jwtGrantEncryptedJwtRejected (JavaDoc) |
無署名の JWT を拒否 | 真偽値 | 無署名の JWT を認可グラントとして使うトークンリクエストを拒否するか否か - Service.jwtGrantUnsignedJwtRejected (JavaDoc) |
Java で書かれたオープンソースの認可サーバー実装サンプルである java-oauth-server の JwtAuthzGrantProcessor.java が、JWT 認可グラントフローのトークンリクエスト処理の実装サンプルとなっています。
当実装は一例に過ぎず、商用利用可能な完璧さは意図していないのでご注意ください。
1. 何らかの方法で JWT を用意する。
$ JWT=eyJraWQiOiJhdXRobGV0ZS1mYXBpZGV2LWFwaS0yMDE4MDUyNCIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL2ZhcGlkZXYtYXMuYXV0aGxldGUubmV0LyIsInN1YiI6IjEwMDQiLCJhdWQiOlsiNTg5OTQ2MzYxNDQ0ODA2MyJdLCJleHAiOjE2NTg2NzExMzAsImlhdCI6MTY1ODY3MDgzMCwiYXV0aF90aW1lIjoxNjU4NjcwODMwLCJub25jZSI6IjEyMzQ1Njc4OSIsInZlcmlmaWVkX2NsYWltcyI6eyJ2ZXJpZmljYXRpb24iOnsidHJ1c3RfZnJhbWV3b3JrIjoibmlzdF84MDBfNjNBIn0sImNsYWltcyI6eyJnaXZlbl9uYW1lIjoiSW5nYSIsImZhbWlseV9uYW1lIjoiU2lsdmVyc3RvbmUiLCJiaXJ0aGRhdGUiOiIxOTkxLTExLTA2IiwiOmFnZV8xOF9vcl9vdmVyIjpudWxsfX19.mxE8FQaDb0edY_rWasSQ7pEMXbFon7oWr-Ccv1dB15q8eh2MaKRGrgvwPw_XjAdXlMNzkcV6iEUjRUvLGTvzm7_45cdoOxRX1xWzQw-vwvRbM46xd3Yht3EVjyRUUBJ_92J1yBmu7Nn93rygcnCE-fC_bSTSIJWgEnoC7dpxHYnoJ2QHrIOYFMBAA_3ZYCLGpgiWbIZnB2D1ib2eqwJ9zoJqeFNEBhXo9ThYkASHYaG-ZWofy7364lgeV4Rqy1r4XqzchFRW4yzWs_IM72bTtXTUkstlNOxZU12KEz50uVhtcOXv06iI71I9vceRP-ZVICpq7Knt0vEKWTM41E3ziw
2. トークンリクエストを行う。
$ curl http://localhost:8080/api/token -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer -d assertion=$JWT -d client_id=5908895171 -d scope=email
3. トークンレスポンスを受け取る。
{
"access_token":"NEdL-q9EfOI4S5XzaMeimXAXVqS139Jm9DTYeLUAd5o",
"token_type":"Bearer",
"expires_in":86400,
"scope":"email"
}