RFC 9470 OAuth 2.0 ステップアップ認証チャレンジプロトコル

概要

RFC 9470: OAuth 2.0 Step Up Authentication Challenge Protocol は、「リソースサーバーがクライアントに対して、現在のリクエストのアクセストークンに関連付けられた認証イベントが認証要件を満たしていないことを通知し、それを満たす方法を指定するメカニズムを導入する」 ものです(“Abstract” からの抜粋)。

最初のステップとして、クライアントアプリケーション (リライングパーティ)が アクセストークン を用いて 保護リソース のエンドポイントにアクセスします。

エンドポイントの実装は、リクエストからアクセストークンを抽出します。

次に、エンドポイントの実装は、アクセストークンに含まれる ユーザー認証 の情報(アクセストークンの発行時に 認可サーバー が実施した認証)を抽出します。

その後、エンドポイントの実装は、取得したユーザー認証情報がエンドポイントが要求する 認証要件 を満たしているかを確認します。

要件が満たされていない場合、エンドポイントの実装は、クライアントアプリケーションに対して認証要件の情報を含むエラーレスポンスを返します。

以下の図は、アクセストークンを用いたリクエストから、認証要件を伴うエラーレスポンスの返却までのフローを示しています。

クライアントアプリケーションが認証要件を含むエラーレスポンスを受け取った後、再度 認可リクエスト を送信します。この際、クライアントアプリケーションは、認可サーバーに対して認証要件を満たすアクセストークンの発行を依頼する必要があります。

認証要件

OAuth 2.0 ステップアップ認証チャレンジプロトコルの仕様では、2種類の認証要件を前提としています。

  1. 認証コンテキストクラスリファレンス(ACR):ユーザー認証のレベルを示す識別子。
  2. 最大認証時間 (Max Age):ユーザー認証が行われた時刻と現在時刻の経過時間の最大許容値。

認証コンテキストクラスリファレンス(ACR)

ユーザー認証は、認可フローのどこかで実行されます。OAuth 2.0 および OpenID Connect は、ユーザー認証の詳細を定義していませんが、クライアントアプリケーションは、認可リクエスト内で ACR のリストを指定することで、ユーザー認証の基準を指定できます。

認可サーバーは、指定された ACR のいずれかを満たすユーザー認証を実施しようとします。しかし、acr クレームが「必須(essential)」として要求されていない限り、指定された ACR を満たす認証が行われなくてもエラーは発生しません。

acr_values リクエストパラメータ

ACR のリストを指定する方法の一つは、acr_values リクエストパラメータを使用することです。このパラメータは、OpenID Connect Core 1.0セクション 3.1.2.1 で次のように定義されています。

  • acr_values:任意(OPTIONAL)。要求された認証コンテキストクラスリファレンス値。スペース区切りの文字列で、認可サーバーに対して認証リクエストの処理時に使用する acr 値を指定します。リストは優先順位の順序で並べられます。実施された認証のコンテキストクラスは acr クレームとして返されます。

このパラメータの値は、スペース区切りの ACR のリストとして、以下のように認可リクエストに含めることができます。

https://as.example.com/authorize?acr_values=acr1+acr2+acr3&...

claims リクエストパラメータ

ACR のリストを指定する 2 つ目の方法は、claims リクエストパラメータを使用することです。このパラメータは、OpenID Connect Core 1.0セクション 5.5 で定義されており、JSON オブジェクトを値として受け取ります。この JSON の構文はやや複雑です。

以下の JSON は、OpenID Connect Core 1.0 から抜粋した claims リクエストパラメータの値の例です。

{
 "userinfo": {
   "given_name": {"essential": true},
   "nickname": null,
   "email": {"essential": true},
   "email_verified": {"essential": true},
   "picture": null,
   "http://example.info/claims/groups": null
 },
 "id_token": {
   "auth_time": {"essential": true},
   "acr": {"values": ["urn:mace:incommon:iap:silver"]}
 }
}

この例では、ACR のリストに "urn:mace:incommon:iap:silver" という 1 つの要素が指定されています。この JSON を claims リクエストパラメータの値として認可リクエストに含めると、認可サーバーは "urn:mace:incommon:iap:silver" の基準を満たすユーザー認証を実施しようとします。

指定された ACR が満たされない場合にエラーを発生させるには、クライアントアプリケーションが acr クレームを 必須(essential) として要求する必要があります。次の JSON の例では、auth_time クレームと acr クレームが必須として要求されています。

{
  "id_token": {
    "auth_time": {
      "essential": true
    },
    "acr": {
      "values": ["urn:mace:incommon:iap:silver"],
      "essential": true
    }
  }
}

default_acr_values クライアントメタデータ

3 つ目の方法は、default_acr_values クライアントメタデータを使用することです。このメタデータは、OpenID Connect Dynamic Client Registration 1.0セクション 2 で次のように定義されています。

  • default_acr_values:任意(OPTIONAL)。デフォルトの認証コンテキストクラスリファレンス値。配列として指定され、OP(OpenID Provider)は、クライアントからのリクエスト処理時に、優先順位の順序でこれらの値を使用することが求められます。認証時に満たされたコンテキストクラスは、発行された ID トークンの acr クレーム値として返されます。

認可リクエストで明示的に ACR のリストが指定されない場合、default_acr_values に設定された値が ACR のリストとして使用されます。

認証要件が満たされない場合

認可リクエストで acr クレームが必須として要求されている場合、指定された ACR が満たされないと認可サーバーはエラーレスポンスを返します。認可サーバーが “OpenID Connect Core Error Code unmet_authentication_requirements” をサポートしている場合、エラーレスポンスの error パラメータの値として unmet_authentication_requirements を使用します。

仕様ではこのエラーコードについて、次のように説明されています。

  • unmet_authentication_requirements:認可サーバーが、リライングパーティ(RP)の要求するユーザー認証要件を満たすことができません。このエラーコードは、OpenID Connect Core 1.0 の セクション 5.5.1.1 で指定されるように、acr クレームが必須として要求され、OP がその要件を満たせない場合に使用されるべきです。

認可サーバーがこの仕様をサポートしていない場合は、どのエラーコードを使用するかは認可サーバーの実装に依存します。

ステップアップ認証のための認証要件が満たされない場合

OpenID Connect Core 1.0 では、acr クレームが必須として要求されない限り、指定された ACR を満たせなくてもエラーにはなりません。

しかし、OAuth 2.0 Step Up Authentication Challenge Protocol では、acr_values リクエストパラメータによる ACR の要求(通常 acr クレームは任意扱い)は 「必須として扱われるべき」 であり、指定された ACR が満たされない場合は unmet_authentication_requirements エラーを返すことを推奨しています。

この推奨は、OpenID Connect Core 1.0 からの以下の抜粋とわずかに矛盾します。

ユーザーがクレームの公開を許可しなかった場合や、クレームが存在しない場合であっても、特定のクレームの説明で特に指定されていない限り、認可サーバーはクレームが返されないことによってエラーを生成 してはならない

しかし、実際の運用上は、このわずかな矛盾が大きな問題になることはないでしょう。

実際の ACR 値

OpenID Connect Core 1.0 では、具体的な ACR 値の定義はされていません。そのため、ACR を利用するためには、実装ごとに ACR 値を定義する必要があります。以下の表は、実際の ACR 値の例を示しています。

デプロイメント ACR 値
UK Open Banking urn:openbanking:psd2:ca
urn:openbanking:psd2:sca
AU CDR urn:cds:au:cdr:2
urn:cds:au:cdr:3
Open Banking Brasil urn:brasil:openbanking:loa2
urn:brasil:openbanking:loa3

認可サーバーは、サポートしている ACR 値について、そのディスカバリードキュメントを通じて広告すべきです。OpenID Connect Discovery 1.0 では、acr_values_supported サーバーメタデータが次のように定義されています。

  • acr_values_supported:任意(OPTIONAL)。この OP がサポートする認証コンテキストクラスリファレンスのリストを含む JSON 配列。

