君の瞳はまるでルビー - Ruby 関連まとめサイト

Sinatra で Rack::Auth を使って、特定のページに Basic 認証を設置する。

最終更新: 2015-03-31 (火) 21:53:21 (2274d)

通常 Rack::Auth を使った場合の認証の範囲は、Web アプリケーション全体です。

Rack::Auth を使ったサンプルは以下を参照してください。

ただし、それは Rack Middleware として使った場合です。

Rack Middleware は、Rack アプリケーションそのものと同じ構造をしているので、部品として利用可能でもあります。

その性質を利用して、Rack::Auth::Basic を部品として用いることで、特定のページにアクセスした場合のみ Basic 認証を要求させる方法があります。

サンプル

app.rb

# coding: UTF-8

require 'sinatra'

helpers do
  def authenticate
    auth = Rack::Auth::Basic.new(Proc.new {}) do |username, password|
      username = 'test' && password = 'test'
    end
    return auth.call(request.env)
  end
end

get '/' do
  'Welcome top page.'
end

get '/secret' do
  not_authentication = authenticate
  return not_authentication if not_authentication

  'Welcome to a secret page, #{env['REMOTE_USER']}!'
end

解説

サンプルを動かすには、以下のコマンドを実行してください。

ruby app.rb

もちろん、事前に Sinatra のインストールが必要です。

サンプルは、

  • / にアクセスした場合、認証を必要としません。
  • /secret にアクセスした場合、認証を必要とします。

実現方法と、その仕組みについて説明します。

helpers で authenticate メソッドを定義し、認証機能を持つ Rack::Auth を部品として利用した認証機能を Web アプリケーション全体で利用できるようにします。

use Rack::Auth::Basic のブロック内で、ユーザ名とパスワードの2つの引数を受け取り、ユーザの認証判断を行うのは通常の Rack::Auth での認証と同じです。

通常の使い方では Rack::Auth は Rack アプリケーションの外側に位置し、Rack アプリケーション全体を包み込んで、認証に成功した場合のみ Rack アプリケーションにリクエストを委譲し、認証に失敗した場合は Rack アプリケーションへリクエストを渡しません。Rack::Auth は、独自に認証失敗の応答を返します。

Rack::Auth を使う場合、以下のような構成になるのが通常です。

  • Rack::Auth::Basic(Rack Middleware)
    • Rack アプリケーション

これは Rack Middleware である Rack::Auth の標準的な使い方です。

Rack::AuthRack Middleware ではなく、単なる部品として利用するため、この構成を逆します。

ただし、Rack::AuthRack Middleware なので、 Rack アプリケーション相当のものを必ず包み込ませなければなりません。

そこで Rack アプリケーションの代わりとして Proc オブジェクトを使います。 Proc オブジェクトは Rack アプリケーションと同じく call というメソッドを持っています。 これを使って Rack Middleware である Rack::Auth を騙して、以下のような構成を作ります。

  • Rack アプリケーション
    • Rack::Auth(部品)
      • Proc(Rack アプリケーションの代替)

Rack::Auth は認証に成功した場合、包み込んでいる内部の Rack アプリケーションの応答を返し、認証に失敗した場合は Rack::Auth が独自に作った失敗の応答を返します。

この場合、Rack アプリケーション相当の Proc.new {} は何も返しません。

その性質を利用し、認証失敗の応答が返ってきた場合のみ、認証失敗の応答を即時に返すよう以下の記述を使います。

not_authentication = authenticate
return not_authentication if not_authentication  # 認証に失敗した場合、Rack::Auth が作った認証失敗の応答を返す。

# 以降は認証に成功した場合の処理 ...

備考

このサンプルは、以下のブログ記事に着想を得ています。

以上のブログ記事はさらに Sinatra メインサイトの Sinatra: Frequently Asked Questions なる以下の項目を参考にしているようです。

このページのサンプルは、以上のページで紹介しているサンプルを少し改良したものです。

以上のページで紹介している方法では、認証失敗時の判断/応答を以下のように自作しています。

# 認証チェック
@auth ||=  Rack::Auth::Basic::Request.new(request.env)
unless  @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials[0] =~ /(foo|var)/
  response['WWW-Authenticate'] = %(Basic realm="Testing HTTP Auth")
  throw(:halt, [401, "Not authorized\n"])
end

少々難解で何をしているのかわかりづらいです。そこで調べて見たところ、つまるところその内容は Rack::Auth の内部に実装されている内容の焼き直しだとわかりました。

単に Rack::Auth 内部の焼き直しならば Rack::Auth をそのまま部品として使った方がわかりやすいのではないかという考えでこのサンプルを作りました。

このサンプルも Proc を使うというちょっとしたマジックを使ってしまっている部分があります。正等に以下のような空の Web アプリケーションを作ろうか、

class EmptyApp
  def call(env)
  end
end

それとも call メソッドを持った無名クラスを一行で書けないか四苦八苦したのですが、 Proc を使って1発で済むのであれば、マジックを1つぐらい入れも許容範囲かなということで今のサンプルで収束させることにしました。

認証チェックの複雑なマジックの意味合いに悩むよりは、Rack::Auth の原型も留めていますし、 このページで紹介している方法をお勧めします。

注意

ログインユーザ名を表す環境変数 env['REMOTE_USER'] は、Rack::Auth によって認証を通したリクエストに対しての設定されます。

Rack::Auth は、リクエスト単位の認証であり、ログインユーザの情報をセッションに格納するなどの高度な機能は持ち合わせていません。

その点注意が必要です。

従って、ログインユーザ情報が必要なアクセスには以下の認証コードを必ず書くようにしてください。

not_authentication = authenticate
return not_authentication if not_authentication

それか、より高度な Web アプリケーション認証技術を使うことをお勧めします。

コメント

本ページの内容に関して何かコメントがある方は、以下に記入してください。

コメントはありません。 コメント/sinatra/use/rack_auth

お名前: