LoginSignup
1
0

More than 3 years have passed since last update.

既存のHyperstackアプリにAzureADによる認証を導入

Last updated at Posted at 2019-07-06

参照

model名を使うところと、model名に関係なく user とするところがあるので、区別するために model名を Userではなく Agentとして試してみました。

  1. devise gemの導入
  2. omniAuth AzureAD の導入
  3. Login画面を通さず、リンクのクリックでAzureAD認証をさせる

devise gem の導入

devise gemのインストール

Gemfile
gem 'devise'
$ bundle install
$ bundle exec rails generate devise:install

modelの生成 と migrate

$ bundle exec rails generate devise Agent

Running via Spring preloader in process 468
      invoke  active_record
      create    db/migrate/20190622011638_add_devise_to_agents.rb
      insert    app/models/agent.rb
       route  devise_for :agents
$ bundle exec rails db:migrate
== 20190630011828 DeviseCreateAgents: migrating ==
-- create_table(:agents)
   -> 0.0010s
-- add_index(:agents, :reset_password_token, {:unique=>true})
   -> 0.0007s
== 2019063011828 DeviseCreateAgents: migrated (0.0030s) ===

app/models/agent.rb を app/hyperstack/models/agent.rb へ移動

ここまでで、http://localhost:5000/agents/sign_up にサインアップ画面が見られるようになる。

application_controller.rb に、before_action :authenticate_<model名>! を追加

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :authenticate_agent!   # 追加

これで http://localhost:5000 にアクセスすると、まず Log in画面が表示されるようになる。

サインアップしてログインすれば、もとのHyperstackアプリに戻ることができる。
login.png

current_<model名> の aliasとして acting_user を作る

acting_<model名> ではなく acting_user にする

app/controllers/application_controller.rb
def acting_user
  current_agent
end

これで、Hyperstack::Application.acting_user_id で、ログイン中のAgentのidにアクセスできるようになる。

model にクラスメソッド current を定義

app/hyperloop/models/agent.rb
class << self
  def current
    id = Hyperstack::Application.acting_user_id
    id ? find(id) : new
  end

model の devise行に unless RUBY_ENGINE == 'opal' を追加

app/hyperstack/models/agent.rb
class Agent < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable unless RUBY_ENGINE == 'opal'

omniAuth AzureADの導入

先に Azure Portalにアプリを登録してCLIENT IDとTENANT IDを取得しておく

Azureポータル

register.PNG

Azureポータルには、「アプリケーション(クライアントID)」、「ディレクトリ(テナントID)」と書かれている

Screenshot from 2019-07-02 10-08-27.png

"ID Token"をenableにしておく

そうしないと、あとでこのエラーを見ることになる。参考 stackoverflow Q&A

AADSTS700054: response_type 'id_token' is not enabled for the application.

"App registrations"で登録したアプリ名を開いて、左側の「認証」を選択し、「ID トークン」をチェック
IDtoken.PNG

"Redirect URI"に、callbackを設定

http://localhost:5000/agents/auth/azureactivedirectory/callback

redirectURI.PNG

omniauth gem と dotenv-rails のインストール

Gemfile
group :development, :test do
  ...
  gem 'dotenv-rails'
end
...

gem 'omniauth'
gem 'omniauth-azure-activedirectory'
$ bundle install

.env に、Azureポータルから取得した CLIENT ID と TENANT IDを設定しておく

.env
AAD_CLIENT_ID = 'abcdabcd-abcd-abcd-abcd-abcdabcdabcd'
AAD_TENANT    = 'abcdabcd-abcd-abcd-abcd-abcdabcdabcd'

(githubなどにアップするなら .envを .gitignoreに登録しておくのを忘れずに)

config/initializers/devise.rb に 登録

config/initializers/devise.rb
config.omniauth :azure_activedirectory, ENV['AAD_CLIENT_ID'], ENV['AAD_TENANT']

modelに provider, uid カラムを追加

必須ではないが、後で使うために name カラムも追加しておく

$ bundle exec rails generate migration AddColumnsToAgents name:string provider:string uid:string
$ bundle exec rails db:migrate

Deviseにcontrollerを作らせる