認証要件チャレンジ

OAuth 2.0 ステップアップ認証チャレンジプロトコルを利用する保護リソースのエンドポイントは、
提示されたアクセストークンが認証要件(ACR または Max Age のいずれか、または両方)を満たさない場合、エラーレスポンスを返します。

以下の図は、認証要件を満たさない場合のエラーレスポンスから、
認証要件を満たすアクセストークンを取得するための認可リクエストを送信するまでの流れを示しています。

エラーレスポンス内のパラメータについて、以下のセクションで説明します。

insufficient_user_authentication エラーコード

認証要件が満たされていないことを示すために、
本仕様では insufficient_user_authentication というエラーコードを次のように定義しています。

  • insufficient_user_authentication
    提示されたアクセストークンに関連付けられた認証イベントが、保護リソースの認証要件を満たしていない。

このエラーコードは、エラーレスポンスの WWW-Authenticate HTTP ヘッダー内に次のように含められます。

WWW-Authenticate: Bearer error="insufficient_user_authentication",...

acr_values パラメータ

アクセストークンが満たすべき ACR のリストを指定するために、
本仕様では acr_values パラメータを次のように定義しています。

  • acr_values
    スペース区切りの文字列で、保護リソースが要求する認証コンテキストクラスリファレンス値のリストを表します。
    リスト内のいずれか 1 つを満たす必要があります。リストの順序は優先順位を示します。

このパラメータは、エラーコード insufficient_user_authentication とともに次のように使用されます(仕様の抜粋)。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="insufficient_user_authentication",
  error_description="A different authentication level is required",
  acr_values="myACR"

クライアントアプリケーションが上記のエラーレスポンスを受信した場合、
認可リクエストの claims パラメータ内で acr クレームを 必須(essential) として指定し、
myACRvalues に含める必要があります。

以下に、その認可リクエストの例を示します。
(改行は見やすくするためのもので、実際のリクエストには含まれません。)

https://as.example.com/authorize?claims={  
    "id_token": {  
        "acr": {  
            "essential": true,  
            "values": [ "myACR" ]  
        }  
    }  
}&...

max_age パラメータ

アクセストークンが満たすべき最大認証時間 を指定するために、
本仕様では max_age パラメータを次のように定義しています。

  • max_age
    提示されたアクセストークンの認証イベントが、
    いつ行われたかを基準に、許容される経過時間(秒単位)を示します。

このパラメータは、エラーコード insufficient_user_authentication とともに次のように使用されます(仕様の抜粋)。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="insufficient_user_authentication",
  error_description="More recent authentication is required",
  max_age="5"

クライアントアプリケーションが上記のエラーレスポンスを受信した場合、
prompt リクエストパラメータに login を指定した認可リクエストを送信する必要があります。

アクセストークンに紐づく認証情報

保護リソースのエンドポイントが、提示されたアクセストークンが認証要件を満たしているかどうかを確認できるようにするためには、
アクセストークンに、発行時に行われたユーザー認証の情報を含める必要があります。

OAuth 2.0 ステップアップ認証チャレンジプロトコルの仕様では、
以下の 2 つの属性をアクセストークンに持たせることを定義しています。
これらの属性は、JWT アクセストークンのペイロード部分や、
RFC 7662 に基づくトークンイントロスペクションのレスポンスに含めることができます。

属性 説明(仕様より抜粋)
acr 認証コンテキストクラスリファレンス。ユーザー認証の際に適用された認証コンテキストクラスを識別するための文字列。
auth_time ユーザー認証が行われた時刻。JSON の数値で表され、1970-01-01T00:00:00Z UTC からの秒数で指定される。

以下は、仕様から抜粋した JWT アクセストークンのペイロード部分の例です。

