OAuthリクエストを送信してTwitter APIを利用する

# はじめに

Twitter API をユーザートークンで使用したい場合は、OAuth 1.0a で認証を行います。

oauth2 というクレートがありますが、使い方がよくわからなかったので、自前でOAuthのリクエストを送信して認証してみます。

# 参考Webサイト

OAuth 1.0の説明はこちらで日本語訳を読むことができます。

https://openid-foundation-japan.github.io/rfc5849.ja.html

# サンプルのソースコード

~~準備中です。~~

[こちら](https://github.com/ktakase00/tw_oauth_demo) を参照してください。

# OAuth のリクエスト

## 概要

OAuthのリクエストは、非常におおざっぱにいうと、上記サイトの「3.5.1.  Authorization Header」にある通り、HTTPヘッダーに「Authorization」というフィールドを指定して送信するものである、ということになります。

## Authorization HTTPヘッダーフィールド

The OAuth 1.0 Protocol のページからそのまま引用します。「3.5.1.  Authorization Header」に記載されています。

```
  Authorization: OAuth realm="Example",
     oauth_consumer_key="0685bd9184jfhq22",
     oauth_token="ad180jjd733klru7",
     oauth_signature_method="HMAC-SHA1",
     oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
     oauth_timestamp="137131200",
     oauth_nonce="4572616e48616d6d65724c61686176",
     oauth_version="1.0"
```

それぞれのパラメータの説明は「3.1.  Making Requests」にありますが、少し言い換えてみます。

|名前|値|
|---|---|
|realm|省略可ということなので、省略しちゃいます。|
|oauth_consumer_key|コンシューマキーを指定します。|
|oauth_token|以下のように指定します。<br>1. テンポラリクレデンシャルの発行、いわゆる Request Token の取得では指定なし<br>2. Access Token の取得では、Request Token で得られた oauth_token<br>3. リソースにアクセスする場合、いわゆるAPI呼び出し時はアクセストークン|
|oauth_signature_method|ひとまず `HMAC-SHA1` で固定値とします。|
|oauth_signature|後述します。|
|oauth_timestamp|1970/01/01 00:00:00 GMT を起点にした経過秒数です。|
|oauth_nonce|ユニークに生成されたランダムな文字列です。uuidなどを指定できます。|
|oauth_version|ここでは `1.0` 固定とします。|

## 全体の流れ

Authorization の値を組み立てるには、oauth_signature が必要で、そのためにはシグニチャベースストリングが必要で、そのためには... とずいぶん準備が多くなりますが、おおよそ以下の流れになります。

~~すみません、ソースコード全体は準備中です。~~

ソースコード全体は [こちら](https://github.com/ktakase00/tw_oauth_demo) を参照してください。


```rust
    fn make_authorization(&self, method: &str, url_string: &str) -> Result<String, OauthError> {
        let url = Url::parse(url_string)?;

        // ベースストリング URI
        let base_string_uri = self.make_base_uri(&url);

        // タイムスタンプとノンス
        let current_at: DateTime<Utc> = Utc::now();
        let oauth_timestamp = format!("{}", &current_at.timestamp());
        let oauth_nonce = Uuid::new_v4().to_simple().to_string();

        // シグニチャベースストリングに含めるパラメータ
        let mut signature_params = StringPairList::new();
        signature_params
            .add("oauth_consumer_key", &self.consumer_key)
            .add("oauth_signature_method", "HMAC-SHA1")
            .add("oauth_nonce", &oauth_nonce)
            .add("oauth_timestamp", &oauth_timestamp)
            .add("oauth_version", "1.0");
        // oauth_token があれば追加する
        if let Some(oauth_token) = &self.oauth_token {
            signature_params.add("oauth_token", oauth_token);
        }
        // クエリパラメータを追加する
        let pairs = url.query_pairs();
        for (key, value) in pairs {
            signature_params.add(&key, &value);
        }

        // シグニチャベースストリング
        let signature_base_string =
            self.make_signature_base_string(method, &base_string_uri, &signature_params);

        // oauth_signature
        let oauth_token_secret = self.oauth_token_secret.as_ref().map(|x| x.clone());
        let oauth_signature = self.make_oauth_signature(
            &self.consumer_secret,
            &signature_base_string,
            oauth_token_secret,
        );

        // パラメータに oauth_signature を追加
        signature_params.add("oauth_signature", &oauth_signature);

        // Authorization ヘッダーフィールドの値を生成
        let authorization_content = signature_params.make_authorization();
        Ok(authorization_content)
    }
```

## ベースストリング URI

「3.4.1.2.  Base String URI」の部分です。ここではポート番号の考慮は省略してしまいました。

```rust
    fn make_base_uri(&self, url: &Url) -> String {
        format!(
            "{}://{}{}",
            url.scheme(),
            url.host_str().unwrap_or(""),
            url.path()
        )
    }
```

## シグニチャベースストリング

「3.4.1.  Signature Base String」で説明されている文字列です。以下の3つの文字列をつなぎ合わせたものである、ということです。

- HTTP リクエストメソッド
- ベースストリング URI
- クエリとリクエストのエンティティボディーに含まれるパラメータをエンコードしたもの

```rust
    fn make_signature_base_string(
        &self,
        request_method: &str,
        base_uri: &str,
        signature_params: &StringPairList,
    ) -> String {
        let method = request_method.to_uppercase();

        let src_uri_string = &base_uri.to_lowercase();
        let uri = urlencoding::encode(&src_uri_string);

        let query = signature_params.make_query();
        let encoded_query = urlencoding::encode(&query);

        format!("{}&{}&{}", &method, &uri, &encoded_query)
    }
```

あ！リクエストボディが考慮されていませんね。すみません。

## oauth_signature

「3.4.2.  HMAC-SHA1」です。「シグニチャベースストリング」を HMAC-SHA1 して Base64 します。おっと、変な日本語になってしまいました。

キーはクライアント鍵(=コンシューマシークレット)とトークン共有鍵(=アクセストークンシークレット)を結合した文字列です。

```rust
    fn make_oauth_signature(
        &self,
        consumer_secret: &str,
        signature_base_string: &str,
        oauth_token_secret: Option<String>,
    ) -> String {
        let key_string = format!(
            "{}&{}",
            consumer_secret,
            oauth_token_secret.unwrap_or("".to_owned())
        );

        let hasher = Sha1::new();
        let mut hmac = Hmac::new(hasher, &key_string.into_bytes());
        hmac.input(&signature_base_string.to_owned().into_bytes());
        base64::encode(hmac.result().code())
    }
```

## Authorization の組み立て

さて、「3.1.  Making Requests」に戻ってきました。Authorization HTTPヘッダーフィールドに指定する値を組み立てます。

```rust
    pub fn make_authorization(&self) -> String {
        let sort_list = self.make_sort_list();
        let param_string = sort_list
            .iter()
            .map(|x| x.make_encode_string(true))
            .collect::<Vec<String>>()
            .join(", ");
        format!("OAuth {}", &param_string)
    }
```

# Twitter での OAuth 認証

## API

ユーザーの認証をしてみます。以下の流れになります。

1. テンポラリクレデンシャルを取得する。
2. ユーザー認証の画面にリダイレクトする。
3. トークンクレデンシャルを取得する。

Twitter ではそれぞれ以下の API が対応します。

|操作|API|
|---|---|
|テンポラリクレデンシャルの取得|[POST oauth/request_token](https://developer.twitter.com/ja/docs/basics/authentication/api-reference/request_token)|
|ユーザー認証のためのリダイレクト|[GET oauth/authorize](https://developer.twitter.com/ja/docs/basics/authentication/api-reference/authorize)|
|トークンクレデンシャルの取得|[POST oauth/access_token](https://developer.twitter.com/ja/docs/basics/authentication/api-reference/access_token)|

## OAuth リクエストの送信

Authorization HTTPヘッダーを指定してリクエストを送信します。Actix Web で開発しています。HTTPクライアントは [awc](https://docs.rs/awc/latest/awc/) を使用します。

```rust
    /// POST で送信する。
    pub async fn post(&self, url_string: &str) -> Result<String, OauthError> {
        let authorization_content = self.make_authorization("POST", url_string)?;
        let client = awc::Client::default();
        let client_request = client.post(url_string);
        let body_string = self
            .send_request(client_request, &authorization_content)
            .await?;
        Ok(body_string)
    }

    /// GET で送信する。
    pub async fn get(&self, url_string: &str) -> Result<String, OauthError> {
        let authorization_content = self.make_authorization("GET", url_string)?;
        let client = awc::Client::default();
        let client_request = client.get(url_string);
        let body_string = self
            .send_request(client_request, &authorization_content)
            .await?;
        Ok(body_string)
    }

    /// HTTPリクエストを送信する。
    async fn send_request(
        &self,
        client_request: ClientRequest,
        authorization_content: &str,
    ) -> Result<String, OauthError> {
        let mut response = client_request
            .header(
                "Content-Type",
                "application/x-www-form-urlencoded".to_owned(),
            )
            .header("Authorization", authorization_content.to_owned())
            .send()
            .await?;
        // println!("Response: {:?}", response);
        let body = response.body().await?;
        let body_string = String::from_utf8(body.to_vec())?;
        // println!("{}", &body_string);
        Ok(body_string)
    }
```

# サンプルアプリケーション

Actix Web で、ユーザー認証を行い、取得したアクセストークンでユーザーのプロフィール画像を取得してみます。プロフィール画像の取得には、[GET account/verify_credentials](https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials) を使用します。

![20211214_OAuth_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/581eb343-3b8f-5d10-00b3-b95e8f803b32.png)

↓リンクをクリックして Twitterアカウントの認証を行うと、↓

![20211214_OAuth_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27959/61b95010-6eeb-2093-52c5-43c40e3c52a7.png)

表示できました。

# おわりに

Twitter API を例に、OAuth リクエストの送信をしてみました。あちこち考慮が足りていませんが、ユーザートークンによるAPI呼び出しができました。

これで、Twitter API v2 で遊べますね！試してみたいと思います。