OAuth 2.0 PKCE 認証について簡単にまとめてみました。

今回は、 `Fitbit API` を利用する際に `OAuth 2.0 PKCE` というものが出てきたので、調べてみた結果をざっくりとまとめてみました。

:::note info
シーケンス図の上下の余白について
この記事に掲載されているシーケンス図は、 `Mermaid構文` を利用して生成されています。
~~横に長くなりすぎた為なのか、シーケンス図の上下に余分な余白が発生してしまっています。
height が固定値で入ってしまっているので、調整が出来ませんでした。
見ずらいとは思いますが、ご了承のほどよろしくお願いいたします。~~

改善されました！
ご対応ありがとうございました。
https://github.com/increments/qiita-discussions/discussions/481
:::


# OAuth 2.0 の許可コードの取得方法って何があるの？
OAuth 2.0 を用いた許可コードの取得方法は、大雑把に分けると下記の2つが挙げられます。  
- `OAuth 2.0`（[RFC6749](https://datatracker.ietf.org/doc/html/rfc6749)）
- `OAuth 2.0 PKCE`（[RFC7636](https://datatracker.ietf.org/doc/html/rfc7636)）

最近の API では `OAuth 2.0 PKCE`（[RFC7636](https://datatracker.ietf.org/doc/html/rfc7636)）を用いた認証フローが推奨されています。  
前述の `Fitbit` 以外では `LINE` や `Yahoo!` でも導入されています。  
（`PKCE` は `ピクシー` と発音します。）  



# そもそも何で、2種類あるの？
結論から言うと、 `OAuth 2.0` では認証の許可コードが悪意あるアプリに横取りされる可能性があるからです。

## 認証の許可コードの横取り攻撃について
`OAuth 2.0` を用いた認証フローでは、API連携のためのログインおよび許可後、許可コードが発行されて、コールバックURLへリダイレクトされます。  
事前にコールバックURLをアプリに紐付けておくことで、アプリ側で許可コードが取得できるようになります。  

この際、アプリとコールバックURLを紐付ける方法として、 `カスタムURLスキーム` というものが用いられます。  
カスタムURLスキームとは、 `apps://` というようなURLのことで、このURLにアクセスすると、事前に設定されたアプリが開くようになっています。  
カスタムURLスキームは、他のアプリと同じものを指定することが出来てしまうため、開発者側でどのアプリを開かせるかまではコントロールできません。  

悪意あるアプリは、カスタムURLスキームをターゲットとなる他のアプリに偽装し、成りすますことで許可コードを奪取しようとします。



# OAuth 2.0 認証について
`OAuth 2.0` と `OAuth 2.0 PKCE` の違いや横取り攻撃について理解するために、まずは `OAuth 2.0` の通常の認証フローと悪意あるアプリが認証コードを奪取しようとする際のフローをシーケンス図で見てみましょう。

## 通常の OAuth 2.0 認証フロー
まずは Fitbit API を例にした通常の OAuth 2.0 認証フローです。  
```mermaid
sequenceDiagram
    actor ユーザー
    participant OS
    participant アプリ
    participant Fitbit OAuth
    participant Fitbit Token
    ユーザー ->> アプリ: アプリへのログイン
    アプリ ->> Fitbit OAuth: Fitbit連携用ログインURLへアクセス
    Fitbit OAuth -) ユーザー: ログイン画面の表示
    ユーザー ->> Fitbit OAuth: ログイン
    Fitbit OAuth -) ユーザー: 同意画面の表示
    ユーザー ->> Fitbit OAuth: 許可
    Fitbit OAuth ->> Fitbit OAuth: 許可コードを発行して保存
    Fitbit OAuth -) OS: 許可コード("code")を含むコールバックURLへリダイレクト(カスタムURLスキーム)
    OS ->> アプリ: カスタムURLスキームを利用し、アプリを起動
    アプリ ->> Fitbit Token: 許可コード("code")を用いてアクセストークンのリクエスト
    Fitbit Token ->> Fitbit Token: 許可コードの検証
    Fitbit Token -) アプリ: アクセストークンの返却
```

## 悪意あるアプリにアクセストークンを奪取されてしまった場合の OAuth 2.0 認証フロー
次に Fitbit API を例にした悪意あるアプリにアクセストークンを奪取されてしまった場合の OAuth 2.0 認証フローです。
```mermaid
sequenceDiagram
    actor ユーザー
    participant OS
    participant アプリ
    participant 悪意あるアプリ
    participant Fitbit OAuth
    participant Fitbit Token
    ユーザー ->> アプリ: アプリへのログイン
    アプリ ->> Fitbit OAuth: Fitbit連携用ログインURLへアクセス
    Fitbit OAuth -) ユーザー: Fitbitログイン画面の表示
    ユーザー ->> Fitbit OAuth: ログイン
    Fitbit OAuth -) ユーザー: 同意画面の表示
    ユーザー ->> Fitbit OAuth: 許可
    Fitbit OAuth ->> Fitbit OAuth: 許可コードを発行して保存
    Fitbit OAuth -) OS: 許可コード("code")を含むコールバックURLへリダイレクト(カスタムURLスキーム)
    OS ->> 悪意あるアプリ: カスタムURLスキームを乗っ取り、許可コード("code")を奪取
    悪意あるアプリ ->> Fitbit Token: 許可コード("code")を用いてアクセストークンのリクエスト
    Fitbit Token ->> Fitbit Token: 許可コードの検証
    Fitbit Token -) 悪意あるアプリ: アクセストークンの返却
```


# OAuth 2.0 PKCE 認証について
上記の認可コードの横取り攻撃を阻止する方法として、考えられたのが `OAuth 2.0 PKCE`（[RFC7636](https://datatracker.ietf.org/doc/html/rfc7636)）認証フローです。  

PKCE の特徴は、許可コードのリクエストを行うサーバー側で事前に `code_verifier` と `code_challenge` と呼ばれるものを生成します。  

`code_verifier` と `code_challenge` のそれぞれの特徴は下記のとおりです。  
- code_verifier： 43～128文字から構成されるランダムな文字列です。  
- code_challenge： `code_verifier` を `SHA256` でハッシュ化し、 `Base64 URL Encode` したものです。

認証フローでの特徴は、下記のとおりです。
1. OAuth サーバーへの許可コードのリクエスト時に、 `code_challenge` を送ります。
1. OAuth サーバーでは `code_challenge` を保存した上で、許可コードを発行します。
1. 発行された許可コードとアクセストークンの引換のリクエスト時に OAuth サーバーに `code_verifier` を送ります。
1. OAuth サーバーでは、送られてきた `code_verifier` からハッシュ値を計算し、事前に受け取っていた `code_challenge` と計算したハッシュ値が一致するかを検証します。
1. ハッシュ値が一致した場合はアクセストークンを発行し、返します。

こちらも先程同様にシーケンス図で見てみましょう。

## OAuth 2.0 PKCE 認証フロー
まずは Fitbit API を例にした通常の OAuth 2.0 PKCE 認証フローです。
```mermaid
sequenceDiagram
    actor ユーザー
    participant OS
    participant アプリ
    participant Fitbit OAuth
    participant Fitbit Token
    ユーザー ->> アプリ: アプリへのログイン
    アプリ ->> Fitbit OAuth: Fitbit連携用ログインURLへアクセス("code_challenge" を渡す)
    Fitbit OAuth ->> Fitbit OAuth: "code_challenge" の保存
    Fitbit OAuth -) ユーザー: ログイン画面の表示
    ユーザー ->> Fitbit OAuth: ログイン
    Fitbit OAuth -) ユーザー: 同意画面の表示
    ユーザー ->> Fitbit OAuth: 許可
    Fitbit OAuth ->> Fitbit OAuth: 許可コードを発行して保存
    Fitbit OAuth -) OS: 許可コード("code")を含むコールバックURLへリダイレクト(カスタムURLスキーム)
    OS ->> アプリ: カスタムURLスキームを利用し、アプリを起動
    アプリ ->> Fitbit Token: 許可コード("code")を用いてアクセストークンのリクエスト("code_verifier" を渡す)
    Fitbit Token ->> Fitbit Token: "code_verifier" をハッシュ化し、"code_challenge" と一致するか検証
    Fitbit Token -) アプリ: アクセストークンの返却
```

## 悪意あるアプリがアクセストークンの奪取をしようとした場合の OAuth 2.0 PKCE 認証フロー
次に Fitbit API を例にした悪意あるアプリがアクセストークンの奪取をしようとした場合の OAuth 2.0 PKCE 認証フローです。
```mermaid
sequenceDiagram
    actor ユーザー
    participant OS
    participant アプリ
    participant 悪意あるアプリ
    participant Fitbit OAuth
    participant Fitbit Token
    ユーザー ->> アプリ: アプリへのログイン
    アプリ ->> Fitbit OAuth: Fitbit連携用ログインURLへアクセス("code_challenge" を渡す)
    Fitbit OAuth ->> Fitbit OAuth: "code_challenge" の保存
    Fitbit OAuth -) ユーザー: ログイン画面の表示
    ユーザー ->> Fitbit OAuth: ログイン
    Fitbit OAuth -) ユーザー: 同意画面の表示
    ユーザー ->> Fitbit OAuth: 許可
    Fitbit OAuth ->> Fitbit OAuth: 許可コードを発行して保存
    Fitbit OAuth -) OS: 許可コード("code")を含むコールバックURLへリダイレクト(カスタムURLスキーム)
    OS ->> 悪意あるアプリ: カスタムURLスキームを乗っ取り、許可コード("code")を奪取
    悪意あるアプリ ->> Fitbit Token: 許可コード("code")を用いてアクセストークンのリクエスト
    Fitbit Token --x Fitbit Token: "code_verifier" をハッシュ化し、"code_challenge" と一致するか検証
    Fitbit Token --x 悪意あるアプリ: アクセストークンの返却
```

## まとめ
PKCE を用いた認証を行うことで、悪意あるアプリに認証コードを奪取されたとしても、 OAuthサーバーに保存されている `code_challenge` の元になっている `code_verifier` が、悪意あるアプリには分からないため、アクセストークンの取得を阻止できます。  

ただし、 `code_verifier` は正規のアプリしか知り得ない前提となっているため、 `code_verifier` が外部に漏れてしまうと横取り攻撃をされるリスクが生じます。  
従って、 `code_verifier` 及び `code_challenge` は一意のものを利用するのではなく、認証コードのリクエストごとに生成し直す必要がある点に注意が必要です。


# 参考文献
下記のサイトを参考にさせて頂きました。

https://qiita.com/naoya_matsuda/items/992c67b803ff460818ec