🗝️

いまさら OAuth 2.0 から 2.1 での変更点

2022/10/05に公開

タイトルの通りです。また雑記です。

2012年から発行されているというステートレスな認証機構のプロトコルである「OAuth2.0」ですが、バージョン2.1が昨年2021年8月にドラフト版として公開されています。
OAuth2.1は、ドラフト版とはいえ、疎結合関係を前提とするネットワークでの認証設計においては大きなポイントと感じていて、思考整理の意味でも投稿しておきます。

https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01

変更点

https://oauth.net/2.1/

Index

  1. Authorization Code Grant では PKCE が必要
  2. redirect_uri は、正確な文字列一致を使用が必要
  3. Implicit Grant ( response_type=token ) は仕様省略(非推奨)
  4. Resource Owner Password Credentials Grant は仕様省略(非推奨)
  5. URIのクエリ文字列での Bearer Token の使用省略(非推奨)
  6. client_type=public の場合の refresh_token は送信者によって制限されているか、1回限りの使用である必要あり
  7. client_type は、クライアントが資格情報を持っているかどうかのみを参照するように簡略化

概要

1. Authorization Code Grant では PKCE が必要

Authorization Code Grant そのものは、認証および認可サーバ(IDプロバイダ = IdP)とサービスサーバ(サービスプロバイダ = SP)の関係性がある中で、相互のリダイレクトを伴いながら、認証コードの発行と発行コードの認証、アクセストークンの発行という流れをもって、IdP/SP間での認可およびユーザー認証を行うというものです。

まぁ工程が多いし、認証・認可、サービス、ユーザー本人情報との整合性確認に使われる情報も多いわけなので、他のグラントと比べて安全性は高い方式ですよね。

Authorization Code Grant は、クライアント情報である client_id をリクエストパラメータに含める他に、IdPからSPに認証後リダイレクトする先となる認証コード受取URL( redirect_uri )を加えることで、クライアント情報を使った不正チェックができ、トークンを発行して良いSPとしてIdPも認識できる状況となり、認証コードやアクセストークンが払い出せる仕組みです。
当然、工程中にズレや抜けが発生すれば、認証に失敗するわけなので堅牢性が高いよね、と言えます。

PKCE (Proof Key for Code Exchange)

Authorization Code Grant の弱点は、認証コードの横取りです。

IdPでのログイン認証後に、SPに戻り、認証コードを受け取る工程があるわけですが、クライアントにプロセスが戻ってくるわけですから、SP側で埋め込まれた、何らかのスクリプトが実行できる余地が与えられてしまいます。
例えば、XSSで、認証コード受け取りとあわせて盗聴できる可能性が生まれてしまいますので、横取りされ、アクセストークンを発行されてしまい、個人情報のリスクへと発展することが想定できてしまいます。

これは OAuth がもつ合理性や簡便性を実現するアーキテクチャ(SPAなど)では、秘匿したい情報を隠蔽することが難しいという事情が脆弱性につながる要因ではあります。が、サーバサイドプログラミングを持たない環境要件の場合であれば何とかしたいものです。

なので、脆弱性への対策として、特定方式で暗号化された文字列とその方式をキーにして、トークンをリクエストする元に不正がないかのチェック工程を挟んで横取りを防ぐ仕組みが「PKCE」になります。

SP側であらかじめ定義した暗号化方式( code_challenge_method )と、暗号化されたハッシュ文字列( code_challenge )を認証コードのリクエスト時にIdPに送信し、IdPで情報を保管。SPで認証コードを受け取った後、SPからアクセストークンをリクエストするタイミングで、暗号化する前の文字列( code_verifier )とともに認証コードを送り、IdPで再度暗号化した文字列と保管しておいた情報を比較してリクエスト元情報に不正がないかをチェックするという仕組みです。
認証コードだけ横取りされても、トークンを取り出すためのキー情報を持たない(認識もできない)リクエストのため、トークン発行に失敗する = 盗聴を防御できる、ということになります。

https://dev.classmethod.jp/articles/oauth-2-0-pkce-by-auth0/

この一手間で堅牢性を増すわけなのですが、2.0に明記がなく、脆弱性発見から拡張された仕様という経緯から、2.0では「任意」の位置付けの仕組みだったので OAuth2.1 では公式的にも「必須」となりました。

2. redirect_uri は、正確な文字列一致を使用が必要

Authorization Code Grant では、アクセストークンを取得するための前段階として、認証コードの取得を行う必要があるのですが、認証コードの受け取り方として、IdPでログインを行なった後にSPへリダイレクトする際のGETパラメータに認証コードが付与され、SP側で取り出せるという手続きの流れになります。

認証コードをリクエストする時のリクエストパラメータのひとつであるリダイレクト先指定が redirect_uri なわけなんですけど、SPやIdP、IDaaSのサービス仕様によっては指定をせずとも、内部処理的にリダイレクトさせることはできたため「任意」という位置付けだったのですが、OAuth2.1ではプロトコル上「必須」となりました。

3. Implicit Grant (response_type=token) は仕様省略(非推奨)

Implicit Grant は Authorization Code Grant の簡略版と言って良いかなと思うのですが、IdPへのリダイレクトを行い、ログイン認証を経て認証コードを取得する、という手続きのなかでの「認証コードの発行と取得、IdPでの照合」の工程がなくなった方式になります。

