woshidan's blog

そんなことよりコードにダイブ。

OmniAuth(omniauth-twitter)での認証が401 Authorization Required invalid_credentialsで失敗する

OmniAuth系のライブラリでTwitterFacebookなどのサービスと連携しようとしたところ、401 Authorization Required invalid_credentialsでひたすら失敗してつらかったです。

特にTwitterAPIには、401でないエラーを401で返すという話もあり*1、結局関連するソフトウェアや設定を一つずつ検討することになりました。。

これを忘れて繰り返すと非常に辛いので、この場で一つずつ簡潔にまとめておきます。

https://github.com/omniauth/omniauth/wiki/FAQ#how-to-fix-oauthunauthorized-error-for-twitter-provider

検討事項

  • OAuthっぽいこと
    • アカウント情報
    • Twitterアプリの作り直し
    • ブラウザの時刻
  • memcached関連
    • memcachedサーバとの接続
    • provider_ignores_stateでsessionの値を確認を回避
    • memcachedのstats
  • omniauthの設定
    • OmniAuth.config.full_host

OAuthっぽいこと

アカウント情報

Twitterアプリなどで、OAuth用のアプリケーションの権限、利用ドメインなどの設定があればその登録、リダイレクトURI、アプリケーションのIDとシークレットなどの確認をします。

Twitterアプリの作り直し

参考: http://qiita.com/hirokishirai/items/5a43977a38ecd922bfb9

どうしてもうまくいかないときはTwitterアプリケーションを作り直すとうまくいくことがあるそうです。

ここではTwitterアプリの作り直しを試したりしました。

連携先のサービスによってはOAuth用アプリケーションごとに一人のユーザーを異なるアカウントIDで管理していたりするので、新しく連携させるわけでないときは注意しましょう*2

ブラウザの時刻

Twitterでは、0.1秒でもブラウザの時刻がずれていると認証に失敗してしまいます(アプリをエミュなどでテストをする際は割と辛い...)。

こちらのページなどでご利用のパソコンでの時刻にずれがないか確認できます http://www.nict.go.jp/JST/JST5.html

memcached関連

401のエラーを吐いている箇所のコードはこちらのようになっています。

!options.provider_ignores_state && (request.params["state"].to_s.empty? || request.params["state"] != session.delete("omniauth.state"))

の部分を参考にGoogleしたら、sessionの値がTwitterなどからPOSTされるまで、保持されてないのかもしれない、ということになりました。

自分の環境の場合は、sessionの保存にmemcachedを利用していたので、memcachedの確認をしていました。

memcachedサーバとの接続

とりあえず、pingした。

provider_ignores_stateでsessionの値を確認を回避

を参考に request.params["state"] != session.delete("omniauth.state") の判定をそもそも呼ばないようにしてみました。

memcachedのstats

sessionに利用してるmemcachedの利用出来るバイト数などは問題ないか、何かデータがうまく取れてないかなということで、memcachedstatsを確認してみました。

あんまりよくわかっていないので、get_misses, bytes, limit_maxbytes, accepting_conns, threadsなどを見てみたのですが、それらしい違いはなかったです。

omniauthの設定

config/initializers/omniauth.rbなどのinitializersのファイル中の設定を確認しました。

具体的には、

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, Rails.secrets.oauth.twitter["app_id"], Rails.secrets.oauth.twitter["app_secret"]
  provider :facebook, Rails.secrets.oauth.facebook["app_id"], Rails.secrets.oauth.facebook["app_secret"]
en

など(自分で書いておいて、おそらくdotenv、利用して環境変数で渡した方がセキュアでしょう)で書いてあると思うのですが、これが動作環境で所望の値が得られているか、railsコンソールで叩いて確認したりしていました。

結局は、OmniAuth.config.full_hostに設定するホストのスキーマhttpsにしなければいけないところがhttp になっているという話でした。

長々書きましたが、いままでの全部関係なかったのでつらいですね。

SSLに対応している部分と対応してない部分、対応している部分ではhttpsにリダイレクトするような設定なのですっかり頭から抜けていたのですが、リダイレクト先に設定したスキーマに合わせましょう。

デバッグ途中で開発環境で動かしてみたら401にならなかったので、localhostとリモートで何の設定が違うんだ、と眺めていたら発見しました。