{
    "active": true,
    "client_id": "s6BhdRkqt3",
    "scope": "purchase",
    "sub": "someone@example.net",
    "aud": "https://rs.example.com",
    "iss": "https://as.example.net",
    "exp": 1639528912,
    "iat": 1618354090,
    "auth_time": 1646340198,
    "acr": "myACR"
}

Authlete の実装

Authlete は、バージョン 2.3 以降で OAuth 2.0 ステップアップ認証チャレンジプロトコルの仕様をサポートしています。

認証情報のバインディング

Authlete の /auth/authorization/issue API では、
acr リクエストパラメータと authTime リクエストパラメータを受け取ることができます。
これらの値は、発行される ID トークンacr クレームおよび auth_time クレームの値として使用されます。
また、Authlete 2.3 以降では、アクセストークンにもこれらの情報がバインドされます。

以下のシェルコマンドのセットは、OAuth 2.0 認可コードフロー (RFC 6749 セクション 4.1) をシミュレートし、
ACR (acr) および認証時刻 (auth_time) を含むアクセストークンを発行するものです。

(1) /auth/authorization API の呼び出し

認可リクエストを parameters に含めて /auth/authorization API を呼び出し、
そのリクエストの情報を JSON 形式で取得します。

curl -v -X POST https://{api-cluster}.authlete.com/api/{serviceId}/auth/authorization \
-H 'Content-Type: application/json' \
-u 'Authorization: Bearer <Service Access Token>' \
--data-urlencode parameters="response_type=code&scope=openid&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&state=${STATE}&claims={\"id_token\":{\"acr\":{\"essential\":true,\"values\":[\"${ACR}\"]}}}" \
> authorization_response.json

(2) /auth/authorization/issue API の呼び出し

取得した ticket を用いて /auth/authorization/issue API を呼び出し、
認可コード、アクセストークン、ID トークンを発行します。

$ TICKET=`jq -r .ticket authorization_response.json`
$ AUTH_TIME=`date +%s`
$ curl -v -X POST https://{api-cluster}.authlete.com/api/{serviceId}/auth/authorization/issue \
-H 'Content-Type: application/json' \
-u 'Authorization: Bearer <Service Access Token>' \
-d ticket=$TICKET -d subject=$SUBJECT -d authTime=$AUTH_TIME -d acr=$ACR \
> authorization_issue_response.json

(3) /auth/token API の呼び出し

取得した認可コードを使用して /auth/token API を呼び出し、
アクセストークン(および必要に応じて ID トークン)を取得します。

$ AUTHORIZATION_CODE=`jq -r .authorizationCode authorization_issue_response.json`
$ curl -v -X POST https://{api-cluster}.authlete.com/api/{serviceId}/auth/token \
-H 'Content-Type: application/json' \
-u 'Authorization: Bearer <Service Access Token>' \
--data-urlencode parameters="client_id=${CLIENT_ID}&grant_type=authorization_code&code=${AUTHORIZATION_CODE}&redirect_uri=${REDIRECT_URI}" \
-d clientId=${CLIENT_ID} -d clientSecret=${CLIENT_SECRET} \
> token_response.json

認証要件が満たされない場合の処理

認可エンドポイントの実装では、/auth/authorization/issue API を呼び出す代わりに、
/auth/authorization/fail API を呼び出して認可リクエストの失敗を通知することもできます。

/auth/authorization/fail API の呼び出し

この API では、reason リクエストパラメータを指定して、
エラーの理由を通知する必要があります(AuthorizationFailRequest 参照)。

認証要件が満たされない場合、
reason パラメータの値として <a href="https://authlete.github.io/authlete-java-common/com/authlete/common/dto/AuthorizationFailRequest.Reason.html#ACR_NOT_SATISFIED">ACR_NOT_SATISFIED</a> を指定すると、
Authlete 2.3 以降では、error 値が unmet_authentication_requirements となるエラーレスポンスを生成します。

curl -v -X POST https://{api-cluster}.authlete.com/api/{serviceId}/auth/authorization/fail \
-H 'Content-Type: application/json' \
-u 'Authorization: Bearer <Service Access Token>' \
-d ticket=$TICKET -d reason=ACR_NOT_SATISFIED \
> authorization_fail_response.json

