Sinatra + OmniAuth2.0の情報が少ねえ!!
手軽にOAuthの認可サーバー立てようと思ってSinatraを使ったらハマった。 OAuthの知識ほとんどゼロでも動くところまで持っていけたので、今後のためにも対処法をメモしておく。
環境
Ruby 3.2.2
rbenv 1.2.0
Gemバージョン
sinatra (3.2.0) puma (6.4.2) omniauth (2.1.2) omniauth-oauth2 (1.8.0) omniauth-spotify (0.0.13)
参考にした記事
『OmniAuthを使ってみる - Kludge Factory』がめちゃくちゃ参考になった。
2015年の記事ではあるものの、OmniAuthの嬉しさから具体的にどうセットアップするかまで体系的にまとまっていて、かなり助けられた。
ハマりポイント① GETリクエストが通らない
CVE-2015-9284という脆弱性が発見されてから、GETリクエストでアクセスすると404エラーが出る。
get '/' do erb "<a href='/auth/twitter'>ログイン</a><br>" end
みたいにシンプルに書けないので、
get '/' do html = <<-HTML <form action="/auth/twitter" method="POST" enctype="multipart/form-data"> <input type="submit" value="ログイン"> </form> HTML erb html end
って感じでformタグ作ってPOSTリクエストを飛ばすようにする。
参考リンク: https://qiita.com/megane42/items/fc46c26d6fd1187e77c0
ハマりポイント② 404 Forbiddenになる
formでPOST飛ばすように変更してもエラーを吐く。
Puma caught this error: EOFError (EOFError) [MY_PATH]/.bundle/ruby/3.2.0/gems/rack-2.2.8/lib/rack/multipart/parser.rb:373:in `handle_empty_content!' [MY_PATH]/.bundle/ruby/3.2.0/gems/rack-2.2.8/lib/rack/multipart/parser.rb:199:in `on_read' [MY_PATH]/.bundle/ruby/3.2.0/gems/rack-2.2.8/lib/rack/multipart/parser.rb:80:in `block in parse' [MY_PATH]/.bundle/ruby/3.2.0/gems/rack-2.2.8/lib/rack/multipart/parser.rb:78:in `loop' [MY_PATH]/.bundle/ruby/3.2.0/gems/rack-2.2.8/lib/rack/multipart/parser.rb:78:in `parse' [MY_PATH]/.bundle/ruby/3.2.0/gems/rack-2.2.8/lib/rack/multipart.rb:53:in `extract_multipart' [MY_PATH]/.bundle/ruby/3.2.0/gems/rack-2.2.8/lib/rack/request.rb:594:in `parse_multipart' [MY_PATH]/.bundle/ruby/3.2.0/gems/rack-2.2.8/lib/rack/request.rb:446:in `POST'
エラーメッセージ見てもピンとこないのでSinatra側のログを見ると、
W, [2024-01-17T21:52:30.068908 #93108] WARN -- omniauth: Attack prevented by OmniAuth::AuthenticityTokenProtection E, [2024-01-17T21:52:30.068953 #93108] ERROR -- omniauth: (spotify) Authentication failure! authenticity_error: OmniAuth::AuthenticityError, Forbidden E, [2024-01-17T21:52:30.069048 #93108] ERROR -- omniauth: (spotify) Authentication failure! Forbidden: OmniAuth::AuthenticityError, Forbidden
認証でミスってるっぽい。
ググると出てくるのは基本Rails用の対処法(gem 'omniauth-rails_csrf_protection'
を追加する)だけで、Sinatra用のやつが中々見つからない。
あとはprovider_ignores_state: true
をOmniAuth::Builderのところに追加するというのもあったけど、そもそも通らなかったし普通に対処法として良くなさそう。
色々ググると、authenticity_token
を送らないと通らないことがわかった。ので、
get '/' do html = <<-HTML <form action="/auth/twitter" method="POST" enctype="multipart/form-data"> + <input type="hidden" name="authenticity_token" value='#{request.env['rack.session']['csrf']}'> <input type="submit" value="ログイン"> </form> HTML erb html end
inputを追加したら通った。
参考リンク: https://blog.orz.at/2021/04/19/omniauth-twitter-forbidden/
コード全体
OmniAuth::Builderのところはproviderごとに変える。
require 'dotenv' require 'sinatra' require 'sinatra/reloader' require 'omniauth' require 'omniauth-spotify' require 'json' Dotenv.load class SinatraApp < Sinatra::Base register Sinatra::Reloader configure do set :sessions, true set :inline_templates, true end use OmniAuth::Builder do provider :spotify, ENV['APP_TOKEN'], ENV['APP_SECRET_TOKEN'], scope: 'user-read-private playlist-read-private playlist-read-collaborative playlist-modify-public' end get '/' do html = <<-HTML <form action="/auth/spotify" method="POST" enctype="multipart/form-data"> <input type="hidden" name="authenticity_token" value='#{request.env['rack.session']['csrf']}'> <input type="submit"> </form> HTML erb html end get '/auth/:provider/callback' do result = request.env['omniauth.auth'] erb "<pre>#{JSON.pretty_generate(result)}</pre>" end end SinatraApp.run! if __FILE__ == $PROGRAM_NAME