Netskin Logo

Office 365 data in Ruby on Rails app using Microsoft Graph REST API

#rails
by Ingo Albers on 19.12.2022

Connecting your Ruby on Rails application with the Azure Active Directory is a powerful tool to get Office 365 data into your app. In this simple example we store some basic data in our session. This could be extended to manage your calendar or emails for example. You can also use it as a single sign-on mechanism for a user model of your website.

In order to access the Office 365 data, we first need to set up the web application in the Azure Active Directory:

  1. Open a browser and navigate to the Azure Active Directory admin center. Login using a personal account (aka: Microsoft Account) or Work or School Account.
  2. Select Azure Active Directory in the left-hand navigation, then select App registrations under Manage.
  3. Select New registration. On the Register an application page, set the values as follows:

    • Set Name to Ruby Graph Tutorial.
    • Set Supported account types to Accounts in any organizational directory and personal Microsoft accounts.
    • Under Redirect URI, set the first drop-down to Web and set the value to http://localhost:3000/auth/microsoft_graph_auth/callback.
  4. Choose Register. On the Ruby Graph Tutorial page, copy the value of the Application (client) ID and save it, you will need it in the next step.
  5. Select Certificates & secrets under Manage. Select the New client secret button. Enter a value in Description and select one of the options for Expires and choose Add.
  6. Copy the client secret value before you leave this page. You will need it in the next step.

Once the registration is completed we set up our Rails application. Add the omniauth-oauth2 gem to our Gemfile:

# Gemfile
gem 'omniauth-oauth2'

Setup the OmniAuth strategy as in the sample app:

# lib/microsoft_graph_auth.rb
require 'omniauth-oauth2'

module OmniAuth
  module Strategies
    # Implements an OmniAuth strategy to get a Microsoft Graph
    # compatible token from Azure AD
    class MicrosoftGraphAuth < OmniAuth::Strategies::OAuth2
      option :name, :microsoft_graph_auth

      # Configure the Microsoft identity platform endpoints
      option :client_options,
             site: 'https://login.microsoftonline.com',
             authorize_url: '/common/oauth2/v2.0/authorize',
             token_url: '/common/oauth2/v2.0/token'

      # Send the scope parameter during authorize
      option :authorize_options, [:scope]

      # Unique ID for the user is the id field
      uid { raw_info['id'] }

      # Get additional information after token is retrieved
      extra do
        {
          'raw_info' => raw_info
        }
      end

      def raw_info
        # Get user profile information from the /me endpoint
        @raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me?$select=displayName,mail,mailboxSettings,userPrincipalName').parsed
      end

      # Override callback URL
      # OmniAuth by default passes the entire URL of the callback, including
      # query parameters. Azure fails validation because that doesn't match the
      # registered callback.
      def callback_url
        options[:redirect_uri] || (full_host + script_name + callback_path)
      end
    end
  end
end

Add an initializer to load the strategy when the application starts. Here we provide the APP_ID and APP_SECRET, that we got from the Azure Active Directory admin center, as environment variables. scope defines which data the user will grant access to. For a full reference you can check the Microsoft Graph Permissions Reference.

# config/initializers/omniauth_graph.rb
require 'microsoft_graph_auth'

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :microsoft_graph_auth,
           ENV['AZURE_APP_ID'],
           ENV['AZURE_APP_SECRET'],
           :scope => 'openid profile email offline_access user.read mailboxsettings.read calendars.readwrite'
end

Add the controller to handle the callback from Azure. The callback action in this example only stores the provided displayName as user_name in our session.

# app/controllers/auth_controller.rb
class AuthController < ApplicationController
  def callback
    # Access the authentication hash for omniauth
    auth_hash = request.env['omniauth.auth']

    # Save the user's display name
    session[:user_name] = auth_hash.dig(:extra, :raw_info, :displayName)

    redirect_to root_url
  end
end

Configure the route for the callback and a simple example page:

# config/routes.rb
Rails.application.routes.draw do
  get 'home/index'
  root 'home/index'
  # Route for OmniAuth callback
  match '/auth/:provider/callback', to: 'auth#callback', via: [:get, :post]
end

Add a view containing a login button if no user data is saved in the session already, otherwise show the data.

# app/views/home/index.html.erb
<% if session[:user_name] %>
  <h4>Welcome <%= session[:user_name] %>!</h4>
<% else %>
  <%= form_tag("/auth/microsoft_graph_auth", method: "post") do %>
    <button type="submit" class="btn btn-primary btn-large">Click here to sign in</button>
  <% end %>
<% end %>

When you click on the button to sign in you will be redirected to https://login.microsoftonline.com. Login here using your Office 365 account and it will ask you to confirm to access the data based on the permissions defined in the previously defined scope. Once the login is completed, you will be redirected back to your sample application and will be able to see your Office 365 display name in your Rails application.

For a full working example with more extensive features like a calendar view, you can check out the sample app linked in the resources.

Happy Coding!

Resources

❮ Using UNION Queries with Rails
Pure Models in Migrations ❯
Netskin Logo