Rich Authorization Requests (RAR)

Rich Authorization Requests (RAR)

はじめに

RFC 9396: OAuth 2.0 Rich Authorization Requests (RAR) は OAuth 2.0 の Proposed Standard 拡張仕様です。クライアントは従来の「スコープ」よりも細かな粒度の認可要件を JSON 構造で指定できるようになります。

本記事では、Authlete における RAR のサポートと、その機能を使って適切な粒度の権限を処理する方法を説明します。

RAR とは

リソースオーナーがサードパーティに付与した権限を表現するために、OAuth 2.0 認可フレームワーク は「スコープ」というしくみを導入しました。スコープに対し特定の意味を与えることにより、権限や機能オプションを表現できるようになります。

たとえば OpenID Connect 仕様では、“profile” のようなスコープを、“openid” スコープと同時に定義しています。“profile” スコープは、ユーザープロファイルの特定の属性 へのアクセス権限を、サードパーティに付与します。一方の “openid” スコープはセマンティクスが異なり、ID トークンの生成と UserInfo エンドポイントへのアクセス権限を意味します。

OAuth2 が普及するにつれて、新たなユースケースが登場し、それに対する新たなソリューションが必要となっています。RAR 仕様は、サードパーティがユーザーの承認を得る際にどのようなコンテキスト・意図があるかを示す必要があるケースに対処するものです。たとえば、オンライン決済や、ファイル共有、健康診断の結果などです。

RAR の構造

RAR は共通の JSON 構造を定義しています。この構造を用いて、より細かな粒度の権限を表現できます。それぞれの権限には、“type” 属性と、オプションである “locations” “actions” “datatypes” “identifier” “privileges” 属性、そして独自に定義された属性があります。詳細については Request parameter “authorization_details” セクションをご参照ください。

“type” 属性とそのセマンティクス(表現する内容や、その組み立て方、エンドユーザーに対する意図、そして属性間の関係など)をどう定義するかは、認可サーバー、もしくは認可サーバーが属するエコシステムに任されています。これにより、エンドユーザーに要求する権限を正確に定義できるようになります。

“locations” 属性は Resource Indicators 機構 と同じコンセプトであり、権限がどのリソースサーバー (の URI) に関するものかを指定するものです。“identifier” 属性は権限要求時に、特定リソースの指定に使えます。端的に言えば、“locations” はリソース群を示し、“identifier” は単一のリソースを指します。

“actions” は「動作」、たとえばファイルシステム上でのアクションや、銀行口座に関する操作、医療機器の制御、家電の挙動、データベースの権限などの表現に使えます。

“datatypes” と “privileges” は、要求する権限の特性を示す際に用います。

設定

Authlete の RAR のサポートはデフォルトで有効になっています。しかし、許諾する認可タイプは初期設定では存在していません。RAR を用いるためには、サービスレベルで認可タイプを設定し、クライアント単位で利用可能な認可タイプを指定する必要があります。

サービスレベルの設定

サポートする認可の “type” のリストの設定は管理者が行います。設定はサービスオーナーコンソールの「認可」タブにあります。この、サポートする “type” のリストが、クライアントからリクエスト可能な “type” になります。ただし、クライアントに対して自動的に利用可能になるものではありません。

Screen_Shot_2021-08-09_at_14
List of supported authorization types by a service

クライアント単位の設定

開発者コンソールにおいて、個別のクライアントごとに、リクエスト可能な認可の “type” を指定します。設定は「認可」タブにあります。

Screen_Shot_2021-08-09_at_14
List of authorization types that a client can request

利用例

認可リクエスト

認可リクエストの内容を「プッシュ」しなくても、あるいはリクエストオブジェクトを用いなくても、 RAR は利用可能です。ただし実際には、これらの併用が必須となる場合があります。もし RAR によって表現されるリクエストが非常に大きいときには PAR (Pushed Authorization Requests) が必須となるでしょう。また、もし改ざん防止を求めるのであれば JAR を使うべきです。

たとえばクライアントが、RAR を URL エンコードして、それを認可リクエストに含めて送信した場合、認可リクエストを受け取った認可サーバーから Authlete の /auth/authorization API へのリクエストは、以下のようになります。

  • リクエスト(レスポンスは省略)
