Skip to content
Carlos Antonio da Silva edited this page Feb 16, 2023 · 9 revisions

Make sure you read the OmniAuth Overview first

It's recommended to check out this OmniAuth Overview for more information on OmniAuth in general, before proceeding. It shows a different integration example but gives a good idea of how it looks like.

Setting up Omniauth for AzureAD

I followed this guide from medium with some modifications:

0.) Login to portal.azure.com and go to Azure Active Directory -> App Registrations and register your web application.

  • Take note of the client_id, the tenant_id on the Overview dashboard.
  • Under Authentication add http://localhost:3000/users/auth/microsoft/callback and https://<your domain>/users/auth/microsoft/callback
  • Under Certificates and Secrets add a Client Secret and note its value (the ID is not the client_id).
  • Optionally add branding, etc.

1.) Adding Omniauth plugin gem

I used the omniauth-azure-activedirectory-v2 gem which was forked from the omniauth-azure-oauth2 gem, because the latter did not support v2 end points of the Azure Active Directory API.

# Gemfile.rb
gem 'omniauth-azure-activedirectory-v2'

Run bundle install.

Note: with OmniAuth 2+ you probably need to install omniauth-rails_csrf_protection as well. Check the overview wiki for more info.

2.) Configure Devise

This was a bit tricky, because I didn't like the auth URL to be called azure-activedirectory-v2 but microsoft (when other strategies are just called twitter).

# config/devise.rb

  config.omniauth :microsoft,
                  client_id:     Rails.application.credentials.azure[:client_id],
                  client_secret: Rails.application.credentials.azure[:client_secret],
                  tenant_id:     Rails.application.credentials.azure[:tenant_id], # Remove for 'common' end-point.
                  name: 'microsoft',
                  strategy_class: OmniAuth::Strategies::AzureActivedirectoryV2

Note 1: If you don't want to use your own tenant_id for app registrations, then remove the tenant_id: ... line. This will call the common endpoint and allow multi-tenant sign-ups to your app.

Note 2: I am not passing the credentials by ENV, so you need to add the following to your credentials.yml.enc:

# config/credentials.yml.enc
azure:
  client_id: ....
  client_secret: ....
  tenant_id: ....

3.) Create a callback controller for omniauth. This is the place where OAuth2 will call you back when user is authenticated successfully. You have to implement this to handle the business logic what should happen with a signed-in user. Setting the @user for the session and error handling are shown below:

# app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
    def microsoft
        @user = User.from_omniauth(request.env["omniauth.auth"])

        if @user&.persisted?
            sign_in_and_redirect @user, event: :authentication
        else
            flash[:alert] = @user.errors.full_messages.join("<br>")
            Rails.logger.error "Couldn't login user: " + @user.errors.inspect
            redirect_back(fallback_location: root_path, allow_other_host: false) # Don't redirect back to Microsoft
        end
    end
end

4.) Update the User model

The business logic above needs the following addition to the User model so that a user can be created if s/he doesn't already exists. Note that this does not include logic to merge an account with the same email which was created via sign-up locally and authenticated via OAuth2.

# app/models/user.rb
 class User < ApplicationRecord
   devise :rememberable,
          :database_authenticatable,
          :registerable,
          :recoverable,
          :validatable,
+         :omniauthable,
+         omniauth_providers: [:microsoft]
+         # Unfortunately the develper strategy lacks CSRF protection, so it can't be used in development
+         
+  def self.from_omniauth(auth)
+    where(uid: auth.uid, provider: auth.provider).first_or_create do |user|
+      user.provider = auth.provider
+      user.uid = auth.uid
+      user.email = auth.info.email
+      user.password = Devise.friendly_token # Set dummy password otherwise Devise will complain about empty passwords.
+      user.last_name = auth.info.last_name
+      user.first_name = auth.info.first_name
+    end
end

5.) Put a button on your page (e.g. application.html.erb)

I would suggest to add the prompt: 'select_account' option. This causes the login page at login.microsoft.com to prompt the user to always select his/her account. Without this option, the user might be silently logged in with the account currently active in the browser. See the documentation on the prompt parameter for further information.

# app/views/layouts/application.html.erb
<%= button_to "Login", user_microsoft_omniauth_authorize_path(prompt: 'select_account'), class: 'navbar-link', data: { turbo: false } %>

Note: before OmniAuth 2 you were allowed to use GET requests, so your button/link might need to be tweaked accordingly depending on the OmniAuth version you're using.

6.) Add routes:

# config/routes.rb
  devise_for :users,
    controllers: {
      omniauth_callbacks: 'users/omniauth_callbacks'
    }

7.) Add database migration:

rails g migration AddOmniAuthColumnsToUsers provider uid first_name last_name

Add a two column index to the resulting migration file (:uid should come first in array):

# db/migrate/20...._add_omni_auth_columns_to_users.rb
 class AddOmniAuthColumnsToUsers < ActiveRecord::Migration[6.0]
   def change
     add_column :users, :provider, :string
     add_column :users, :uid, :string
     add_column :users, :first_name, :string
     add_column :users, :last_name, :string
 
+    add_index :users, [:uid, :provider], unique: true
   end
 end

And execute the migration:

rails db:migrate

Open topics

Credits

Clone this wiki locally