これは
自身の学習のためにTwitterの認証方法の1つであるPINコード認証を実施するシェルスクリプトを実装しました。本稿はその手順とコードの解説になります。
コードは下記に置いてあります。
環境
WSL2環境(Ubuntu 20.04 LTS)です。また、テキストブラウザとして lynx を使用しています。
前提
Twitter Developper Portalへの登録が済んでおり、アプリケーションの登録とコンシューマーキー、コンシューマーシークレットキーが取得できていることを前提とします。
実装の前提
各種キーの取得方法
Twitter APIでPINコード認証する際は、コンシューマーキー、コンシューマーシークレットキー、および(手順上)OAuthトークンシークレットが必要となります。これらは環境によって異なるため、それぞれ環境変数CONSUMER_KEY
, CONSUMER_SECRET_KEY
, OAUTH_TOKEN_SECRET
で受け取るようにしています。
nonceの生成方法
Twitter APIでリクエストを発行する際は、oauth_nonce
パラメータを指定する必要があります。これはこちらに記載されている通りアプリケーションがリクエストごとに生成する必要がある一意のトークンです。
oauth_nonceパラメータの生成処理は複数のリクエストで共通的に使用するため、下記のような関数に切り出しています。
function generate_nonce {
fold -b -w 32 < /dev/urandom | head -n 1 | base64
}
処理内容は、/dev/urandom
からランダム値を取得し、base64
エンコードしています。
timestampの生成方法
oauth_nonce
パラメータと同じく、Twitter APIでリクエストを発行する際はoauth_timestamp
パラメータを指定する必要があります。これもこちらに記載されている通りリクエストが生成された際のUnix時間からの秒数です。
oauth_timestamp
パラメータの生成処理も複数のリクエストで共通的に使用するため、下記のような関数に切り出しています。
function generate_timestamp {
date +%s
}
処理内容は、date
コマンドでunix時間からの経過秒数を取得しています。
パーセントエンコードの方法
Twitter APIの仕様を見ると、さまざまな所でパーセントエンコードを行う必要があります。
bashで文字列をパーセントエンコードする方法は、調べるとjq
を用いる方法やnkf
を用いる方法など、いくつか存在するのですが、今回は下記のように自前で実装する方法を取っています(それぞれの方法には癖があるようで、期待通りにエンコードされなかったため)。
function pct_encode {
local length="${#1}"
for ((i = 0; i < length; i++)); do
local c="${1:$i:1}"
case $c in
[a-zA-Z0-9.~_-]) printf '%s' "$c" ;;
*) printf '%%%02X' "'$c" ;;
esac
done
}
処理内容なシンプルに、1文字ずつ、a-zA-Z0-9.~_-
のいずれかであればそのまま出力、そうでなければASCIIコードの値を16進で出力しています。
Twitter API の認証について
Twitter APIを叩くためには、基本的に認証が必要となります。認証は大きく下記2つの手順から成ります。
- 署名の作成
- Authorizationヘッダ文字列の作成
署名の作成
リクエストにかかわる各種パラメータを基に署名を作成します。手順はこちら。
実装したコード
署名作成の処理を複数のリクエストで共通的に利用できるよう、mksignature.sh
というコマンドへ切り出しています。
#!/bin/bash
# shellcheck disable=SC2214
# shellcheck disable=SC2034
############
# 署名の作成
############
###
# リクエストメソッドとURLの収集, パラメーターの収集
###
declare -A signature_params=(
["oauth_consumer_key"]="${CONSUMER_KEY:?CONSUMER_KEY not found}"
["oauth_signature_method"]="HMAC-SHA1"
["oauth_version"]="1.0"
)
while getopts :-: opt; do
optarg="${!OPTIND}"
[[ "$opt" = - ]] && opt="-$OPTARG"
case "-$opt" in
--http_method)
http_method="$optarg"
shift
;;
--base_url)
base_url="$optarg"
shift
;;
--*)
signature_params["${opt##-}"]="$optarg"
shift
;;
esac
done
# 署名されるすべてのキーと値をパーセントエンコードします。
declare -A encoded_signature_params
for key in "${!signature_params[@]}"; do
encoded_signature_params[$(pct_encode "${key}")]=$(pct_encode "${signature_params[${key}]}")
done
# パラメーターのリストをエンコードされたキーでアルファベット順に並べ替えます。
# (bashの連想配列はソートできないため、ソートされたキー順にアクセスすることで代替します)
orig_ifs=${IFS}
IFS=$'\n'
mapfile -t sorted_encoded_keys < <(echo "${!encoded_signature_params[*]}" | sort)
IFS=${orig_ifs}
# それぞれのキーと値のペアに対して:
parameter_string=""
for encoded_key in "${sorted_encoded_keys[@]}"; do
# 出力文字列にエンコードされたキーを追加します。
parameter_string="${parameter_string}${encoded_key}"
# 出力文字列に「=」を追加します。
parameter_string="${parameter_string}="
# 出力文字列にエンコードされた値を追加します。
parameter_string="${parameter_string}${encoded_signature_params[${encoded_key}]}"
# キーと値のペアがまだ残っている場合は、出力文字列に「&」を追加します。
parameter_string="${parameter_string}&"
done
parameter_string=${parameter_string%&} #末尾の不要な"&"を取り除く
###
# 署名ベース文字列の作成
###
signature_base_string=""
# HTTPメソッドを大文字に変換し、出力文字列をこの値に設定します。
signature_base_string="${signature_base_string}${http_method^^}"
# 出力文字列に「&」を追加します。
signature_base_string="${signature_base_string}&"
# URLをパーセントエンコードし、出力文字列に追加します。
signature_base_string="${signature_base_string}$(pct_encode "${base_url}")"
# 出力文字列に「&」を追加します。
signature_base_string="${signature_base_string}&"
# パラメーター文字列をパーセントエンコードし、出力文字列に追加します。
signature_base_string="${signature_base_string}$(pct_encode "${parameter_string}")"
###
# 署名キーの取得
###
signature_key="$(pct_encode "${CONSUMER_SECRET_KEY:?CONSUMER_SECRET_KEY not found}")&$(pct_encode "${OAUTH_TOKEN_SECRET}")"
###
# 署名の計算
###
signature="$(echo -n "${signature_base_string}" | openssl sha1 -hmac "${signature_key}" -binary | base64 | tr -d '\n')"
echo "${signature}" #作成した署名を返す
署名作成に利用するパラメータを格納する連想配列signature_params
を定義し、すべてのリクエストで固定値となるoauth_consumer_key
, oauth_signature_method
, oauth_version
はハードコーディング、リクエスト毎に異なるものは引数で受け付けるようにしています。
引数はロングオプションのオプション名としてhttp_method
, base_url
, もしくはそれ以外のパラメータ(oauth_xx
)が指定され、http_method
, base_url
の場合はそれぞれの変数、パラメータの場合はオプション名をkey、設定値をvalueとして連想配列signature_params
へ格納しています。
それ以降の処理はこちらの手順を愚直に実装したものです。手順のうちパラメータのリストのソートについては、bashの連想配列はソートできないため、キー一覧を取得し、それをソートした順にアクセスすることで処理を代替しています。なお、OAUTH_TOKEN_SECRET
が定義されていない場合でもエラーとしてはいませんが、これはリクエストトークンの取得など、OAuthトークンシークレットを使用しないリクエストが存在するためです(今回のPINコード認証でも使用しません)。
Authorizationヘッダ文字列の作成
作成した署名、および他のパラメータを基にリクエストのAuthorizationヘッダへ付与する文字列を作成します。手順はこちら。
実装したコード
ヘッダ作成の処理を複数のリクエストで共通的に利用できるよう、mkauthorization.sh
というコマンドへ切り出しています。
#!/bin/bash
# shellcheck disable=SC2034
################
# リクエストの認証
################
###
# パラメーターの収集
###
declare -A auth_params=(
["oauth_consumer_key"]="${CONSUMER_KEY:?CONSUMER_KEY not found}"
["oauth_signature_method"]="HMAC-SHA1"
["oauth_version"]="1.0"
)
while getopts :-: opt; do
optarg="${!OPTIND}"
[[ "$opt" = - ]] && opt="-$OPTARG"
case "-$opt" in
--*)
auth_params["${opt##-}"]="$optarg"
shift
;;
esac
done
###
# ヘッダー文字列の構築
###
dst=""
# 文字列「OAuth 」(最後のスペースを含む)をDSTに追加します。
dst="${dst}OAuth "
# 上述の7つのパラメーターの各キー/値のペアについて:
for key in ${!auth_params[*]}; do
# キーをパーセントエンコードしてDSTに追加します。
dst="${dst}$(pct_encode "${key}")"
# 等号文字「=」をDSTに追加します。
dst="${dst}="
# 二重引用符「”」をDSTに追加します。
dst="${dst}\""
# 値をパーセントエンコードしてDSTに追加します。
dst="${dst}$(pct_encode "${auth_params[${key}]}")"
# 二重引用符「”」をDSTに追加します。
dst="${dst}\""
# キーと値のペアが残っている場合は、コンマ「,」とスペース「 」をDSTに追加します。
dst="${dst}, "
done
dst=${dst%, } #末尾の不要な", "を取り除く
echo "${dst}" #作成したヘッダー文字列を返す
中の作りは前述したmksignature.sh
とほぼ同じで、ヘッダ作成に利用するパラメータを格納する連想配列params
を定義し、すべてのリクエストで固定値となるoauth_consumer_key
, oauth_signature_method
, oauth_version
はハードコーディング、リクエスト毎に異なるものは引数で受け付け、オプション名をkey、設定値をvalueとして連想配列params
へ格納します。
実際にヘッダを作成する処理もこちらの手順を愚直に実装したもので、特に特筆すべきことはありません。
PINコード認証の手順
こちらの手順を順に実施していきます。ここに書かれている通り、手順は3レッグ認証やTwitterでログインの手順とほぼ同じです。
手順1 : リクエストトークンの取得
まずはリクエストトークンを取得します。リクエストトークンとはPINコード認証(や他の認証方法)を実施するために必要となるコードであり、oauth/request_token へリクエストをpostすることで取得します。
署名の作成
署名の作成では下記のHTTPメソッド、ベースURL、およびパラメータ群を用います。
-
HTTPメソッド
POST -
パラメータ群
パラメータ名 設定値 oauth_consumer_key (アプリ固有の文字列) oauth_signature_method "HMAC-SHA1" oauth_version "1.0" oauth_nonce (任意) oauth_timestamp (任意) oauth_callback "oob" -
oauth_consumer_key
Twitter Developper Portalで取得する、アプリケーションを識別する一意の文字列となります。
-
oauth_signature_method
Twitter APIでは
"HMAC-SHA1"
の固定文字列となります。 -
oauth_version
Twitter APIでは
"1.0"
の固定文字列となります。 -
oauth_nonce
前述した
oauth_nonce
パラメータです。 -
oauth_timestamp
前述した
oauth_timestamp
パラメータです。 -
oauth_callback
3レッグ認証やTwitterでログインとは異なり、PINコード認証では
oauth_callback
は固定文字列"oob"
となります。
-
Authorizationヘッダ文字列の作成
リクエストトークンの取得において、Authorizationヘッダ文字列の作成では下記のパラメータ群を用います。oauth_signature
以外は前述の署名の作成と同じです。
-
パラメータ群
パラメータ名 設定値 oauth_consumer_key (アプリ固有の文字列) oauth_signature_method "HMAC-SHA1" oauth_version "1.0" oauth_nonce (任意) oauth_timestamp (任意) oauth_signature 署名の作成で作成した署名
実装したコード
以上より、実際にリクエストトークンの取得の処理の実装は下記のようになります。
http_method="POST"
base_url="https://api.twitter.com/oauth/request_token"
oauth_nonce="$(generate_nonce)"
oauth_timestamp="$(generate_timestamp)"
oauth_callback="oob"
# 署名の作成
oauth_signature="$(./mksignature.sh \
--oauth_nonce "${oauth_nonce}" \
--oauth_timestamp "${oauth_timestamp}" \
--oauth_callback "${oauth_callback}" \
--http_method "${http_method}" \
--base_url "${base_url}" \
)"
# リクエストの認証
authorization_header="$(./mkauthorization.sh \
--oauth_nonce "${oauth_nonce}" \
--oauth_timestamp "${oauth_timestamp}" \
--oauth_signature "${oauth_signature}" \
)"
request_token="$(curl -s \
--request "${http_method}" \
--url "${base_url}?oauth_callback=$(pct_encode "${oauth_callback}")" \
--header "Authorization: ${authorization_header}" | sed -r 's/^oauth_token=([^&]*).*$/\1/g')"
処理内容は、各種パラメータを定義したのち、前述のmksignature.sh
とmkauthorization.sh
で署名とAuthorizationヘッダの文字列を作成し、curlでAPIを叩いています。
レスポンスはauth_token=XXX&oauth_token_secret=YYY&oauth_callback_confirmed=true
のような文字列で帰ってくるため、そこからリクエストトークンだけを取得しています(リクエストトークンシークレットは使用しません)。
手順2 : PINコードの取得の取得
手順1で取得したリクエストトークンを用いてPINコードを取得します。この手順ではブラウザでのログインが必要となるため、前述したとおりテキストブラウザであるlynx
を用いています。
実装したコード
lynx "https://api.twitter.com/oauth/authenticate?oauth_token=${request_token}"
read -r -p "Input the pin code : " pincode
lynxでhttps://api.twitter.com/oauth/authenticate
へアクセスし、取得したPINコードを入力として受け付けるようにしています。
手順3 : アクセストークンの取得
最後に、認証を行いアクセストークンを取得します。
署名の作成
署名の作成では下記のHTTPメソッド、ベースURL、およびパラメータ群を用います。oauth_consumer_key
, oauth_signature_method
, oauth_version
は固定であるため手順1と同じです。
-
HTTPメソッド
POST -
パラメータ群
パラメータ名 設定値 oauth_consumer_key (アプリ固有の文字列) oauth_signature_method "HMAC-SHA1" oauth_version "1.0" oauth_nonce (任意) oauth_timestamp (任意) oauth_token 手順1で取得したリクエストトークン oauth_verifier 手順2で取得したPINコード
Authorizationヘッダ文字列の作成
リクエストトークンの取得において、Authorizationヘッダ文字列の作成では下記のパラメータ群を用います。oauth_signature
以外は前述の署名の作成と同じです。
-
パラメータ群
パラメータ名 設定値 oauth_consumer_key (アプリ固有の文字列) oauth_signature_method "HMAC-SHA1" oauth_version "1.0" oauth_nonce (任意) oauth_timestamp (任意) oauth_signature 署名の作成で作成した署名
実装したコード
以上より、実際のアクセストークンの取得の処理の実装は下記のようになります。
http_method="POST"
base_url="https://api.twitter.com/oauth/access_token"
oauth_nonce="$(generate_nonce)"
oauth_timestamp="$(generate_timestamp)"
# 署名の作成
oauth_signature="$(./mksignature.sh \
--oauth_nonce "${oauth_nonce}" \
--oauth_timestamp "${oauth_timestamp}" \
--oauth_token "${request_token}" \
--oauth_verifier "${pincode}" \
--http_method "${http_method}" \
--base_url "${base_url}" \
)"
# リクエストの認証
authorization_header="$(./mkauthorization.sh \
--oauth_nonce "${oauth_nonce}" \
--oauth_timestamp "${oauth_timestamp}" \
--oauth_signature "${oauth_signature}" \
)"
res="$(curl -s \
--request "${http_method}" \
--url "${base_url}?oauth_token=$(pct_encode "${request_token}")&oauth_verifier=$(pct_encode "${pincode}")" \
--header "Authorization: ${authorization_header}")"
oauth_token="$(echo "${res}" | cut -d "&" -f 1 | cut -d "=" -f 2)"
oauth_token_secret="$(echo "${res}" | cut -d "&" -f 2 | cut -d "=" -f 2)"
###
# 結果表示
###
echo "oauth_token : ${oauth_token}"
echo "oauth_token_secret : ${oauth_token_secret}"
処理内容はstep1と同じで、各種パラメータを定義したのち、前述のmksignature.sh
とmkauthorization.sh
で署名とAuthorizationヘッダの文字列を作成し、curlでAPIを叩いています。
レスポンスはoauth_token=XXX&oauth_token_secret=YYY&user_id=12345678&screen_name=ZZZ
のような文字列で帰ってくるため、そこからアクセストークンとアクセストークンシークレットだけを取得し、最後のそれらを表示して終了です。
最後に
本稿は以上になります。どなたかの参考になれば幸いです。
参考にさせていただいた記事など