Ruby on Rails での認証(Basic、Digest)

Ruby on Rails での認証について調べたので、順番に実装を試してみる。

前提条件

認証について作成する前にscaffold でRecordsテーブルと各コントローラを作っておいた。

1
2
rails g scaffold Resords title:string body:string level:integer
rails db:migrate

Basic認証

ActionController::HttpAuthentication::Basic
を参考にする。
コントローラで定義する。

1
2
3
class RecordsController < ApplicationController
http_basic_authenticate_with name: "ID", password: "p@ssw0rd"
#以下省略

先頭でhttp_basic_authenticate_withを定義する。
定義することでアクセスしたときに、以下のようにIDとパスワードを要求するようになる。

認証を要求しない画面例えば、一覧・個別の表示するだけならば、以下のようにexcept:を定義する。

[アプリケーションルート]\app\controllers\records_controller.rb
1
2
3
class RecordsController < ApplicationController
http_basic_authenticate_with name: "ID", password: "p@ssw0rd", except: [:index,:show]
#以下省略

ここまでの場合、認証をRecordsControllerに付けているので、
他のコントローラーを作ったらまた別途実装が必要になる。
なので、すべてのコントローラの継承元に認証を実装する。

[アプリケーションルート]\app\controllers\application_controller.rb
1
2
3
class ApplicationController < ActionController::Base
http_basic_authenticate_with name: "ID", password: "p@ssw0rd"
end

これは動作しない。

こちらは動作する。

[アプリケーションルート]\app\controllers\application_controller.rb
1
2
3
4
5
6
7
8
9
class ApplicationController < ActionController::Base
before_action :basic_outh
private
def basic_outh
authenticate_or_request_with_http_basic do|name,password|
name=="ID" && password=="p@ssw0rd"
end
end
end

IDとパスワードに誤った入力をすれば、何度も要求を繰り返す。

authenticate_or_request_with_http_basicをputsで出力して確認したところ、
認証の結果はtrueもしくはHTTP Basic: Access denied.を返していた。
(返り値型くらいは統一してほしいと思った。)

なので、認証のどこかへリダイレクトさせるとするなら、
以下のようにできるかと思った。

[アプリケーションルート]\app\controllers\application_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
class ApplicationController < ActionController::Base
before_action :basic_outh
private
def basic_outh
result = authenticate_or_request_with_http_basic do|name,password|
name=="dx" && password=="xza"
end
if !(result.is_a?(TrueClass))
redirect_to 'https://google.com'
end
end
end

が、しかし動かない。

以下のようにすることで動かすことができたが、
一度認証エラーを起こすとサーバーとブラウザ再起動するまで、二度ログインできなくなった・・・。

[アプリケーションルート]\app\controllers\application_controller.rb
1
2
3
4
5
6
7
8
9
10
class ApplicationController < ActionController::Base
before_action :basic_outh
private
def basic_outh
authenticate_or_request_with_http_basic do|name,password|
if !(name=="ID" && password=="p@ssw0rd")
redirect_to "/"
end
end
end

これまでの内容をふまえて、以下を実現する。

  • 複数のコントローラでも認証できるように定義
  • 特定のページでは認証要求しない
[アプリケーションルート]\app\controllers\application_controller.rb
1
2
3
4
5
6
7
8
class ApplicationController < ActionController::Base
private
def basic_outh
authenticate_or_request_with_http_basic do|name,password|
name=="ID" && password=="p@ssw0rd"
end
end
end
[アプリケーションルート]\app\controllers\records_controller.rb
1
2
3
class RecordsController < ApplicationController
before_action :basic_outh ,except: [:index,:show]
#以下省略

RecordsControllerApplicationControllerの継承で、
basic_outhApplicationControllerで定義されているので、
RecordsControllerからbasic_outhが呼び出しできる。

Digest認証

ActionController::HttpAuthentication::Digest](https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Digest.html)
を参考にする。
コントローラで定義する。

Basic認証で学んだことも踏まえて、application_controller.rbで認証部分を作成
records_controller.rbで割り当てだけする。
以下の通り

[アプリケーションルート]\app\controllers\application_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'digest/md5'
class ApplicationController < ActionController::Base

REALM="zxcvbn"
USERLIST={"ID" => Digest::MD5.hexdigest(["ID",REALM,"p@ssw0rd"].join(":"))}

private
def digest_outh
puts "outh"
authenticate_or_request_with_http_digest(REALM) do |name|
USERLIST[name]
end
end
end

[アプリケーションルート]\app\controllers\records_controller.rb
1
2
3
class RecordsController < ApplicationController
before_action :digest_outh,except: [:index,:show]
#以下省略

以上でDigest認証が作成できたけど、気になる点がいくつかあるので試す

ハッシュ値の作成関数

以下の部分でMD5以外を当てたらどうなるか?

1
2
3
4
5
USERLIST={"ID" => Digest::MD5.hexdigest(["ID",REALM,"p@ssw0rd"].join(":"))}
#例えば sha1にしてみる
USERLIST={"ID" => Digest::SHA1.hexdigest(["ID",REALM,"p@ssw0rd"].join(":"))}
#例えば sha256にしてみる
USERLIST={"ID" => Digest::SHA256.hexdigest(["ID",REALM,"p@ssw0rd"].join(":"))}

この点を試すために、digest_outhの処理で、辞書の検索結果をコンソールに出力するようにしてみる。

1
2
3
4
5
6
7
def digest_outh
puts "outh"
authenticate_or_request_with_http_digest(REALM) do |name|
puts USERLIST[name]
USERLIST[name]
end
end

上記の実装をしてブラウザからアクセスしユーザー名をIDとして認証するとすると、コンソールに値が表示される。
MD5のとき値はe1d7859d8d534102a331e11b9fb694b1
SHA1のとき57df5d040ef136547a1c3f6e38af31c23b0a8903
SHA256なら9e4e04ad705cc37a33db266460d4c039e73beb1cc2997784c8e3699c80599c08
より、長く複雑な文字列生成していることを確認できた。

ハッシュ値の作成の種部分

ハッシュ値作成の種が["ID",REALM,"p@ssw0rd"].join(":")となっているけど
別にパスワード文字列でもできる

1
2
3
USERLIST={"ID" => Digest::MD5.hexdigest(["ID",REALM,"p@ssw0rd"].join(":"))}
#例えば sha1にしてみる
USERLIST={"ID" => Digest::MD5.hexdigest("p@ssw0rd")}

この時生成されている値は0f359740bd1cda994f8b55330c86d845になる。

以上2件を試したが これらは無駄である

  • Digest認証とはユーザー名とパスワードをMD5でハッシュ化して送るものなので、
    やったところで、送られたものとは一致しないので認証できない。
  • 種部分をパスワードのみにしたが、
    クライアントは[ユーザー名]:[realm]:[パスワード]のようにそれぞれを:でつないだ文字列をMD5でハッシュ化したものを送る仕様だから認証できない。
    realmには今回zxcvbnを使用したけど、RFC2617では例として”myhost@testrealm.com“の利用を示していた。

無駄ではあったかもしれないが勉強になった

これで認証はできるんだけど、ログイン・ログアウトの仕組みにはならないので、まだまだやることがある。

ではでは。