OAuth 1.0a 認証の実装 (Twitter API 用)

以前、Twitter API の認証処理を自前で実装して記事にしたのですが、説明をもう少し上手くまとめたいと思ったため、別記事にしました。

Twitter API 以外でも同様に実装できると思いますが、Twitter API 以外向けにも書こうとすると説明をより抽象化しないといけないため、ここでは Twitter API 向けとして書きます。他向けに OAuth 認証を実装する場合は上手く読み替えて下さい。

参考「[Creating a signature — Twitter Developers](https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/creating-a-signature)」

# 1. 認証の流れ

通常の HTTP リクエストに Authorization ヘッダーを加えることで認証します。

```http:Authorization&nbsp;ヘッダーの例
Authorization: OAuth
    oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", 
    oauth_nonce="6eb24361e7250e5112288fa4954dd8f634a7320c342c43019510c2cda8c8b3db", 
    oauth_signature_method="HMAC-SHA1", 
    oauth_timestamp="1581159389", 
    oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", 
    oauth_version="1.0", 
    oauth_signature="8TACi1tsshSi9dfiLa8Vm8SasTs%3D"
```

※見やすくするために改行していますが、実際は 1 行で記述します。

- `oauth_consumer_key`, `oauth_token`: 手持ちの API キーとアクセストークン
- `oauth_signature_method`, `oauth_version`: 固定値 "HMAC-SHA1", "1.0"
- `oauth_timestamp`: リクエスト時の秒単位のタイムスタンプ
- `oauth_nonce`: リクエストごとに固有の値。生成方法に特に規定なし
- `oauth_signature`: **OAuth 1.0a HMAC-SHA1 シグネチャ** (※計算方法は後述)

参考「[Authorizing a request — Twitter Developers](https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/authorizing-a-request)」

# 2. シグネチャの計算

## 2.1. 材料

- リクエストメソッド `GET`/`POST`
- リクエスト URL (GET の場合はクエリパラメータを付けていない状態の)
- GET や POST のパラメータ
- `oauth_*` パラメータ (`oauth_signature` 除く)
    - API キー・アクセストークン
    - タイムスタンプ・NONCE
    - "HMAC-SHA1", "1.0"
- API シークレットキー・アクセストークンシークレット

## 2.2. 大まかな手順

「シグネチャベース」と「シグネチャキー」をそれぞれ計算し、その 2 つから HMAC-SHA1 ハッシュ値を求め、Base64 エンコードする。

![test01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232067/af566c86-2383-5d0b-aa61-0745957c7238.png)


## 2.3. 計算

### 2.3.1. パラメータをまとめる

1. 「GET や POST のパラメータ」と「`oauth_*` パラメータ」をを合わせる
2. key と value を **RFC 3986** に基づくパーセントエンコードする
3. キーの名前順にソート ※
4. `&` で連結してクエリ文字列にする

※ Twitter API の場合はキーは重複しませんが、他の API 等で重複する場合は次に値の順でソートしてください。

例：

|エンコードされた key|エンコードされた value|
|---|---|
|count|100|
|oauth_consumer_key|xvz1evFS4wEEPTGEFPHBog|
|oauth_nonce|6eb24361e7250e5112288fa4954dd8f634a7320c342c43019510c2cda8c8b3db|
|oauth_signature_method|HMAC-SHA1|
|oauth_timestamp|1581159389|
|oauth_token|370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb|
|oauth_version|1.0|
|screen_name|twitterdev|
|skip_status|true|

`count=100&oauth_consumer_key=xvz1evFS4wEEPTGEFPHBog& ... (略)`

### 2.3.2. 「シグネチャベース」の計算

以下の文字列を RFC 3986 に基づくパーセントエンコードをして、クエリ文字列にします。これを「シグネチャベース」とする。

- リクエストメソッド `GET`/`POST`
- リクエスト URL (GET の場合はクエリパラメータを付けていない状態の)
- 「2.3.1」のパラメータを合体したクエリ文字列

例：

|生の key|
|---|
|GET|
|https://api.twitter.com/1.1/followers/list.json|
|count=100&oauth_consumer_key=xvz1evFS4wEEPTGEFPHBog& ... (略)|

`GET&https%3A%2F%2Fapi.twitter.com%2F1.1%2Ffollowers%2Flist.json&count%3D100%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26 ... (略)`

### 2.3.3. 「シグネチャキー」の計算

以下の文字列を RFC 3986 に基づくパーセントエンコードをして、クエリ文字列にします。これを「シグネチャキー」とする。

- API シークレットキー・アクセストークンシークレット

例：

|生の key|
|---|
|kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw|
|LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE|

`kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE`

### 2.3.4. HMAC-SHA1 ハッシュ値の計算

「シグネチャベース」と「シグネチャキー」から HMAC-SHA1 ハッシュ値を計算します。

これは自力で実装するよりも、既存のライブラリ等を利用したほうが安全で簡単です。

バイナリの出力を得られます。これを Base64 エンコードすると文字列のシグネチャになります。

例：`hCtSmYh+iHYCEqBWrE7C7hYmtUk=`

### 2.3.5. 注意点

- URL エンコードは RFC 3986 に基づく。
- 当然ながら、クエリ文字列を作る際にキーと値は URL エンコードする。
    - JavaScript の場合、`encodeURIComponent()` は RFC 3986 に基づいていないため、一部文字の置換が必要。
    - PHP の場合、`http_build_query()` を使うときに `PHP_QUERY_RFC3986` を指定すると良い。
- 「2.3.1」でパラメータをまとめる際に、パラメータをソートする**前に**キーを URL エンコードする。
- (OAuth の仕様では、キーが重複する際に値でソートするが、Twitter API の場合はキーが重複しない。)
- (リクエストメソッドは大文字にする。)
- 「シグネチャベース」を作る際に、「パラメータを合体したクエリ文字列」の中の '&' はエスケープされ、全体として '&' が 2 つのみの状態になる。

参考「[encodeURIComponent() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent)」
参考「[PHP: http_build_query - Manual](https://www.php.net/manual/ja/function.http-build-query.php)」

# 3. Authorization OAuth ヘッダの作成

- `oauth_*` パラメータ
    - API キー・アクセストークン
    - タイムスタンプ・NONCE
    - "HMAC-SHA1", "1.0"
    - シグネチャ

1. key と value を RFC 3986 に基づくパーセントエンコードする
2. `key="value"` を `,`` ` (カンマとスペース)　区切りで連結する

認証情報: `oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", oauth_nonce= ... (略)`
ヘッダ: `Authorization: OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", oauth_nonce= ... (略)`

参考「[Authorizing a request — Twitter Developers](https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/authorizing-a-request)」(再掲)