Go
golang
OAuth
TwitterAPI

Golangで(ライブラリがあるのを知らずに)手でOAuthしようとして頓挫した話

はじめに

この記事は やってみたけどダメだったコードを供養するために書かれています
この通りにやっても、実際にTwitterのアクセストークンを取得することはできないので、 間違ってもコピペしないでください

Twitterアクセストークンを取得するのに便利なライブラリがあるので、そちらを利用することをおすすめします。(頓挫した後、自分はこれを使いました。)

==> https://github.com/mrjones/oauth

これを使った実装方法として、 skratchdot/open-golangでOAuthで返ってきたURLを自動的に開くようにする実装方法を考えています。実装できればまた記事にします。(2017/12/28 現在)

経緯

なぜTwitterAPI OAuth認証を手で書いてしまったのかについて軽く経緯を説明しておきます。

えもともと、CLIからTwitterに投稿できるアプリケーションを作ろうと思っていたため、コールバックを使うやり方は使えないことがわかっていました。

そのため、別の方法を探していたところ、PINベース認証というやり方を見つけました。

しかし、それを実行するためには、コールバックの値を "oob" にしないと行けないということを知り、通常のOAuth認証ライブラリではできないと勘違いしてしまっていました。(第一のミス)

そのため、TWitter RESTAPI で検索して出てきた、Syncerの記事を読みながらスクラッチで実装する形を取り、現在に至っています…(第二のミス…?)

詰まったところ

そもそもOAuthについてわかっていない

自分が経験してきたOAuthはRubyのgemを使ってWeb画面ありきのOAuth実装をすることしか経験していなかったため、 Resourceに認証をリクエストしてアクセストークンをもらってそのアクセストークンをResoucreに渡してデータをもらえるようにする 程度のざっくりとした知識しか持っていませんでした。

そのため、 TwitterAPIキー・TwitterSecretキーとデータででシグネチャを作って送りOAuth tokenを受取って送りアクセストークンを受け取る という流れについて理解するのにかなりの時間を費やしてしまいました。

しかしながら、まるっとさらったおかげでOAuthについての知識はかなりつきました…
これは一応の収穫です。

HMAC-SHA1などの暗号方式に明るくない

これもそもそもわかっていなかったのが問題で、HMACとSHA1がそれぞれ何なのかとか、何を使って何を作るのかなどをさらうのにだいぶと時間がかかってしまいました…

実行したけどなんかあかんわ!というエラーだけが返ってくる

自分はこれで萎えてしまいました。
ここにぶち当たるまでにかれこれ累計18時間ほど溶かしてしまっており、その状態で色々試しても {"errors":[{"code":32,"message":"Could not authenticate you."}]} とだけ返ってくるAPIに対してブチ切れてしまいました。

どこが悪いのかざっくりでも察することができるエラーメッセージならば試行錯誤する気も起きるのですが、そんなこともなくひたすらに「認証できませんでした」としか返ってこないAPIと格闘するのがアホらしくなってしまい、現在に至ります。

もし何か下記のコードを見て気づいた点などあれば、教えていただけると今後の勉強のためにとても助かります。

よろしくお願いします。

書いていたコード

もし何かお気づきの点があれば、コメントか Twitter(ahaha0807_alg) までよろしくおねがいします!

main.go
func getRequestToken() string {
    baseUrl := "https://api.twitter.com"
    endPoint := "/oauth/request_token"

    requestUrl := baseUrl + endPoint

    consumerKey := os.Getenv("TWITTER_CLI_CONSUMER_KEY")
    consumerSecretKey := os.Getenv("TWITTER_CLI_CONSUMER_SECRET_KEY")

    values := url.Values{}
    values.Add("oauth_callback", "oob")
    values.Add("oauth_consumer_key", consumerKey)
    values.Add("oauth_signature_method", url.QueryEscape("HMAC-SHA1"))
    values.Add("oauth_timestamp", strconv.FormatInt(time.Now().Unix(), 10))
    values.Add("oauth_nonce", strconv.FormatInt(time.Now().Unix()/3, 10))
    values.Add("oauth_version", "1.0")

    signatureParam := values.Encode()
    method := url.QueryEscape("POST")
    escapedRequestUrl := url.QueryEscape(requestUrl)

    signatureKey := url.QueryEscape(consumerKey) + "&" + url.QueryEscape(consumerSecretKey)

    hash := hmac.New(sha1.New, []byte(signatureKey))
    hash.Write([]byte(signatureParam + "&" + method + "&" + escapedRequestUrl))
    signature := url.QueryEscape(base64.StdEncoding.EncodeToString(hash.Sum(nil)))
    fmt.Println(signature)

    values.Add("oauth_signature", signature)

    req, err := http.NewRequest("POST", requestUrl, nil)
    util.Check(err)

    parameter := strings.Replace(values.Encode(), "&", ",", -1)

    fmt.Println(parameter)
    req.Header.Set("Authorization", "OAuth "+parameter)

    fmt.Println(req.Header.Get("Authorization"))

    client := &http.Client{}
    response, err := client.Do(req)
    util.Check(err)
    defer response.Body.Close()

    a, err := ioutil.ReadAll(response.Body)
    util.Check(err)
    fmt.Println(string(a))

    return ""
}