A note of a person who is learning programming, SakaTaQ

ロック好きのプログラミング学習

BASIC認証の導入

アプリケーションをデプロイした場合、基本的に世界中のどのユーザーもログインできる状態になっている。
成果物として、他のサイトの模写サイトなどのようなクローンサイトを作成してアップロードなどをおこなった場合に、不特定多数のユーザーが誤解しないように閲覧を制限できる機能BASIC認証がある。これをRuby on Railsで導入する。

BASIC認証機能

HTTP通信規格に備わっているユーザー認証の仕組みの一つ。
ユーザー名とパスワードを設定しておき、それを知っているユーザーのみが閲覧、利用できる状態にする。
知らない場合でもアクセス自体は可能だが、ポップアップウィンドウで表示される認証項目を入力しないと画面は表示されない。

導入方法

導入するにあたって特に必要なgemなどは存在しない。
Railsには元々BASIC認証機能を導入するためのメソッドauthenticate_or_request_with_http_basicが用意されているのでそちらを利用する。

RailsアプリケーションでのBASIC認証機能を用いたログインは全てのアクセス(コントローラー)で行われるのが望ましい。
その為、app/controllers/application_controller.rbのprivateメソッドにて定義し、before_actionで呼び出すことで、上記の要件を満たすことができる。

class ApplicationController < ActionController::Base
  before_action :basic_auth

  private

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == 'admin' && password == '2222'
    end
  end
end

application_controllerはこのコントローラーを継承している全てのコントローラーの処理が実行される前に呼び出される。
privateメソッドは呼び出された場合のみ、読み込みや処理がされる。
before_actionとしておくことで、このコントローラーが動作した場合に、basic_authメソッドが実行され、BASIC認証機能が働く。
このコードではユーザー名がadmin、パスワードは2222となっている。
これらを入力することでアプリケーションを利用することが可能。

問題点

HerokuやAWSなどのサーバーにアクセスする際に利用するパスワードや、PAY.JPなどの公開鍵•秘密鍵なども含め、基本的にこういった個人情報に繋がるような秘匿すべき情報はコードに記述することは危険。
特に今回のようにGitHubなどでOSSとして公開されている場合(Public repository)は、こういった情報は誰でも見ることができるようになってしまう。
その為、こういった情報は環境変数を利用した実装に切り替える必要がある。

また、この実装のままでは全ての環境でこのメソッドが実行されてしまう。
公開前である開発、テスト環境の時点でこの処理が実行されると非常に煩わしくなる為、本番環境(デプロイ後)のみで実行されるように記述を変えていく。
(環境変数は開発環境、テスト環境、本番環境ごとに別途設定する必要がある。
開発、テストなどのローカル環境であれば、自身のPC~/.zshrcに、本番環境であればEC2やHerokuなどのリモートマシン上の環境設定ファイルに設定する必要がある)

% vim ~/.zshrc

vimモードで.zshrcを開いて、インサートモードで書き込み(追記)を行っていく。
※ Mojave(旧Macバージョン)の場合はファイル名は.bash_profileなので注意。ファイルの格納場所は同じ。

export BASIC_AUTH_USER='admin'
export BASIC_AUTH_PASSWORD='2222'

追記が完了したら「esc」を押してコマンドモードに戻し、「:wq」で保存しながら終了する。
環境変数などの設定を変更した場合、すぐに適用されない為再読み込みコマンドを実行する。

% source ~/.zshrc

続いて本番環境の設定を行っていく。

# AWSなどを用いてデプロイする場合
% ssh -i [ダウンロードした鍵の名前].pem ec2-user@[作成したEC2インスタンスに紐づいたElastic IP]
例)% ssh -i adminman.pem ec2-user@111.222.333.444 のような形
% sudo vim /etc/environment

# ファイルを開いたらiでインサートモードにして環境設定ファイルに「追記」を行う。
BASIC_AUTH_USER='admin'
BASIC_AUTH_PASSWORD='2222'

# esc→:wqで保存終了した後に設定を適用する為、一度リモートマシンからログアウトし再度ログイン
% exit
% ssh -i [ダウンロードした鍵の名前].pem ec2-user@[作成したEC2インスタンスに紐づいたElastic IP]

# 設定した環境変数を確認するためのコマンド
% env | grep BASIC_AUTH_USER
% env | grep BASIC_AUTH_PASSWORD


# Herokuを用いてデプロイする場合
# % heroku config:set KEY=VALUE の形

% heroku login

% heroku config:set BASIC_AUTH_NAME='admin'
% heroku config:set BASIC_AUTH_PASSWORD='2222'

