OAuth2.0のPKCEとは

codeの受け渡しをどう守るか?

以前Oauth2.0のstateについて書いていた際にも思いましたが、フローを見ているとアクセストークンの受け渡し、もっと言うとプラスしてcodeの受け渡し部分は非常にセンシティブな箇所です。アクセストークンがあればリソースを取得できますし、codeはアクセストークンの受け渡しの必要条件ですので。

要するに、この簡単なイメージ図で言う「握手券」をどう安全に管理することができるのか?という所なのですが、

f:id:ybnskk:20201102061234p:plain

例えの便宜上逆にややこしい部分もございますが、実際にはstateパラメータを使うことで、①~⑦までの一連の流れが同一人物によって為されているかを確認しているので、

例えば⑤の部分で握手券を奪っても、それを悪意のあるユーザが横入りして使うことはできなかったりします。ちゃんと正規の入り口から入った上で一連の処理が行われているか見ることで、codeの利用を守っているんですね。

しかし実はもう一つ考慮するポイントがあります。これまで出てこなかったClient側でのリスクです。

どういうことかというのを改めて握手会で例えてみると、

f:id:ybnskk:20210202061911p:plain

  1. 参加者は握手券の「予約券(=まだ握手できる権利はない)」を持って会場に行きます。

  2. 参加者が予約券しか持っていないので、警備員(Client)に受付に行くように言われます。

  3. 参加者は受付(Authorization server)に予約券と、本人確認書類を持って行き、「自分が推しのXさんの握手会の予約者本人である」ことを証明します。

  4. 受付は参加者に「Xさんとの握手券」を渡します。

    と、ここまでは問題ないのですが、

  5. 参加者は握手券を持って会場に戻るのですが、会場内に似たような警備員(Client)がいて、知らぬうちに参加者はその怪しい警備員に握手券を渡してしまいます。

  6. 怪しい警備員=悪意あるClientは参加者を放置して自分の物のようにスタッフ(Resource Server)に握手券を渡します。

  7. スタッフは握手券に確認済みの印をつけて警備員に渡します。

というように、ユーザではなくClientの側から握手券=codeを奪い取られる危険性が存在しています(2号館のももクロイベントに行こうと思ったのに間違えて3号館のAKB握手会に行っちゃって、3号館のスタッフに素知らぬ顔でももクロイベントのチケットを奪われてたみたいな感じだなと思いました)。

ですが、この点も基本的には対策がされています。

図は割愛しておりますが、⑥でResource Serverがcodeを受け取る際に、codeといっしょに「client_sercret 」=正しいClientしか使えない文字列を送らせて、その内容が正しいかをチェックすることで、本当に正式なClientか?をResource Server側で確認しています。

つまり怪しい警備員はこのclient_secretの正しい値を持っていないので、codeを手に入れたとしても基本的には利用することはできない仕組みになっています。

しかしWebアプリケーションの場合はこのようにclient_sercretで防ぐことができるのですが、例えばモバイルアプリケーションのように、カスタムURIスキーム(app://~のような)で動作するものの場合は、カスタムURIスキームとアプリがResource Server側で情報登録されていると、悪意あるモバイルアプリケーションがcodeを送信できる可能性が出てきてしまいます(2号館と3号館を同じ場所ととらえてしまったように…)。

PKCEの登場

そこで活躍するのが「PKCE」です。

PKCEは「Proof Key for Code Exchange by OAuth Public Clients」の略で、RFCにも定義があります。

流れとしては下記のような形で、

f:id:ybnskk:20210202061942p:plain

  1. 参加者は握手券の「予約券(=まだ握手できる権利はない)」を持って会場に行きます。

    と、ここまではまた一緒なのですが、次から少し変化します。

  2. 参加者が予約券しか持っていないので、警備員(Client)に受付に行くように言われます。 この際に、「code_verifier」から「code_challenge」を生成し、後者の「code_challenge」とチャレンジを生成する際の方法=「code_challenge_method」を一緒にもっていかせます。

    ※code_challenge_methodは任意のパラメータでデフォルトはplainですが、本来の目的に照らし合わせるとsha256を利用する方が好ましいと考えられます。

  3. 参加者は受付(Authorization server)に予約券と、本人確認書類を持って行き、「自分が推しのXさんの握手会の予約者本人である」ことを証明します。

  4. 受付は参加者に「Xさんとの握手券」を渡します。

    ここで、受付=Authorization serverはcodeを生成する際に、codeと紐づけてcode_challengeとcode_challenge_methodを保持しておきます。

  5. 参加者は握手券を持って会場に戻り、警備員(Client)に渡します。

  6. 警備員はスタッフ(Resource Server)に握手券を渡します。

    ここで、これまでは握手券=codeを渡すのみでしたが、この際に②で生成したcode_verifierを一緒に渡します。これにより、怪しいアプリケーションではなく、②でcodeをリクエストしたアプリケーション本体だということを確認します。

  7. スタッフは受け取ったcode_verifierを、codeに紐づいているcode_challenge_methodで変換します。正しいcode_verifierであれば、同じくcodeに紐づいてたcode_challengeの値=code_verifierとなるはずです。

codeなんちゃらというワードが色々出てきて頭がとっ散らかりますが、

  • code_verifier:正しいClientしか知らない言葉
  • code_challenge:code_verifierを変換して作ったバレてもいい言葉

というイメージで、code_challengeのもとになるcode_verifierを知っているということが、怪しいアプリでないことの確認に重要となります(厳密には違う気がしますが公開鍵暗号みたいだなと思いました…)

フローを見ていてこの部分の安全性はどうやって確かめるんだろう?と疑問に思った点だったので、PKCEの仕様を見て解けました。