Table of Contents
RFC 7523 JSON Web Token (JWT) プロファイル の セクション 2.1. JWT を認可グラントとして使用する では、RFC 6749 で定義された OAuth 2.0 標準フローとは異なるアクセストークン発行のフローが定義されています。このフローを JWT認可グラント フローと呼びます。
このフローでは、JWT (RFC 7519) を 認可グラント として使用します。これは、JWT の保有者がアクセストークンを取得する権限を持っていることを示します。JWT を認可グラントとして使用する という概念は、認可コードフロー (RFC 6749 セクション 4.1) における 認可コード と同じです。
アクセストークンを取得するには、リクエスターが トークンエンドポイント (RFC 6749 セクション 3.2) に JWT を提示します。以下の図は、このフローを示しています。
RFC 7523 は、JWT を認可グラントとしてどのように、誰が生成するかの詳細を定義していません。 そのため、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 認可グラントは、クライアント認証または識別の有無に関わらず使用することができます。
技術的に言うと、“クライアント認証の有無” とは、クライアントアプリケーションの クライアントタイプ (RFC 6749 セクション 2.1) が パブリック か コンフィデンシャル かを気にしないことを意味し、“クライアントの識別なし” とは、トークンリクエストにクライアントを識別する情報 (例: client_id
リクエストパラメータの欠如) が含まれないことを意味します。
JWT 認可グラントフローのトークンリクエストには、標準の OAuth 2.0 フローと同様に、scope
リクエストパラメータを含めてリクエストするスコープを指定することができます。
JWT を認可グラントとして使用する場合、RFC 7521 の セクション 4.1 に定義されている assertion
リクエストパラメータを使用します。
以下の表は、JWT に含めるべきクレームの要否を示したものです。各クレームの詳細な要件については、RFC 7523 の セクション 3 を参照してください。
クレーム | 必須か否か |
---|---|
iss |
必須 |
sub |
必須 |
aud |
必須 |
exp |
必須 |
nbf |
任意 |
iat |
任意 |
jti |
任意 |
以下は、RFC 7523 セクション 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[...省略...].
J9l-ZhwP[...省略...]
JWT 認可グラントフローにおけるトークンレスポンスは、RFC 6749 に準拠しています。RFC 7523 により追加されるレスポンスパラメータはありません。
唯一の注意点として、提供された JWT が無効な場合、error
レスポンスパラメータの値として invalid_grant
を使用しなければなりません。
JWT 認可グラントフローは、Authlete 2.3 以降でサポートされています。
/auth/token
API のレスポンスJWT 認可グラントフローをサポートするために、新しい action
値 JWT_BEARER
が追加されました。トークンリクエストの grant_type
リクエストパラメータの値が urn:ietf:params:oauth:grant-type:jwt-bearer
であり、Authlete サーバー側での基本的な検証ステップを通過すると、Authlete の /auth/token
API のレスポンスにおける action
パラメータの値として JWT_BEARER
が設定されます。
認可サーバーの実装では、JWT 認可グラントフローをサポートするために JWT_BEARER
アクションを処理する必要があります。以下は、authlete-java-jaxrs ライブラリの TokenRequestHandler.java から抜粋した switch
文の例であり、JWT_BEARER
の case
エントリが含まれています。
// アクションに応じて処理を分岐
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:
// "リソースオーナーパスワードクレデンシャル" フローの処理
return handlePassword(response);
case OK:
// 200 OK
return ResponseUtil.ok(content);
case TOKEN_EXCHANGE:
// トークン交換リクエスト (RFC 8693) の処理
return handleTokenExchange(response);
case JWT_BEARER:
// JWT 認可グラント (RFC 7523) を使用するトークンリクエストの処理
return handleJwtBearer(response);
default:
// あり得ないケース
throw getApiCaller().unknownAction("/api/auth/token", action);
}
以下の表は、/auth/token
API のレスポンスにおいて JWT 認可グラントに関連するパラメータを示しています。
レスポンスパラメータ | 型 | 説明 |
---|---|---|
assertion |
string | トークンリクエストの assertion リクエストパラメータの値。 |
scopes |
string array | トークンリクエストの scope リクエストパラメータの値。 |
トークンリクエストのグラントタイプが urn:ietf:params:oauth:grant-type:jwt-bearer
の場合、Authlete (具体的には、Authlete の /auth/token
API の実装) は以下の検証ステップを順番に実行します。そのため、認可サーバーの実装では、同じ検証ステップを省略できます。
No. | 検証内容 |
---|---|
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 クレームが含まれており、現在時刻が exp で示される時刻を超えていないことを確認する。 |
9 | JWT に iat クレームが含まれている場合、現在時刻が iat で示される時刻以上であることを確認する。 |
10 | JWT に nbf クレームが含まれている場合、現在時刻が nbf で示される時刻以上であることを確認する。 |
11 | JWT が署名されているかを確認し、(a) jwtGrantUnsignedJwtRejected フラグが true の場合はリクエストを拒否する、(b) それ以外の場合は検証を終了する。 |
JWT 認可グラントに関連するオプションを設定するには、以下の手順に従ってください。
次のオプションが設定可能です。
設定オプション | 説明 |
---|---|
Client ID | クライアント識別子を含まないトークンリクエストを拒否するかどうかを設定します。 |
Encrypted JWT | 暗号化された JWT を認可グラントとして使用するトークンリクエストを許可するか拒否するかを設定します。 |
Unsigned JWT | 署名なしの JWT を認可グラントとして使用するトークンリクエストを許可するか拒否するかを設定します。 |
JwtAuthzGrantProcessor.java は、java-oauth-server という 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"
}