この API のレスポンスには responseContent パラメータが含まれ、
この値は、エラーコードや追加情報を含むリダイレクト URL になります(AuthorizationFailResponse 参照)。

$ jq -r .responseContent authorization_fail_response.json
http://localhost:4000/api/mock/redirection/4803170471?error=unmet_authentication_requirements&error_description=%5BA060305%5D+The+authorization+request+requests+%27acr%27+as+essential%2C+but+the+authentication+performed+for+the+end-user+satisfies+none+of+the+requested+ACRs.&error_uri=https%3A%2F%2Fdocs.authlete.com%2F%23A060305&state=917bdc1ef38ba6f6c297b4e31ac84007&iss=https%3A%2F%2Fauthlete.com

URL に error_description パラメータが含まれていますが、
サービスの errorDescriptionOmitted 設定が true の場合は省略されます
Service.isErrorDescriptionOmitted() 参照)。

JWT アクセストークンのサポート

Authlete では、アクセストークンの形式を設定可能です。
ServiceaccessTokenSignAlg プロパティが設定されている場合、
アクセストークンの形式は JWT となります
(詳細は JWT ベースのアクセストークンの有効化 を参照)。

Authlete 2.3 以降では、OAuth 2.0 ステップアップ認証チャレンジプロトコルの仕様に基づき、
JWT アクセストークンのペイロードに acr クレームと auth_time クレームを埋め込みます。

JWT アクセストークンの取得

サービスが JWT アクセストークンを生成する設定になっている場合、
/auth/token API のレスポンスの jwtAccessToken パラメータに JWT アクセストークンが含まれます
TokenResponse 参照)。

$ JWT_AT=`jq -r .jwtAccessToken token_response.json`

JWT アクセストークンのデコード

JWT アクセストークンのペイロード部分をデコードすると、
acr クレームおよび auth_time クレームが埋め込まれていることが確認できます。

$ echo $JWT_AT | ruby -paF\\. -rbase64 -e '$_=Base64.urlsafe_decode64 $F[1]' | jq .

出力例:

{
  "sub": "taka",
  "acr": "acr1",
  "scope": "openid",
  "auth_time": 1667321595,
  "iss": "https://authlete.com",
  "exp": 1667408271,
  "iat": 1667321871,
  "client_id": "5575687621",
  "jti": "A1pURs-TMuyvueCoP2mSsntQX4-0IvYxlvectI4E2h8"
}

トークンイントロスペクションのサポート

Authlete を利用する認可サーバーは、
RFC 7662 に準拠したトークンイントロスペクションエンドポイントを
/auth/introspection/standard API を用いて実装できます。
保護リソースのエンドポイントは、このトークンイントロスペクションエンドポイントを使用して、
アクセストークンの詳細情報を取得できます。

/auth/introspection/standard API の呼び出し

この API は、parameters にトークン情報を含めてリクエストを送信し、
イントロスペクション結果を JSON 形式で返します
StandardIntrospectionResponse 参照)。

$ AT=`jq -r .accessToken token_response.json`
$ curl -v -X POST https://{api-cluster}.authlete.com/api/{serviceId}/auth/introspection/standard \
-H "Content-Type:application/json" \
-u 'Authorization: Bearer <Service Access Token>' \
--data-urlencode parameters="token=${AT}" \
> introspection_standard_response.json

レスポンスには responseContent パラメータが含まれます。
この値は、RFC 7662 に準拠した JSON 文字列です。

以下のコマンドを実行すると、レスポンスの responseContent の値を解析できます。

$ jq -r .responseContent introspection_standard_response.json | jq .

出力例:

{
  "sub": "taka",
  "acr": "acr1",
  "scope": "openid",
  "auth_time": 1667321595,
  "iss": "https://authlete.com",
  "active": true,
  "token_type": "Bearer",
  "exp": 1667408271,
  "client_id": "5575687621"
}