curl --request POST 'https://api.authlete.com/api/auth/authorization' \
     --header 'Content-Type: application/json' \
     --header 'Authorization: Basic OXXXXXXXXXX=' \
     --data '{
         "parameters": "client_id=4025660683512920&
            scope=openid&
            response_type=code&
            redirect_uri=https%3A%2F%2Fmobile.example.com%2Fcb&
            code_challenge=NcCW6zMwKWy5Mya8jopzE1SVeTBJBAHH1jU7TPpYK9A&
            code_challenge_method=S256&
            authorization\_details=%5B%7B%22type%22%3A%20%22customer\_information%22%2C%22locations%22%3A%20%5B%22https%3A%2F%2Fexample.com%2Fcustomers%22%2C%5D%2C%22actions%22%3A%5B%22read%22%2C%22write%22%5D%2C%22datatypes%22%3A%5B%22contacts%22%2C%22photos%22%5D%7D%5D
"
     }'

この RAR の内容を、クライアントが JAR にラップした場合には、以下のサンプルのようになります。ここでは、Authlete が認可サーバーに、認可リクエストの内容をエコーバックしています。認可サーバーはこの内容をもとに、ユーザーの承認を確認します。

  • リクエスト
curl --request POST 'https://api.authlete.com/api/auth/authorization' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic OXXXXXX8=' \
--data-raw '{ "parameters": "client_id=4025658857453025&request=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InNpZzEifQ.eyJpc3MiOiI0MDI1NjU4ODU3NDUzMDI1IiwiYXVkIjoiaHR0cHM6Ly9hdXRobGV0ZS5jb20iLCJyZXNwb25zZV90eXBlIjoiY29kZSIsImNsaWVudF9pZCI6IjQwMjU2NTg4NTc0NTMwMjUiLCJyZWRpcmVjdF91cmkiOiJodHRwczovL21vYmlsZS5leGFtcGxlLmNvbS9jYiIsInNjb3BlIjoib3BlbmlkIiwic3RhdGUiOiJhZjBpZmpzbGRraiIsImNvZGVfY2hhbGxlbmdlIjoiQW9OM29NcVFvUVF1RGhoQmc2VTF2Y0ljbXRtQ19QWm9UMGNoVEVSVktFZyIsImNvZGVfY2hhbGxlbmdlX21ldGhvZCI6IlMyNTYiLCJhdXRob3JpemF0aW9uX2RldGFpbHMiOlt7InR5cGUiOiJjdXN0b21lcl9pbmZvcm1hdGlvbiIsImxvY2F0aW9ucyI6WyJodHRwczovL2V4YW1wbGUuY29tL2N1c3RvbWVycyJdLCJhY3Rpb25zIjpbInJlYWQiLCJ3cml0ZSJdLCJkYXRhdHlwZXMiOlsiY29udGFjdHMiLCJwaG90b3MiXX1dLCJpYXQiOjE2Mjg1MzE0NDQsIm5iZiI6MTYyODUzMTQ0NCwiZXhwIjoxNjI4NTMyMDQ5LCJqdGkiOiI4WjJEbGpCaUZVckpnTUt3bThiQ3EifQ.PIxVI2GFWi7B_frRfLg9r8AWEz7HGeopMeQo7MLYVEMGpOdoPkt5piBrLXI7PPI7ohrUmhxd-B4kZfm4WfkKH5qSub4A_mdd6pBpTWacBgfVQDIOvzE1yPrawCDEWQn2xgdYd1G-KM6pk8rDngOMEfaBbnoJ5C9krQtgYMHGbDIScgm8Y5AHf5aEF41FboZI67BlvbzXdxcJEPvB2zLGwV9twMrJ07OeRX0NVpIamhhEgfMQ87FyOsPVx9bqYUPN_VjwgB8lkKgrCdIkc9jPs2mQkpUbx0AIg8Pfmwyw0F5Vih7tgBbpb1LlwNgW36La3DPtTY9xSZ7SQGcyGxteIA
" }'
  • レスポンス
{
    "type": "authorizationResponse",
    "resultCode": "A004001",
    "resultMessage": "[A004001] Authlete has successfully issued a ticket to the service (API Key = 979748525706) for the authorization request from the client (ID = 4025658857453025). [response_type=code, openid=true]",
    "action": "INTERACTION
",
    "authorizationDetails": {
        "elements": [
            {
                "actions": ["read", "write"],
                "dataTypes": ["contacts", "photos"],
                "locations": ["https://example.com/customers"],
                "type": "customer_information"
            }
        ]
    },
    "requestObjectPayload": "{\"iss\":\"4025658857453025\",\"aud\":\"https://authlete.com\",\"response_type\":\"code\",\"client_id\":\"4025658857453025\",\"redirect_uri\":\"https://mobile.example.com/cb\",\"scope\":\"openid\",\"state\":\"af0ifjsldkj\",\"code_challenge\":\"AoN3oMqQoQQuDhhBg6U1vcIcmtmC_PZoT0chTERVKEg\",\"code_challenge_method\":\"S256\",\"authorization_details\":[{\"type\":\"customer_information\",\"locations\":[\"https://example.com/customers\"],\"actions\":[\"read\",\"write\"],\"datatypes\":[\"contacts\",\"photos\"]}],\"iat\":1628531444,\"nbf\":1628531444,\"exp\":1628532049,\"jti\":\"8Z2DljBiFUrJgMKwm8bCq\"}",
    "ticket": "xTZCagNjVJUltVS-WD7CKZr1fp0zeFTAAva86Rmzuow"
}

トークンリクエスト

ユーザーが承認した後に、認可サーバーは認可コードを生成し、それをクライアントへ返却します。クライアントは認可サーバーのトークンエンドポイントにアクセスします(必要であればそこでクライアント認証を受けます)。認可サーバーから呼び出された Authlete の /auth/token API は、アクセストークンと、もし openid スコープが指定されていれば ID トークンを生成します。

なお、ID トークンは認可詳細を含みません。

  • リクエスト
curl --request POST 'https://api.authlete.com/api/auth/token' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic OXXXXXXXXXXXXXXXX8=' \
--data '{ "parameters": "grant_type=authorization_code&code=wkPCpsu-HsMDeaNFBI78LydnmW7IyadBLloa3Mn7ZzM&redirect_uri=https%3A%2F%2Fmobile.example.com%2Fcb&code_verifier=ZhoMDipQfa7iMabyG-wSQ83ATy1GCVvE8Lh3SlDZdNo", "clientId" : "4025658857453025" }'
  • レスポンス
{
    "type": "authorizationResponse",
    "resultCode": "A004001",
    "resultMessage": "[A004001] Authlete has successfully issued a ticket to the service (API Key = 979748525706) for the authorization request from the client (ID = 4025658857453025). [response_type=code, openid=true]",
    "action": "INTERACTION",
    "authorizationDetails": {
        "elements": [
            {
                "actions": ["read", "write"],
                "dataTypes": ["contacts", "photos"],
                "locations": ["https://example.com/customers"],
                "type": "customer_information"
            }
        ]
    },
    "requestObjectPayload": "{\"iss\":\"4025658857453025\",\"aud\":\"https://authlete.com\",\"response_type\":\"code\",\"client_id\":\"4025658857453025\",\"redirect_uri\":\"https://mobile.example.com/cb\",\"scope\":\"openid\",\"state\":\"af0ifjsldkj\",\"code_challenge\":\"AoN3oMqQoQQuDhhBg6U1vcIcmtmC_PZoT0chTERVKEg\",\"code_challenge_method\":\"S256\",\"authorization_details\":[{\"type\":\"customer_information\",\"locations\":[\"https://example.com/customers\"],\"actions\":[\"read\",\"write\"],\"datatypes\":[\"contacts\",\"photos\"]}],\"iat\":1628531444,\"nbf\":1628531444,\"exp\":1628532049,\"jti\":\"8Z2DljBiFUrJgMKwm8bCq\"}",
    "ticket": "xTZCagNjVJUltVS-WD7CKZr1fp0zeFTAAva86Rmzuow"
}

トークンイントロスペクション

アクセストークンを含む API リクエストを受信したリソースサーバーは、そのアクセストークンにどのような認可が与えられているかを確認するために、イントロスペクションエンドポイントを用います。そして、その API リクエストを処理するか、拒否するかを決定します。

  • リクエスト
curl --request POST 'https://api.authlete.com/api/auth/introspection/standard' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic OXXXXXXXXXXXXXXXXXX8=' \
--data '{ "parameters": "token=Q4nS7518ZP4W0JaabMGJRBYGj919SY1IyxuDHZUw_Qc"}'
  • レスポンス
{
    "type": "standardIntrospectionResponse",
    "resultCode": "A145001",
    "resultMessage": "[A145001] Introspection was performed successfully (type=access_token, active=true).",
    "action": "OK",
    "responseContent": "{
        \"sub\":\"Ciara_Sporer@gmail.com\",
        \"authorization_details\":[ {
            \"type\":\"customer_information\",
            \"locations\":[\"https://example.com/customers\"],
            \"actions\":[\"read\",\"write\"],
            \"datatypes\":[\"contacts\",\"photos\"]
        }],
        \"scope\":\"openid\",
        \"iss\":\"https://authlete.com\",
        \"active\":true,
        \"token_type\":\"Bearer\",
        \"exp\":1628618721,
        \"client_id\":\"4025658857453025\"
    }"
}