LoginSignup
0
2

Rust用TwitterAPIv2ライブラリでOAuth1.0a系の認証をサポートした話

Posted at

目的

RustでTwitterAPIv2のライブラリを書いています。
twapi-v2
TwitterAPIv2からOAuth2.0がサポートされてBearer認証で呼びだせます。
実はTwitterAPIv1で使われていたOAuth認証もまだ使えると気が付いたのでサポートしました。

OAuth2.0とOAuth1.0aの違い

OAuth2.0

■ WebなどのOAuth結果
access_token : APIを呼び出すトークン
refresh_token : 有効期限が切れたaccess_tokenの更新のためのトークン
expires_in : access_tokenの有効期間
■ APIを呼び出す時の使い方
ヘッダーに「Authorization: Bearer <access_token>」を設定
■ 有効期限
あり

OAuth1.0a

■ WebなどのOAuth結果
oauth_token : APIの認証の計算に使用
oauth_token_secret : APIの認証の計算に使用
■ APIを呼び出す時の使い方
以下を使って認証トークンを計算する

  • アプリケーションを登録した時に得られるoauth_consumer_key, oauth_consumer_secret
  • 上記のoauth_token, oauth_token_secret
  • APIのURL, メソッド, クエリーパラメーター

ヘッダーに「Authorization: <認証トークン>」を設定
■ 有効期限
無し

OAuth2.0しかサポートしなかった0.5系

Bearer認証しかサポートしていないコードは以下のようになります。最初に認証コードを渡しています。

main.rs
use twapi_v2::api::get_2_tweets_id;

#[tokio::main]
async fn main() {
    let bearer_code = std::env::var("BEARER_CODE").unwrap();
    let tweet_id = std::env::var("TWEET_ID").unwrap();
    let res = get_2_tweets_id::Api::open(&bearer_code, &tweet_id)
        .execute()
        .await;
    if let Some((val, rate_limit)) = res {
        println!("{:?}", val);
    }
}

OAuth2.0とOAuth1.0aをサポートした0.7系

認証方法は実行時に渡すことにしました。以下のようなコードになります。

main.rs
use twapi_v2::api::{get_2_tweets_id, BearerAuthentication};

#[tokio::main]
async fn main() {
    let bearer_code = std::env::var("BEARER_CODE").unwrap();
    let auth = BearerAuthentication::new(bearer_code);
    let tweet_id = std::env::var("TWEET_ID").unwrap();
    let res = get_2_tweets_id::Api::open(&tweet_id)
        .execute(&auth)
        .await;
    if let Some((val, rate_limit)) = res {
        println!("{:?}", val);
    }
}

OAuth1.0aは以下のようになります。

main.rs
use twapi_v2::api::{get_2_tweets_id, BearerAuthentication};
use twapi_v2::oauth10a::OAuthAuthentication;

#[tokio::main]
async fn main() {
    let auth = OAuthAuthentication::new(
        std::env::var("CONSUMER_KEY").unwrap_or_default(),
        std::env::var("CONSUMER_SECRET").unwrap_or_default(),
        std::env::var("ACCESS_KEY").unwrap_or_default(),
        std::env::var("ACCESS_SECRET").unwrap_or_default(),
    );
    let tweet_id = std::env::var("TWEET_ID").unwrap();
    let res = get_2_tweets_id::Api::open(&tweet_id)
        .execute(&auth)
        .await;
    if let Some((val, rate_limit)) = res {
        println!("{:?}", val);
    }
}

コードの説明

Authenticationトレイト

まず共通する認証用のトレイトを作成しました。

pub trait Authentication {
    fn execute(
        &self,
        builder: RequestBuilder,
        method: &str,
        uri: &str,
        options: &[(&str, &str)],
    ) -> RequestBuilder;
}

builderにはここで計算した認証情報を設定して返します。それ以外のパラメーターはOAuth1.0aの計算で必要なものです。

OAuth2.0

OAuth2.0用の実装です。bearer_codeだけが必要になります。

pub struct BearerAuthentication {
    bearer_code: String,
}

impl BearerAuthentication {
    pub fn new<T: Into<String>>(bearer_code: T) -> Self {
        Self {
            bearer_code: bearer_code.into(),
        }
    }
}

impl Authentication for BearerAuthentication {
    fn execute(
        &self,
        builder: RequestBuilder,
        _method: &str,
        _uri: &str,
        _options: &[(&str, &str)],
    ) -> RequestBuilder {
        builder.bearer_auth(&self.bearer_code)
    }
}

Bearer認証はとても簡単です。reqwestが専用のメソッドを用意しています。

OAuth1.0a

OAuth1.0a用の実装です。4つの値が必要になります。

pub struct OAuthAuthentication {
    consumer_key: String,
    consumer_secret: String,
    access_key: String,
    access_secret: String,
}

impl OAuthAuthentication {
    pub fn new<T: Into<String>>(
        consumer_key: T,
        consumer_secret: T,
        access_key: T,
        access_secret: T,
    ) -> Self {
        Self {
            consumer_key: consumer_key.into(),
            consumer_secret: consumer_secret.into(),
            access_key: access_key.into(),
            access_secret: access_secret.into(),
        }
    }
}

impl Authentication for OAuthAuthentication {
    fn execute(
        &self,
        builder: RequestBuilder,
        method: &str,
        uri: &str,
        options: &[(&str, &str)],
    ) -> RequestBuilder {
        let auth = oauth1_authorization_header(
            &self.consumer_key,
            &self.consumer_secret,
            &self.access_key,
            &self.access_secret,
            method,
            uri,
            &options.to_vec(),
        );
        builder.header(reqwest::header::AUTHORIZATION, auth)
    }
}

この認証ではbuilder以外の引数を全て使って認証コードを計算します。builderにはAuthorizationのヘッダーとして設定します。

各API

実際に利用してるコードは以下のようになります。

pub fn build(self, authentication: &impl Authentication) -> RequestBuilder {
    let mut query_parameters = vec![];
    if let Some(expansions) = self.expansions {
        query_parameters.push(("expansions", expansions.iter().join(",")));
    }
    if let Some(media_fields) = self.media_fields {
        query_parameters.push(("media.fields", media_fields.iter().join(",")));
    }
    if let Some(place_fields) = self.place_fields {
        query_parameters.push(("place.fields", place_fields.iter().join(",")));
    }
    if let Some(poll_fields) = self.poll_fields {
        query_parameters.push(("poll.fields", poll_fields.iter().join(",")));
    }
    if let Some(tweet_fields) = self.tweet_fields {
        query_parameters.push(("tweet.fields", tweet_fields.iter().join(",")));
    }
    if let Some(user_fields) = self.user_fields {
        query_parameters.push(("user.fields", user_fields.iter().join(",")));
    }
    let client = reqwest::Client::new();
    let url = URL.replace(":id", &self.id);
    let builder = client.get(&url).query(&query_parameters);
    authentication.execute(
        builder,
        "GET",
        &url,
        &query_parameters
            .iter()
            .map(|it| (it.0, it.1.as_str()))
            .collect::<Vec<_>>(),
    )
}

query_parameterは実際にリクエストに渡すだけでなくOAuthの計算にも必要になります。

まとめ

共通トレイトを書いて認証方法をそれぞれ実装しました。利用者は使いたい認証を選択するだけでAPIは同じように呼び出すことができます。

0
2
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
0
2