ユーザーの認可にはOAuthを用いるのが一般的です。OAuthではアクセストークンをやりとりしますが、OAuth標準の仕様だと、悪意のある第三者にアクセストークンを取得されるおそれがあります。
これを防ぐのがPKCEです。アプリケーションをPKCEに対応することで、第三者によるアクセストークンの取得を防ぐことができます。この記事では、OAuthによる認可機能の実装をより安全にするためのPKCEという仕様の概要と実装例を示します。
テクニカルライター。元エンジニア。共著で「現場で使えるRuby on Rails 5」を書きました。プログラミング教室を作るのが目標です。
PKCEとは何か
PKCEは「アクセストークンを安全にやり取りするための、OAuth 2.0の拡張仕様」です。Proof Key for Code Exchangeの略で、意訳すると「安全にコードをやりとりするための証明鍵」となります。
PKCEは2015年9月にRFC7636として定義されています。「ピクシー」と発音します。
一般的な認可フロー
OAuthを用いた一般的な認可のフローを見てみます。ユーザーがアプリにアクセスすると、認可を求められます。ユーザーは認可サーバー上で認証を行い、成功すると認可サーバーからアプリ側にコードが渡ります。

アプリは受け取ったコードを元に、認可サーバーからアクセストークンを取得します。あとはアクセストークンを元にプロフィール情報などを取得して、ユーザーに画面を表示します。これが一般的な認可のフローになります。
第三者によるアクセストークンの横取りフロー
一般的な認可のフローでは、手順5でアプリがコールバックを受け取っています。このコールバックを、「悪意のあるアプリ」に取得されてしまった場合のフローを見てみます。
ユーザーが認可サーバー上で認証を行うまでは同じですが、認証後にアプリではなく第三者にコールバックが渡ってしまいます。

このコールバックには、アクセストークンを取得するためのコードが含まれています。つまり、第三者がアクセストークンを取得できてしまいます。アクセストークンがあれば、ユーザーの個人情報を取得したり、機能を操作することができてしまいます。これを防ぐのがPKCEという仕様になります。
「悪意のあるアプリ」とは
上のフロー図で「悪意のあるアプリ」というのが出てきました。この第三者は、どうやってコールバックを取得するのでしょうか。
具体的にはカスタムURLスキームを利用する方法があります。例えばiPhoneでは、ブラウザにmusic://
というURLを入力するとMusicアプリを起動できます。このようにURLの形式で任意のアプリを開くのがURLスキームです。
ただ、開発者が設定できるカスタムURLスキームでは、どのアプリを開くかまでは特定できません。つまり、あなたのアプリがfoo://
というURLスキームでの起動を想定しても、実際には第三者のアプリが起動する可能性が出てきます。
第三者があなたのアプリのfoo://
から始まるコールバックを取得してしまった場合、認可サーバーから第三者のアプリが起動されることになります。これがアクセストークンの流出につながります。
この問題を防ぐために、例えばLINEはURLスキームでのアプリ起動を非推奨にしています。アプリを開発する上では、このような乗っ取りの可能性があることを想定しておきたいです。
PKCEに対応する方法
上述した、悪意のあるアプリへの対策がPKCEです。PKCEに対応した場合の認可のフローを見てみます。

ユーザーがアプリにアクセスすると認可を求められます。このとき、アプリ側ではcode_verifier
とcode_challenge
という二つの文字列を生成します。
文字列 | 概要 | 例 |
---|---|---|
code_verifier | リクエストを検証するための鍵 | ns2KCK1Ixjxo1XXl... |
code_challenge | code_verifierを暗号化したもの | 7jvJI4-Gzlc69T0x... |
アプリはcode_challenge
を認可用URLに含めることで認可サーバーに送ります。認可サーバー側で、このcode_challenge
を保管しておきます。あとは、アクセストークンをリクエストするときにcode_verifier
を付け加えます。認可サーバーはcode_verifier
が正しいかどうかを、保管しておいたcode_challenge
を元に検証します。
code_verifier
が正しければアクセストークンを返却し、間違っていれば何も返しません。code_verifier
は正規のアプリしか知ることはないので、第三者にアクセストークンが横取りされることがなくなる、という仕組みです。
第三者による横取りの例
PKCEに対応したアプリに対して、悪意のあるアプリが横取りを試みるフローを見てみます。第三者がコールバックURLを取得できたとします。この場合でも、第三者がcode_verifier
を知ることができません。

認可サーバーはリクエストのcode_verifier
を元に検証を試みますが、正しい値ではないため検証に失敗します。つまり、アクセストークンが返されることはありません。
PKCEのアプリ側の実装例
次に、PKCEに対応するための具体的な実装例を見てみます。ここでは参考実装として、Rubyのコードを示します。PKCE対応において重要なのはcode_verifier
とcode_challenge
の生成です。この二つの仕様は次の通りです。
文字列 | 仕様 |
---|---|
code_verifier | 43〜128文字。半角英数、-._~ の記号で構成される。推測されてはならず、適切な乱数生成器を用いて生成する |
code_challenge | code_verifier をSHA256で暗号化し、Base64URL形式にエンコードしたもの |
実装例は次の通りです。
# code_verifier code_verifier = SecureRandom.alphanumeric(64) # code_challenge code_challenge = Base64.urlsafe_encode64(OpenSSL::Digest::SHA256.digest(code_verifier), padding: false)
この二つの文字列を説明するために、以下にPKCE対応のフロー図を再掲します。

ここでいう認可用URLのパラメータとしてcode_challenge
を付与します。また、あわせてcode_challenge_method
という値も含めます。code_challenge_method
は「code_verifier
をどんな方法で暗号化したか」で、SHA256を表すS256
という文字列で固定します。
https://xxx/authorize?xxx=xxx&code_challenge=abcdefg...&code_challenge_method=S256
あとは、アクセストークンのリクエスト時にcode_verifier
を含めるだけです。
$ curl -v -X POST https://xxx/access_token \ -d 'xxx=xxx' \ -d 'code_verifier=123456...'
こうすることで、認可サーバー側でcode_verifier
を検証してくれます。アプリ側の実装としては、やることは以上になります。これだけで、第三者によるアクセストークンの横取りを防ぐことができます。
PKCEの対応状況
PKCEの仕様は2015年9月に策定され、各社が対応しています。例えば認証サービスのAuth0では、モバイルアプリでのPKCE対応を必須、SPAでも推奨としています。
Yahoo! IDでもPKCEへの対応が可能になっており、またLINEの認証サービス「LINEログイン」でもPKCE対応が推奨されています。Rubyの認可ライブラリ「OmniAuth」では、オプションでPKCEを有効化できます。
インターネットの標準化団体であるIETFのOAuth 2.0 Security Best Current PracticeでもPKCE対応についての記述があるように、PKCEに対応することでより安全なアプリをユーザーに提供することができます。