はじめに
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!("{}", ¤t_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 {}", ¶m_string)
}
Twitter での OAuth 認証
API
ユーザーの認証をしてみます。以下の流れになります。
- テンポラリクレデンシャルを取得する。
- ユーザー認証の画面にリダイレクトする。
- トークンクレデンシャルを取得する。
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 を使用します。
↓リンクをクリックして Twitterアカウントの認証を行うと、↓
表示できました。
おわりに
Twitter API を例に、OAuth リクエストの送信をしてみました。あちこち考慮が足りていませんが、ユーザートークンによるAPI呼び出しができました。
これで、Twitter API v2 で遊べますね!試してみたいと思います。