AWSでの場合の1行目は、sshを利用した接続で、ダウンロードした鍵を使用してEC2にログインしている。
ssh接続では通信が暗号化されているので安全に通信できる。また、公開鍵と秘密鍵を利用した鍵認証を利用することでより安全に接続を行うことも可能。
-iオプションは秘密鍵である(identityファイル)を指定している。この時、AWSの設定時にダウンロードしたpemキーを記述。 ログインできたら2行目のコマンドでリモートマシン上の環境変数を設定するファイルに書き込みを行うためにsudo(スーパーユーザー)vimモードにて開いている。
ファイルを開くことができたら、元々書いてある記述などを消さないように注意。
環境設定を適用するために再度ログインを行い、env | grepコマンドにて設定した環境変数をの確認を行っている。
なお、通常は本番環境のリモートマシンでも~/.zshrcがあるのでそちらに記述を行えば環境変数の使用は可能だが、Capistranoで自動デプロイをする際には本番環境にある~/.zshrcを参照しないようにプログラムが実行(ユーザーのログインシェルが割り当てられない)されるため、/etc/envirnmentに定義する必要がある為この方法を採った。

~/.zshrc環境変数を記述した場合は、config/deploy.rbにて

set :default_env, {
  rbenv_root: "/usr/local/rbenv",
  path: "/usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH",
  BASIC_AUTH_USER: ENV["BASIC_AUTH_USER"]
  BASIC_AUTH_PASSWORD: ENV["BASIC_AUTH_PASSWORD"]
}

のように記述してどの環境変数を使用するか明示する必要がある。 他にもcredentials.yml.encとかにも色々と書く必要があるのであれば、最初の方法を採った方が導入しやすそうです。

Herokuでの場合、スペースで区切れば複数の環境変数を同時に設定することが可能。
heroku config:set BASIC_AUTH_NAME='admin' BASIC_AUTH_PASSWORD='2222'
環境変数特殊文字などが入っている場合はシングルクォーテーションで囲むこと。
どう設定されたかを確認するにはheroku configでターミナル上で見ることができる。

ソースコード変更

# controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :basic_auth
  protect_from_forgery with: :exception

  private

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]
    end
  end
end

ENV["BASIC_AUTH_USER"]のような変数を環境変数と呼ぶそうです。
環境設定ファイルに定義されてるものはこういった呼び方になるって認識。

本番環境のみBASIC認証を行う

Herokuでのデプロイの場合は
username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"] if Rails.env.production?
に書き直すだけで問題なく導入できるようです。

AWS(with Capistrano)の場合は

# config/deploy/production.rb の10行目付近にあるコメントアウト下に追記
server "(EC2のIPアドレス)", user: "ec2-user", roles: %w{app db web}
例)server "111.222.333.444", user: "ec2-user", roles: %w{app db web}

set :rails_env, "production"
set :unicorn_rack_env, "production"

# role-based syntax
# ==================

この記述でUnicornが現在の環境を本番環境として認識する。application_controllerも少し記述を加える。

class ApplicationController < ActionController::Base
  before_action :basic_auth, if: :production?
  protect_from_forgery with: :exception

  private

  def production?
    Rails.env.production?
  end

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]
    end
  end
end

production?というメソッドを定義し、現在の環境を条件分岐にてtrueかfalseを返すようにしたもの(Rails.env.production?)を、before_action :basic_authの後にif: :production?と記述することにより、本番環境のみでbasic_authメソッドが実行されるようにしている。
なお、この記述は前述したHerokuデプロイ時の記述と動作としては差はない。

BASIC認証の動作確認

※ 初めてcapistranoで自動デプロイをしてBASIC認証の動作確認をする場合のみ、unicornの停止と起動も行ってから動作確認をする。

% bundle exec cap production deploy unicorn:stop
(デプロイ完了まで待ち、完了後に以下を実行)

% bundle exec cap production deploy unicorn:start

うまく起動できない場合にはAWSのEC2コンソールを再起動し、njinx MySQL Unicornを手動で順番に再起動させる。 その際Unicorn

$ cd /var/www/アプリケーション名/current
$ bundle exec unicorn -c /var/www/アプリケーション名/current/config/unicorn.rb -E production -D

のように起動する。

手動、もしくはcapistranoを用いた自動デプロイを行ってアクセスできる状態を整えたら動作確認を行う。

参考にさせていただいた記事
今更だけどRailsアプリケーションにBasic認証を設定した
[Heroku] ReactのアプリのBASIC認証の設定方法
Rails.env


今回はここまで。

デプロイ時に使用するサーバーによって設定する量が少し違うので全体的にごちゃっとした感じが否めませんが...

BASIC認証は導入はしやすいけど完全に信頼における認証方式ではなく、「認証した際の通信経路でユーザー名とパスワードが取得可能」だったり、ログアウト機能は自身で実装する必要がある。
また、複数のサーバーを跨いだ認証は難しくなるようです。
絶対に安全なものとは言い難いけど、最低限の認証機能を採用したい場合の選択肢の一つとして導入する、というくらいの認識の方が望ましいようですね。

したらな❗️ 👋