認証コードの工程が省かれるので、IdPでログインすれば、ダイレクトにアクセストークンを生成してSPに帰ってくる仕様になります。なので、認証コードを受け取る先の指定がないので、IdPから見た時に、リクエスト元のSPに不正がないかどうかの整合性確認が取れない方式になります。
(認証画面を必要とする Resource Owner Password Credentials Grant みたいなもの)

なので、この認証仕様を利用するサービス全てが自社のサービスと特定できる場合などは有効である方式と考えますが、当然堅牢性は低い方式ではあるので、OAuth2.1では仕様が省略され明記が外れており、非推奨となります。

4. Resource Owner Password Credentials Grant は仕様省略(非推奨)

で、前談の Implicit Grant と同様に、認証コードを伴わない Resource Owner Password Credentials Grant は、APIへのリクエストボディに、JSONオブジェクト形式でクライアント情報とユーザー情報を含めリクエストして、ダイレクトにアクセストークンを発行できる手続き方式です。
「Rest APIで全て完結する認証・認可」ですが、OAuth2.1では、これも非推奨です。

これは Laravel フレームワークにおける OAuth パッケージ「Laravel Passport」の主流な方式であった感覚でしたが、Laravelの公式ドキュメントでも注釈がなされているとおり、使わない方が良いとなっています。

Warning!! パスワードグラントトークンの使用は、現在推奨していません。代わりに、OAuth2サーバが現在推奨しているグラントタイプ を選択する必要があります。

https://readouble.com/laravel/9.x/ja/passport.html

https://oauth2.thephpleague.com/authorization-server/which-grant/

5. URIのクエリ文字列での Bearer Token の使用省略(非推奨)

前談の redirect_uri のアップデートと背景は近い話です。

取得したアクセストークンは、認証済みルーティングとして定義されたAPIエンドポイントに対するリクエストのヘッダーに Authorization Bearer のキーでトークン付与してリクエストするという仕様がスタンダードであると考えますが、2.0では仕様に明記はされておらず、リクエストボディでも、GETパラメータでも、付与する場所の指定はありませんでした。

まぁトークンが露呈するのはマズイよね、ということで、OAuth2.1ではクエリ文字列での指定は非推奨となります。

6. client_type=public の場合の refresh_token は送信者によって制限されているか、1回限りの使用である必要あり

client_type=public というパラメータは、後述もしますが、クライアントを特定する情報が必要かどうかを示すもので public の値を指定している場合 client_secret を指定しない認証方式を指すことになります。

client_secret といえば、Client Credential Grant。
この認証方式はクライアントを特定してアクセストークンを引き渡す仕組みであり、その特定する情報として client_idclient_secret をキーとしてトークン発行時のリクエストに含めます。これらは、サービス内で秘匿できる場所に保管しておき、トークンリクエスト時に利用するようにします。

秘匿方法は十分注意してね、という話は置いておけば"すでにわかっている"相手同士とのトークンのやりとりなので refresh_token に対する制約はないのですが、"わからない相手とのトークンのやりとり"でも refresh_token に対する制約がなく、ましてトークンそのものに再利用性があるようであれば、危険であり改善が必要です。

OAuth2.1にならうならば、送信者によって送信そのものが制限される(返さない、など)か、1回限りの使用とするため「revoke」フラグで管理しておく、などの対処が必要になるルールになります。

7. client_type は、クライアントが資格情報を持っているかどうかのみを参照するように簡略化

2.0では、アクセストークンをリクエストする場合に、SPの client_type を指定するパラメータを持っており、クライアント情報を必要とする認証方式かどうかを示すことができます。

https://qiita.com/TakahikoKawasaki/items/63ed4a9d8d6e5109e401#12-クライアントタイプ

client_type が「Confidential」の場合には、クライアント情報が必要になるので、 client_idclient_secret 文字列をリクエストに含める必要があります。

対して、「Public」というタイプもあり、クライアント情報が不要な認証方式として、 client_id だけを送って、クライアント情報以外の情報を組み合わせて認証を行う方式になります。
逆に言えば client_secret がなくとも認証ができるタイプ指定になるので、Client Credential Grantは採用できないタイプという明記でもあります。

client_type のパラメータ値を使って、最適な認証エンドポイントを使わせたり、パラメータの必須/任意を管理するようなサービスもあると思いますけど、見出しのとおり「 client_idclient_secret のパラメータ有無だったり、grant_type の指定もできるので、いらなくね?」という話に至ったようで、OAuth2.1では、省略して問題ないパラメータとなりました。

Authorization Code Grant with PKCE がスタンダード

ということで、OAuth2.1では、見出しの通りの方針となるわけです。
アップデートの意図としては Authorization Code Grant with PKCE を激推しするための仕様変更であり、堅牢性を高めるための措置であると考えられます。

僕は PHPer ですけど、JSが普及しまくってフロントエンドプログラムを基盤とするアーキテクチャーも多くなってきた昨今なので、OAuthやOIDCの普及もすすむのは、何だか良い傾向だなと感じています。

まっさきに思うこととしては、はやくこの設計工程を抜け出したい。。。

Discussion