$ bundle exec rails g devise:controllers agents
Running via Spring preloader in process 341
      create app/controllers/agents/confirmations_controller.rb
      create app/controllers/agents/passwords_controller.rb
      create app/controllers/agents/registrations_controller.rb
      create app/controllers/agents/sessions_controller.rb
      create app/controllers/agents/unlocks_controller.rb
      create app/controllers/agents/omniauth_callbacks_controller.rb
=========================================================================

Some setup you must do manually if you haven't yet:

  Ensure you have overridden routes for generated controllers in your routes.rb.
  For example:

    Rails.applicationo.routes.draw do
      devise_for :users, controllers: {
        sessions: 'users/sessions'
      }
    end

===========================================

routingを定義

app/config/routes.rb
Rails.application.routes.draw do
  devise_for :agents, controllers: {
    omniauth_callbacks: 'agents/omniauth_callbacks',
    registrations: 'agents/registrations'
  }

  mount Hyperstatck::Engine => '/hyperstack'
  get '/(*other)', to: 'hyperstack#app'
end

Modelの devise行に :omniauthable 等を追加

app/hyperstack/models/agent.rb
class Agent < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :omniauthable, omniauth_providers: [:azureactivedirectory] unless RUBY_ENGINE == 'opal'

:azure_activedirectory ではなく、:azureactivedirectory にする。
でないと、サインインしようとしたときに "Not found. Authentication passthru" を見ることになる。
Devise issue# 4208

callbackを定義し、protect_from_forgery から :azureactivedirectory を除外する

app/controllers/agents/omniauth_callbacks_controller.rb
class Agents::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  protect_from_forgery with: :exception, except: [:azureactivedirectory]  # 追加

  def azureactivedirectory
    callback
  end

  def passthru
    super
  end

  def failure
    super
  end

  private

    def callback
      @agent = Agent.find_or_create_for_oauth(request.env['omniauth.auth'])

      if @agent.persisted?
        sign_in_and_redirect @agent
      else
        session['devise.user_attributes'] = @agent.attributes
        redirect_to new_agent_registration_url
      end
    end
end
app/hyperstack/models/agent.rb
class Agent < ApplicationRecord
…(略)…

  class << self

    def current
      id = Hyperstack::Application.acting_user_id
      id ? find(id) : new
    end

    def find_or_create_for_oauth(auth)
      find_or_create_by!(email: auth.info.email.downcase) do |user|  # downcaseが必要
        user.provider = auth.provider
        user.uid = auth.uid
        user.name = auth.info.name
        user.email = auth.info.email
        password = Devise.friendly_token[0..5]
        logger.debug password
        user.password = password
      end
    end

    def new_with_session(params, session)
      if user_attributes = session['devise.user_attributes']
        new(user_attributes) { |user| user.attributes = params }
      else
        super
      end
    end
  end
end

これで、http://localhost:5000 で表示される "Sign in with Azureactivedirectory" から AzureADの認証へ進むようになった。

Screenshot from 2019-07-02 10-31-17.png

Screenshot from 2019-07-02 10-32-19.png

Login画面を通さず、リンクのクリックでAzureAD認証をさせる

HeaderコンポーネントのNavbarにLoginリンクを置いてみる

ログイン後はリンクの代わりにAgent.current.nameを表示させる

app/hyperstack/components/header.rb
class Header < HyperComponent

  render(HEADER) do
    Navbar('light', color: 'success') do
      Nav(class: 'ml-auto') do
        NavItem do
          Agent.current.name || login_link
        end
      end
    end
  end

  def login_link
    NavLink(class: 'login-link', href: 'agents/auth/azureactivedirectory') do
      'ログイン'
    end
  end
end

リンク先に agent_azureactivedirectory_omniauth_authorize_path を使いたいが、Hyperstackコンポーネントから使う方法がわからないので、パス 'agents/auth/azureactivedirectory' を直接指定した。

Login画面を表示しないようにする

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  # before_action :authenticate_agent!    # コメントアウト

  def acting_user
    current_agent
  end
end

これで、「ログイン」リンクをクリックすると、AzureADの認証に進むようになった。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0