hello-world
webエンジニアのメモ。とりあえずやってみる。

[sinatra].htpasswdを使ったbasic認証をやってみる

公開日時

sinatraで作っているアプリにbasic認証をかけようと思って調べていたのですが、公式に載っているサンプルだと一組のid、パスワードしか扱えなかったので、.htpasswdを読み込んで使えるようにできないかやってみました。

# app.rb
require 'sinatra/base'

module BasicSample
  class App < Sinatra::Base
    HTPASSWD_PATH = '.htpasswd'

    helpers do
      def protect!
        unless authorized?
          response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
          throw(:halt, [401, "Not authorized\n"])
        end
      end

      def authorized?
        @auth ||=  Rack::Auth::Basic::Request.new(request.env)
        passwd = File.open(HTPASSWD_PATH).read.split("\n").map {|credential| credential.split(':')}
        if @auth.provided? && @auth.basic? && @auth.credentials
          user, pass = @auth.credentials
          auth = passwd.assoc(user)
          return false unless auth
          # crypt済みパスワードの先頭2文字がsalt
          [user, pass.crypt(auth[1][0..2])] == auth
        end
      end
    end

    get '/' do
      protect!
      "Hello #{@auth.username}!"
    end

    get '/logout' do
      halt 401
    end
  end
end

# config.ru
require './app.rb'

run BasicSample::App

# Gemfile
source "https://rubygems.org"

gem 'sinatra'

これで、

bundle install --path=vendor/bundler
bundle exec rackup config.ru -p 4567

とすれば.htpasswdを読み込んでbasic認証をかけるアプリが起動します。

.htpasswdにfooユーザが存在している場合、認証が通ると「Hello foo!」と表示されます。

authorized?メソッドの中で、.htpasswdファイルの中身を解析して、ユーザが入力したid, パスワードと一致するかチェックしています。

basic認証の場合、一度認証するとブラウザを閉じるまでAuthorizationヘッダを送りつづけるので、/logoutにアクセスすると強制的に401レスポンスを送って、ログアウト状態にするようにしました。

とりあえずは動いたけど、こんな感じでいいんだろうか。

参考


Related #sinatra

railsとsinatraの使い分け

sinatraでアプリを作っていたら、色々機能が足りなかったので継ぎ足し継ぎ足しで色々増やしていったら結局railsのようなもの、になってしまいました。

[sinatra][twitter]screen_nameからユーザidを求めるサンプル

twitterのscreen\_nameを元にユーザidを知りたいと思い調べていたところ、

[sinatra]padrino-helpersを使って自動エスケープ

sinatra + active record を使ったアプリを作っていたらいつの間にかviewが自動でエスケープされるようになっていました。