Authlete の独自トークンイントロスペクション API

Authlete は、RFC 7662 に準拠した標準のイントロスペクションエンドポイントとは別に、
独自の /auth/introspection API も提供しています。
この API を使用すると、より高度なトークン検証を行うことができます。

/auth/introspection API の利点

/auth/introspection API は、標準のイントロスペクションエンドポイントと比較して、以下のような利点があります。

  1. 標準のイントロスペクションエンドポイントではできない追加の検証が可能
  2. RFC 6750 に準拠したエラーメッセージを生成可能

/auth/introspection API のリクエストパラメータ

この API は、以下のリクエストパラメータを受け付けます
IntrospectionRequest 参照)。

パラメータ カテゴリ バージョン 説明
token 共通 1.1 イントロスペクション対象のアクセストークン
scopes 共通 1.1 アクセストークンがカバーすべきスコープ
subject 共通 1.1 アクセストークンに関連付けられるべきユーザー識別子
acrValues ステップアップ認証 2.3 アクセストークンが満たすべき ACR 値のリスト
maxAge ステップアップ認証 2.3 許容される最大認証時間 (秒単位)

ACR 検証

acrValues リクエストパラメータを指定すると、
/auth/introspection API は、アクセストークンにバインドされた ACR 値が acrValues 配列内に含まれているかどうかを検証します。
指定された ACR を満たさない場合、本仕様に準拠したエラーレスポンスを生成します。

ACR 検証の実行

以下のコマンドを実行すると、acrValues を指定した ACR 検証を行うことができます。

$ curl -v -X POST https://{api-cluster}.authlete.com/api/{serviceId}/auth/introspection \
-H 'Content-Type:application/json' \
-u 'Authorization: Bearer <Service Access Token>' \
-d token=$AT -d acrValues="acrX acrY" \
> introspection_response-acrValues.json

レスポンスの responseContent パラメータには、
WWW-Authenticate HTTP ヘッダーの値として使用すべき文字列が含まれます
IntrospectionResponse 参照)。

$ jq -r .responseContent introspection_response-acrValues.json

出力例:

Bearer error="insufficient_user_authentication",
error_description="[A341302] The authentication context class 'acr1' of the user authentication that the authorization server performed during the course of issuing the access token is insufficient. User authentication of an access token must satisfy one of [acrX acrY] to access the protected resource.",
error_uri="https://docs.authlete.com/#A341302",
acr_values="acrX acrY"

最大認証時間 (Max Age)の検証

maxAge リクエストパラメータを指定すると、
/auth/introspection API は、アクセストークンの auth_time からの経過時間が maxAge を超えていないかどうかを検証します。
指定された maxAge を超えている場合、本仕様に準拠したエラーレスポンスを生成します。

Max Age 検証の実行

以下のコマンドを実行すると、最大認証時間 を指定した認証検証を行うことができます。

$ curl -v -X POST https://{api-cluster}.authlete.com/api/{serviceId}/auth/introspection \
-H 'Content-Type:application/json' \
-u 'Authorization: Bearer <Service Access Token>' \
-d token=$AT -d maxAge=600 \
> introspection_response-maxAge.json

レスポンスの responseContent パラメータには、
WWW-Authenticate HTTP ヘッダーの値として使用すべき文字列が含まれます。

$ jq -r .responseContent introspection_response-maxAge.json

出力例:

Bearer error="insufficient_user_authentication",
error_description="[A340301] The time of the user authentication that the authorization server performed during the course of issuing the access token is too old, so re-authentication is needed. The maximum authentication age (max_age) required by the protected resource is 600.",
error_uri="https://docs.authlete.com/#A340301",
max_age="600"

まとめ

Authlete 3.0 は 2024 年 11 月にリリースされました
リリース発表 参照)。
このバージョンでは、OAuth 2.0 ステップアップ認証チャレンジプロトコルが正式にサポートされています。
ぜひ、最大限に活用してください!