-
Notifications
You must be signed in to change notification settings - Fork 5.5k
OmniAuth:AzureAD
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.
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
, thetenant_id
on theOverview
dashboard. - Under
Authentication
addhttp://localhost:3000/users/auth/microsoft/callback
andhttps://<your domain>/users/auth/microsoft/callback
- Under
Certificates and Secrets
add aClient 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
- Add a Sign-up and Login page which shows social login buttons (Sign in with Microsoft)
- Discuss the necessary steps to become a verified publisher with an MPN ID
- Store and manage access and refresh tokens (if we ever wanted to do something with it https://docs.microsoft.com/en-us/graph/tutorials/ruby?tutorial-step=3)
- Get a developer login strategy to work (CSRF is not implemented currently for the OmniAuth Developer strategy)
- Showing errors better.
- Get profile picture.
- Original version by https://github.com/coezbek