JWT 認可グラント (RFC 7523 2.1)

概要

RFC 7523 JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization GrantsSection 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 の出所

認可グラントとしての 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 GrantsSection 4.1. Using Assertions as Authorization Grants で定義されています。

次の表は、JWT クレーム群の要否を要約したものです。クレーム群に対する詳細な要求事項は RFC 7523Section 3 で説明されています。

クレーム 要否
iss 必須
sub 必須
aud 必須
exp 必須
nbf 任意
iat 任意
jti 任意

リクエストの例

次のものは、RFC 7523Section 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-serverJwtAuthzGrantProcessor.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"
}