This little app demonstrates how to use Azure Entra-ID for authentication. According to What is Microsoft Entra ID?,
Microsoft Entra ID is a cloud-based identity and access management service that your employees can use to access external resources.
Entra-ID (formerly known as Azure Active Directory) is Microsoft's brand for identity services like authentication.
In this example, we will be using Azure Entra-ID, and the omniauth library.
OmniAuth is a library that standardizes multi-provider authentication for web applications. It was created to be powerful, flexible, and do as little as possible. Any developer can create strategies for OmniAuth that can authenticate users via disparate systems. OmniAuth strategies have been created for everything from Facebook to LDAP.
https://github.com/RIPAGlobal/omniauth-entra-id
The following steps show how to use Entra ID and Omniauth for authentication with a rails application.
-
Go to the Microsoft Entra Portal: https://entra.microsoft.com (this is now the entry point for Azure AD services).
-
Register your app:
- In the Entra ID section, navigate to App registrations > New registration.
- Provide a name for the app.
- Set the Redirect URI to something like
http://localhost:3000/auth/entra_id/callback
if you're developing locally. - Add another Redirect URI for your production environment
-
After registering, note the following information:
- Application (client) ID.
- Directory (tenant) ID.
-
Create a Client Secret:
- Go to Certificates & secrets > New client secret.
- Make sure to copy the secret value and store it safely.
In your Rails app, you'll need the omniauth-entra_oauth2
gem.
Add the following gems to your Gemfile
:
bundle add omniauth-entra-id
bundle add omniauth
bundle add dotenv-rails --group=development,test
Run bundle install
to install the gems.
Create a new OmniAuth initializer for Entra ID configuration.
Create a file config/initializers/omniauth.rb
:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :developer, fields: [ :name, :email ]
provider :entra_id, {
client_id: ENV["AZURE_CLIENT_ID"],
client_secret: ENV["AZURE_CLIENT_SECRET_VALUE"],
tenant_id: ENV["AZURE_TENANT_ID"],
scope: "openid email profile User.Read",
response_type: "code",
grant_type: "authorization_code"
}
end
OmniAuth.config.logger = Rails.logger
# Ensure OmniAuth works in development without callback issues
OmniAuth.config.allowed_request_methods = [ :post, :get ]
OmniAuth.config.silence_get_warning = true
Ensure that the environment variables for Entra ID are set. Create or update a .env
file:
AZURE_CLIENT_ID=your-client-id
AZURE_CLIENT_SECRET_VALUE=your-client-secret
AZURE_TENANT_ID=your-tenant-id
Next, let's set up a SessionsController
to handle login and logout. We’ll use OmniAuth to handle the OAuth2 authentication flow.
Run the following to generate the controller:
rails generate controller Sessions new create destroy
Edit the app/controllers/sessions_controller.rb
:
class SessionsController < ApplicationController
def new
end
def create
auth = request.env["omniauth.auth"]
# could add guard if auth is nil
user = User.find_or_create_by!(provider: auth["provider"], uid: auth["uid"]) do |u|
u.first_name = auth["info"]["first_name"]
u.last_name = auth["info"]["last_name"]
u.email = auth["info"]["email"]
end
session[:user_id] = user.id
redirect_to root_path, notice: "Signed in #{user.email} successfully"
end
def destroy
session[:user_id] = nil
redirect_to root_path, notice: "Signed out successfully"
end
def failure
msg = "Failure to authenticate with Entra ID: #{params[:message}"
logger.error msg
redirect_to root_path, alert: msg
end
end
Update your config/routes.rb
to handle the authentication routes:
Rails.application.routes.draw do
root to: 'home#index'
# Entra authentication routes
get "/auth/failure", to: "sessions#failure" # this must be before session#new to match
get '/auth/:provider', to: 'sessions#new'
get '/auth/:provider/callback', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy', as: 'logout'
end
For simplicity, let's show a home page where the user can log in and log out based on their session.
Run:
rails generate controller Home index
In the app/controllers/home_controller.rb
:
class HomeController < ApplicationController
def index
end
end
In app/views/home/index.html.erb
, add the following to display login/logout links:
<% if session[:user_id] %>
<p>Welcome, <%= User.find(session[:user_id]).name %>!</p>
<%= link_to 'Sign Out', logout_path, method: :delete %>
<% else %>
<%= link_to 'Sign in with Entra ID', '/auth/entra_oauth2' %>
<% end %>
If you don't have a User
model to store the authenticated users, create one. Run the following:
rails g model User email:string provider:string uid:string first_name:string last_name:string
rails db:migrate
In app/models/user.rb
:
class User < ApplicationRecord
validates :email, presence: true, uniqueness: true
end
Now, you should be ready to test your Entra ID authentication. Run the Rails server:
rails server
Go to http://localhost:3000, and you should see a "Sign in with Entra ID" link. Click the link, and after authentication via Entra ID, you should be redirected back to the home page and see your name (if authenticated).
Note: http://127.0.0.1:3000 will not work, since the Azure / Entra redirection URI does not include this.
- Handle Authentication Failures: You can define an
omniauth_failure
method in yourSessionsController
to handle errors:
class SessionsController < ApplicationController
def omniauth_failure
redirect_to root_path, alert: "Authentication failed. Please try again."
end
end
- Protect Routes: If you want to restrict access to certain parts of your app only to authenticated users, you can use a before_action to ensure the user is logged in:
class ApplicationController < ActionController::Base
before_action :authenticate_user!
private
def authenticate_user!
redirect_to root_path, alert: 'Please sign in first' unless session[:user_id]
end
end
Next I tried to prepare a Dockefile for it. Rails 8 comes already with a Dockerfile.
I needed the following steps:
Create a secret_key_base
using rails secret
. Copy the generated key into your .env
file as:
SECRET_KEY_BASE=xxxxx
See also this Medium post.
To build docker, use:
docker build -t auth-azure-app .
To run docker to test it, you can pass in the .env
file:
docker run -p 3000:3000 --env-file .env auth-azure-app
In the final deployment for production, you would set the environment variables in the deployment environment.
https://github.com/basecamp/thruster
https://camillovisini.com/coding/rails-migrate-from-postgres-to-sqlite
This README would normally document whatever steps are necessary to get the application up and running.
Things you may want to cover:
-
Ruby version
-
System dependencies
-
Configuration
-
Database creation
-
Database initialization
-
How to run the test suite
-
Services (job queues, cache servers, search engines, etc.)
-
Deployment instructions
-
...