3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-13

はじめに

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

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

参考Webサイト

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

サンプルのソースコード

準備中です。

こちら を参照してください。

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 以下のように指定します。
1. テンポラリクレデンシャルの発行、いわゆる Request Token の取得では指定なし
2. Access Token の取得では、Request Token で得られた oauth_token
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 が必要で、そのためにはシグニチャベースストリングが必要で、そのためには... とずいぶん準備が多くなりますが、おおよそ以下の流れになります。

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

ソースコード全体は こちら を参照してください。

    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」の部分です。ここではポート番号の考慮は省略してしまいました。

    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
  • クエリとリクエストのエンティティボディーに含まれるパラメータをエンコードしたもの
    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 します。おっと、変な日本語になってしまいました。

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

    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ヘッダーフィールドに指定する値を組み立てます。

    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
ユーザー認証のためのリダイレクト GET oauth/authorize
トークンクレデンシャルの取得 POST oauth/access_token

OAuth リクエストの送信

Authorization HTTPヘッダーを指定してリクエストを送信します。Actix Web で開発しています。HTTPクライアントは awc を使用します。

    /// 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 を使用します。

20211214_OAuth_01.png

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

20211214_OAuth_02.png

表示できました。

おわりに

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

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

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?