0
0

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.

TwitterのPINコード認証をシェルで実施する

Posted at

これは

自身の学習のために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というコマンドへ切り出しています。

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というコマンドへ切り出しています。

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

  • ベースURL
    https://api.twitter.com/oauth/request_token

  • パラメータ群

    パラメータ名 設定値
    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 署名の作成で作成した署名
    • 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.shmkauthorization.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

  • ベースURL
    https://api.twitter.com/oauth/access_token

  • パラメータ群

    パラメータ名 設定値
    oauth_consumer_key (アプリ固有の文字列)
    oauth_signature_method "HMAC-SHA1"
    oauth_version "1.0"
    oauth_nonce (任意)
    oauth_timestamp (任意)
    oauth_token 手順1で取得したリクエストトークン
    oauth_verifier 手順2で取得したPINコード
    • oauth_nonce

      前述したoauth_nonceパラメータです。リクエスト毎に一意の値である必要があるため、手順1と同じものは用いずに新たに生成します。

    • oauth_timestamp

      前述したoauth_timestampパラメータです。リクエストを生成した時刻であるため、手順1と同じものは用いずに新たに生成します。

    • 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 署名の作成で作成した署名
    • 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.shmkauthorization.shで署名とAuthorizationヘッダの文字列を作成し、curlでAPIを叩いています。

レスポンスはoauth_token=XXX&oauth_token_secret=YYY&user_id=12345678&screen_name=ZZZのような文字列で帰ってくるため、そこからアクセストークンとアクセストークンシークレットだけを取得し、最後のそれらを表示して終了です。

最後に

本稿は以上になります。どなたかの参考になれば幸いです。

参考にさせていただいた記事など

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?