### Install Gem Source: https://github.com/pond/omniauth-entra-id/blob/master/README.md Execute this command in your terminal to install the gem after adding it to the Gemfile. ```shell $ bundle install ``` -------------------------------- ### Install Gem Manually Source: https://github.com/pond/omniauth-entra-id/blob/master/README.md Alternatively, install the gem directly using the gem install command. ```shell $ gem install omniauth-entra-id ``` -------------------------------- ### Example of Setting Authorize Prompt Parameter Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Example showing how the 'prompt' parameter within 'authorize_params' affects the authorization URL. ```ruby omniauth_authorize_url('user', 'entra_id', authorize_params: { prompt: 'select_account' }) # Results in authorize_params[:prompt] being set to 'select_account' ``` -------------------------------- ### Example JWT Token Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/jwt-and-tokens.md An example of a complete JWT, showcasing the typical format. ```text eyJhbGciOiJSUzI1NiIsImtpZCI6IkU0MjREOUNFREVGQjBGNDkxRTA4ODIxNTQzQjcxNDkzIn0. eyJhdWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0NTY3ODkwMTIiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vY29tbW9uL3YyLjAiLCJvaWQiOiJhYmNkZWYwMS1hYmNkLWVmMDEtYWJjZC1lZjAxYWJjZGVmMDEiLCJ0aWQiOiIxMjM0NTY3OC1hYmNkLWVmMDEtYWJjZC1lZjAxYWJjZGVmMDEiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20ifQ. RnHzPKdwNBCCxgYVz0vA7f2V3ZxU5xqF0k0vXZe3lCQ ``` -------------------------------- ### Example of Setting Scope Parameter Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Example showing how the 'scope' parameter is set for an OmniAuth authorization URL. ```ruby omniauth_authorize_url('user', 'entra_id', scope: 'openid profile email offline_access') # Results in scope being set to 'openid profile email offline_access' ``` -------------------------------- ### Incoming Callback Request with Authorization Code Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Example of the GET request received by the application's callback URL after user authentication and consent. ```http GET /auth/entra_id/callback?code=M.R3_BAY...&state=xyz123&session_state=... ``` -------------------------------- ### Ruby - Missing Credentials Example Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Demonstrates scenarios where ArgumentError is raised due to insufficient authentication credentials. Use when initializing the strategy without client_secret or both certificate_path and tenant_id. ```ruby # ❌ Missing both client_secret and certificate_path OmniAuth::Strategies::EntraId.new(app, { client_id: 'id' }) strategy.client # => ArgumentError: "You must provide either client_secret or certificate_path and tenant_id" # ❌ Certificate path without tenant_id OmniAuth::Strategies::EntraId.new(app, { client_id: 'id', certificate_path: '/path/to/cert.p12' }) strategy.client # => ArgumentError # ❌ Tenant ID without certificate_path OmniAuth::Strategies::EntraId.new(app, { client_id: 'id', tenant_id: 'my-tenant' }) strategy.client # => ArgumentError # ✅ Valid: client_secret provided OmniAuth::Strategies::EntraId.new(app, { client_id: 'id', client_secret: 'secret' }) strategy.client # => Success # ✅ Valid: both certificate_path and tenant_id provided OmniAuth::Strategies::EntraId.new(app, { client_id: 'id', certificate_path: '/path/to/cert.p12', tenant_id: 'my-tenant' }) strategy.client # => Success ``` -------------------------------- ### OmniAuth Builder Registration Example Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Demonstrates how to register the Entra ID strategy using OmniAuth::Builder with provider configuration. ```ruby # Makes the following registration work: provider :entra_id, { client_id: '...', ... } # Internally, this calls: OmniAuth::Strategies::EntraId.new(app, options) ``` -------------------------------- ### Custom Provider Class for Dynamic Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/README.md Implement a provider class for dynamic configuration per-request. This example shows how to fetch client ID and secret from environment variables and pass login hints. ```ruby class MyEntraProvider def initialize(strategy) @strategy = strategy end def client_id ENV['ENTRA_CLIENT_ID'] end def client_secret ENV['ENTRA_CLIENT_SECRET'] end def authorize_params params = {} if @strategy.request&.params['login_hint'] params['login_hint'] = @strategy.request.params['login_hint'] end params end end provider :entra_id, MyEntraProvider ``` -------------------------------- ### OmniAuth Strategy Method Resolution Order Example Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Demonstrates how methods are resolved by checking the specific strategy class, then its parent classes like OAuth2 and Strategy. ```ruby strategy.client # Defined in EntraId strategy.client_options # Inherited from OAuth2 strategy.callback_path # Inherited from OAuth2/Strategy strategy.request_phase # Inherited from Strategy (not overridden) ``` -------------------------------- ### Request Parameter Overrides Example Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/entra-id-strategy.md Demonstrates how query parameters like 'prompt' and 'scope' can override the strategy's configuration at request time. ```ruby # Visit: /auth/entra_id?prompt=select_account&scope=openid%20profile # This causes authorize_params[:prompt] to be set to 'select_account' # and the scope to be overridden accordingly ``` -------------------------------- ### Custom Entra ID Provider Example Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/entra-id-strategy.md An example of a custom provider class that can be passed to the strategy constructor for dynamic configuration. It implements required and optional accessor methods for authentication parameters. ```ruby class CustomEntraProvider def initialize(strategy) @strategy = strategy end def client_id ENV['ENTRA_CLIENT_ID'] end def client_secret ENV['ENTRA_CLIENT_SECRET'] end def tenant_id ENV['ENTRA_TENANT_ID'] || 'common' end def authorize_params params = {} if @strategy.request && @strategy.request.params['login_hint'] params['login_hint'] = @strategy.request.params['login_hint'] end params end def domain_hint @strategy.request&.params&.fetch('domain_hint', nil) end end ``` -------------------------------- ### OmniAuth Entra ID Initialization Step 1: Registration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Example of setting up the Entra ID provider within a Rails initializer or OmniAuth.config. ```ruby # In Rails initializer or OmniAuth.config setup: provider :entra_id, { client_id: 'id', client_secret: 'secret' } ``` -------------------------------- ### Initialize EntraId Strategy with Client Secret Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/entra-id-strategy.md Example of initializing the EntraId strategy with client ID, client secret, and tenant ID. This configuration is used to build the OAuth2 client for Entra ID authentication. ```ruby strategy = OmniAuth::Strategies::EntraId.new(app, { client_id: 'your-client-id', client_secret: 'your-client-secret', tenant_id: 'your-tenant-id' }) oauth_client = strategy.client # Uses: https://login.microsoftonline.com/your-tenant-id/oauth2/v2.0/authorize ``` -------------------------------- ### Install omniauth-entra-id Gem Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/README.md Add the gem to your Gemfile and run bundle install to include it in your project. ```ruby # Gemfile gem 'omniauth-entra-id' # Run bundle install ``` -------------------------------- ### Option Key Examples for OmniAuth Entra ID Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Illustrates how to provide configuration options to the OmniAuth Entra ID strategy using symbols or strings as hash keys. Also shows handling of boolean options. ```ruby # All of these work: { client_id: '...' } { 'client_id' => '...' } # Boolean option names work with or without question mark in Hash: { ignore_tid: true } # In Hash { 'adfs' => true } # In Hash # But in provider classes, use question mark methods: def ignore_tid?; true; end # In provider class def adfs?; true; end # In provider class ``` -------------------------------- ### Ruby - Certificate File Not Found Example Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Shows the scenario where Errno::ENOENT is raised because the specified certificate file does not exist or is not readable. This can happen if the path is incorrect or file permissions are insufficient. ```ruby # ❌ File doesn't exist OmniAuth::Strategies::EntraId.new(app, { client_id: 'id', certificate_path: '/nonexistent/path/cert.p12', tenant_id: 'my-tenant' }).client ``` -------------------------------- ### Example Access Token Claims Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/types.md This snippet illustrates optional claims that may be present in an access token, depending on the account type and requested scopes. These can include granted scopes, user roles, and group memberships. ```ruby { "scp" => "openid profile email", "roles" => ["Admin", "Editor"], "groups" => ["group-id-1", "group-id-2"], "appid" => "client-id-here", "app_displayname" => "My Application", "ver" => "2.0" } ``` -------------------------------- ### Example Token Response from Entra OAuth Endpoint Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/types.md Example structure of a token response received from the Microsoft Entra OAuth endpoint. Includes access token, ID token, token type, expiration time, and optionally a refresh token. ```ruby { 'access_token' => 'eyJ...', 'id_token' => 'eyJ...', 'token_type' => 'Bearer', 'expires_in' => 3600, 'refresh_token' => 'AQAB...', # If offline_access scope requested 'scope' => 'openid profile email' } ``` -------------------------------- ### JWT Header with Certificate Information Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/types.md Example JWT header structure when using x5c (certificate chain) and x5t (thumbprint) for certificate-based authentication. ```ruby { 'alg' => 'RS256', 'x5c' => ['{base64-encoded-cert}'], 'x5t' => '{base64-encoded-thumbprint}' } ``` -------------------------------- ### Example JWT Structure Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/jwt-and-tokens.md A typical JWT is composed of a header, payload, and signature, separated by dots. ```text header.payload.signature ``` -------------------------------- ### Dynamic Authorization Parameters via Custom Provider Source: https://github.com/pond/omniauth-entra-id/blob/master/README.md Implement a custom provider class to dynamically set authorization parameters based on the incoming request or user session. This example shows how to include `login_hint` from the request parameters. ```ruby class YouTenantProvider def initialize(strategy) @strategy = strategy end def client_id ENV['ENTRA_CLIENT_ID'] end def client_secret ENV['ENTRA_CLIENT_SECRET'] end def authorize_params ap = {} if @strategy.request && @strategy.request.params['login_hint'] ap['login_hint'] = @strategy.request.params['login_hint'] end # (...and/or set other options such as 'prompt' here...) return ap end end ``` -------------------------------- ### JWT Leeway Example for Time Claims Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/jwt-and-tokens.md Illustrates how JWT leeway provides tolerance for clock skew when verifying expiration (`exp`) and not-before (`nbf`) claims. ```ruby current_time = 1234567850 token_exp = 1234567800 # Expired 50 seconds ago jwt_leeway = 60 # 60 seconds # Without leeway: token is expired, verification fails # With leeway: (token_exp + leeway) = 1234567860 > current_time, verification passes ``` -------------------------------- ### Ruby - Invalid Certificate File Example Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Demonstrates the scenario where OpenSSL::PKCS12::PKCS12Error is raised due to an invalid, corrupted, or incorrectly formatted certificate file. Use this when the certificate file cannot be parsed. ```ruby # ❌ Invalid certificate file OmniAuth::Strategies::EntraId.new(app, { client_id: 'id', certificate_path: '/path/to/invalid.p12', tenant_id: 'my-tenant' }).client # => OpenSSL::PKCS12::PKCS12Error: "PKCS12_parse: ..." ``` -------------------------------- ### Static Configuration with Client ID Only Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Configure the OmniAuth Entra ID provider with only the client ID. This is a minimal setup for static configuration. ```ruby provider :entra_id, { client_id: '12345678-1234-1234-1234-123456789012' } ``` -------------------------------- ### Ruby - Fixing Missing Credentials with Client Secret Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Provides an example of how to correctly configure the OmniAuth Entra ID strategy using the client secret flow. Ensure you have the client_id and client_secret environment variables set. ```ruby provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'] } ``` -------------------------------- ### Custom Rack Application with OmniAuth Entra ID Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Example of integrating OmniAuth Entra ID into a custom Rack application. ```ruby class MyApp def initialize @app = Rack::Builder.new do use OmniAuth::Builder do provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'] } end run lambda { |env| if env['PATH_INFO'] == '/auth/entra_id/callback' auth = env['omniauth.auth'] [200, {}, ["Logged in as #{auth.info.email}"]] else [404, {}, ["Not found"]] end } end end def call(env) @app.call(env) end end ``` -------------------------------- ### Default Authorization Endpoint URL Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Example URL for initiating the OAuth 2.0 authorization request to Entra ID. ```http GET https://login.microsoftonline.com/common/oauth2/v2.0/authorize ``` -------------------------------- ### Standard Entra ID Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Use this configuration for a standard single-tenant Entra ID setup. Ensure 'my-tenant-id' is replaced with your actual tenant ID. ```yaml base_url: 'https://login.microsoftonline.com' tenant_id: 'my-tenant-id' Authorization URL: https://login.microsoftonline.com/my-tenant-id/oauth2/v2.0/authorize Token URL: https://login.microsoftonline.com/my-tenant-id/oauth2/v2.0/token ``` -------------------------------- ### Refresh Token Exchange Request Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Example of a POST request to exchange a refresh token for a new access token. ```http POST https://login.microsoftonline.com/common/oauth2/v2.0/token Form Data: grant_type = refresh_token refresh_token = client_id = client_secret = scope = ``` -------------------------------- ### Extending OmniAuth Entra ID Strategy by Subclassing Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Provides an example of how to extend the OmniAuth::Strategies::EntraId strategy by creating a custom subclass and registering it with OmniAuth::Builder. ```ruby class CustomEntraIdStrategy < OmniAuth::Strategies::EntraId def custom_method # Custom behavior end end # Then register custom strategy: provider CustomEntraIdStrategy, { client_id: '...' } ``` -------------------------------- ### Ruby - Private Key Extraction Failed Example Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Illustrates the scenario where OpenSSL::PKey::PKeyError is raised because the private key cannot be extracted from the certificate file. This occurs if the certificate lacks a private key or it's in an unsupported format. ```ruby # ❌ Certificate without private key OmniAuth::Strategies::EntraId.new(app, { client_id: 'id', certificate_path: '/path/to/public-only.p12', tenant_id: 'my-tenant' }).client # => OpenSSL::PKey::PKeyError: "..." ``` -------------------------------- ### Example ID Token Claims Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/types.md This snippet shows a typical set of standard OpenID Connect claims found in an ID token issued by Entra ID. These claims provide information about the authenticated user and the token itself. ```ruby { "oid" => "abcdef01-2345-6789-abcd-ef0123456789", "tid" => "12345678-1234-1234-1234-123456789012", "email" => "user@example.com", "email_verified" => true, "name" => "John Doe", "given_name" => "John", "family_name" => "Doe", "unique_name" => "john@example.com", "preferred_username" => "john@example.com", "sub" => "a1b2c3d4-...", "aud" => "client-id-here", "iss" => "https://login.microsoftonline.com/12345678-1234-1234-1234-123456789012/v2.0", "exp" => 1234567890, "iat" => 1234567800, "nbf" => 1234567800, "auth_time" => 1234567800, "acr" => "urn:mace:incommon:iap:silver", "amr" => ["pwd", "mfa"] } ``` -------------------------------- ### Example JWT Access Token Claims Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/jwt-and-tokens.md A sample JSON object representing the claims found within a JWT access token, including audience, issuer, timestamps, scopes, roles, groups, and application ID. ```json { "aud": "https://graph.microsoft.com", "iss": "https://login.microsoftonline.com/tenant/v2.0", "iat": 1234567800, "exp": 1234567890, "scp": "openid profile email User.Read", "roles": ["Admin"], "groups": ["1234-5678-..."], "appid": "client-id" } ``` -------------------------------- ### RSpec Tests for Entra ID Authentication Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/usage-patterns.md Example RSpec tests for verifying Entra ID authentication flow, including user creation and sign-in for existing users. Requires OmniAuth test mode setup. ```ruby require 'rails_helper' describe 'Entra ID Authentication', type: :request do before do OmniAuth.config.test_mode = true end after do OmniAuth.config.test_mode = false end it 'creates user from OmniAuth callback' do OmniAuth.config.mock_auth[:entra_id] = OmniAuth::AuthHash.new( mock_entra_auth(uid: 'new-user-id') ) expect { get '/auth/entra_id/callback' }.to change { User.count }.by(1) user = User.last expect(user.uid).to eq('new-user-id') expect(user.email).to eq('test@example.com') end it 'signs in existing user' do user = create(:user, uid: 'existing-id', provider: 'entra_id') OmniAuth.config.mock_auth[:entra_id] = OmniAuth::AuthHash.new( mock_entra_auth(uid: 'existing-id') ) get '/auth/entra_id/callback' expect(response).to redirect_to(root_path) end end ``` -------------------------------- ### Sinatra OmniAuth Entra ID Setup Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Basic setup for using OmniAuth Entra ID within a Sinatra application. ```ruby require 'sinatra' require 'omniauth' require 'omniauth-entra-id' use OmniAuth::Builder do provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'] } end get '/auth/entra_id/callback' do auth = request.env['omniauth.auth'] # Use auth.uid, auth.info, etc. "Hello #{auth.info.name}" end ``` -------------------------------- ### Static Configuration with Client ID and Secret Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Use this method for static configuration by providing a hash with your Entra application's client ID and client secret. ```ruby provider :entra_id, { client_id: 'your-client-id', client_secret: 'your-client-secret' } ``` -------------------------------- ### OmniAuth Entra ID Initialization Step 2: Strategy Instantiation Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Illustrates how OmniAuth internally instantiates the EntraId strategy with the application and options. ```ruby # OmniAuth internally creates: strategy = OmniAuth::Strategies::EntraId.new( app, # Rack middleware app options # Merged configuration ) ``` -------------------------------- ### Dynamic Configuration: Multi-Tenant Provider Class Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/usage-patterns.md Enables multi-tenant support by dynamically determining the tenant ID. The `EntraProvider` class can fetch tenant IDs from request parameters or environment variables. ```ruby class EntraProvider def initialize(strategy) @strategy = strategy end def client_id ENV['ENTRA_CLIENT_ID'] end def client_secret ENV['ENTRA_CLIENT_SECRET'] end def tenant_id # Allow tenant selection from request parameter if @strategy.request&.params&.fetch('tenant_id', nil) @strategy.request.params['tenant_id'] else ENV['ENTRA_TENANT_ID'] || 'common' end end def authorize_params # Dynamic parameters based on request params = {} if @strategy.request params['prompt'] = @strategy.request.params['prompt'] if @strategy.request.params['prompt'] params['login_hint'] = @strategy.request.params['login_hint'] if @strategy.request.params['login_hint'] end params end end ``` ```ruby require_relative '../../app/lib/entra_provider' Rails.application.config.middleware.use OmniAuth::Builder do provider :entra_id, EntraProvider end ``` ```ruby class AuthController < ApplicationController def login # Generate auth URL with dynamic tenant redirect_to omniauth_authorize_path( 'user', 'entra_id', tenant_id: current_tenant.entra_id ) end end ``` -------------------------------- ### Ruby - Fixing Missing Credentials with Certificate Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Illustrates the correct configuration for the OmniAuth Entra ID strategy using the certificate flow. Both certificate_path and tenant_id must be provided. ```ruby provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], certificate_path: ENV['ENTRA_CERT_PATH'], tenant_id: ENV['ENTRA_TENANT_ID'] } ``` -------------------------------- ### Configure JWT Leeway in OmniAuth Strategy Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/jwt-and-tokens.md Example of configuring the `jwt_leeway` option for an OmniAuth strategy, setting a custom tolerance for time-based claim verification. ```ruby provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], jwt_leeway: 120 # 2 minutes instead of default 60 seconds } ``` -------------------------------- ### Set Authorization Parameters with 'prompt: select_account' Source: https://github.com/pond/omniauth-entra-id/blob/master/README.md Use the `authorize_params` option to add key-value pairs to the initial OAuth request to the Microsoft Entra login page. Setting `prompt: 'select_account'` ensures the account selection UI is always shown. ```ruby { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'] authorize_params: { prompt: 'select_account' } } ``` -------------------------------- ### Dynamic Configuration with Provider Class Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Implement a provider class for per-request customization of OmniAuth settings. The initializer receives the strategy instance for accessing request and session data. ```ruby class DynamicEntraProvider def initialize(strategy) @strategy = strategy end def client_id ENV['ENTRA_CLIENT_ID'] end def client_secret ENV['ENTRA_CLIENT_SECRET'] end def authorize_params params = {} # Access the current HTTP request if @strategy.request params['login_hint'] = @strategy.request.params['login_hint'] if @strategy.request.params['login_hint'] params['prompt'] = @strategy.request.params['prompt'] if @strategy.request.params['prompt'] end # Access the session if @strategy.session tenant = @strategy.session['selected_tenant'] params['tenant'] = tenant if tenant end params end def domain_hint @strategy.request&.params&.fetch('domain_hint', nil) if @strategy.request end def tenant_id # Dynamic tenant selection from request or session if @strategy.request @strategy.request.params['tenant_id'] || ENV['ENTRA_TENANT_ID'] else ENV['ENTRA_TENANT_ID'] end end end provider :entra_id, DynamicEntraProvider ``` -------------------------------- ### Single Tenant Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/README.md Configure the strategy for a single tenant (your organization). The token issuer is verified to ensure tokens come from your specific tenant. ```ruby provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], tenant_id: 'your-organization-tenant-id' } ``` -------------------------------- ### Get Client Assertion Type for Certificate Authentication Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/entra-id-strategy.md Returns the client assertion type constant used for certificate-based authentication. This is a standard OAuth 2.0 parameter. ```ruby def client_assertion_type # Returns the client assertion type constant for certificate-based authentication. # **Parameters:** None # **Returns:** String (`'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'`) # **Example:** type = strategy.client_assertion_type # => "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" ``` -------------------------------- ### client Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/entra-id-strategy.md Returns the OAuth2 client instance, building and configuring it from options or tenant provider. Constructs Entra ID authorization and token URLs based on tenant configuration. Handles both client secret and certificate-based (client assertion) authentication flows. ```APIDOC ## client ### Description Returns the OAuth2 client instance, building and configuring it from options or tenant provider. Constructs Entra ID authorization and token URLs based on tenant configuration. Handles both client secret and certificate-based (client assertion) authentication flows. ### Method `def client` ### Parameters None ### Returns `OAuth2::Client` instance configured for Entra ID ### Raises - `ArgumentError` — if neither `client_secret` nor (`certificate_path` and `tenant_id`) are provided ### Example ```ruby strategy = OmniAuth::Strategies::EntraId.new(app, { client_id: 'your-client-id', client_secret: 'your-client-secret', tenant_id: 'your-tenant-id' }) os_client = strategy.client # Uses: https://login.microsoftonline.com/your-tenant-id/oauth2/v2.0/authorize ``` ``` -------------------------------- ### Configure for On-Premise AD FS Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Enable `adfs: true` to use on-premise Active Directory Federation Services. This requires setting `base_url` to your AD FS server and `tenant_id` to `'adfs'`, altering the URL structure for authentication endpoints. ```ruby provider :entra_id, client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], base_url: 'https://adfs.example.com', tenant_id: 'adfs', adfs: true ``` -------------------------------- ### OmniAuth Entra ID Initialization Step 3: OAuth Client Creation Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Shows the lazy initialization of the OAuth2::Client when accessing strategy.client, including credential reading and URL construction. ```ruby # When first accessing strategy.client: oauth_client = strategy.client # Inside client() method: # 1. Initializes or loads provider object # 2. Reads credentials (secret or certificate) # 3. Constructs Entra URLs # 4. Returns OAuth2::Client ``` -------------------------------- ### Tenant ID Constants for OmniAuth::Strategies::EntraId Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Provides constants for various tenant identifiers, including common, ADFS, organizations, and consumers, along with the consumer GUID. ```ruby COMMON_TENANT_ID = 'common' # Multi-tenant endpoint AD_FS_TENANT_ID = 'adfs' # On-premise AD FS ORGANIZATIONS_TENANT_ID = 'organizations' # Organizations endpoint CONSUMERS_TENANT_ID = 'consumers' # Consumer accounts CONSUMERS_TENANT_GUID = '9188040d-6c67-4c5b-b112-36a304b66dad' # Consumer GUID ``` -------------------------------- ### Generate UID with Default Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/entra-id-strategy.md Demonstrates generating a unique user identifier using the default strategy configuration, which combines the tenant ID (TID) and object ID (OID). ```ruby # With default configuration (uses tid+oid) uid = strategy.uid # => "1234567890-abcd-efgh-ijkl-mnopqrstuv" ``` -------------------------------- ### Example Entra ID raw_info Hash Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/types.md Illustrates the structure of the raw_info hash, which merges JWT claims from ID and access tokens. This hash contains user and token-specific information. ```ruby strategy.raw_info # => { # "oid" => "user-object-id", # "tid" => "tenant-id", # "email" => "user@example.com", # "name" => "John Doe", # "given_name" => "John", # "family_name" => "Doe", # "unique_name" => "john@example.com", # "preferred_username" => "john@example.com", # "aud" => "client-id", # "iss" => "https://login.microsoftonline.com/tenant-id/v2.0", # "exp" => 1234567890, # "iat" => 1234567800, # "nbf" => 1234567800, # "auth_time" => 1234567800, # "scp" => "openid profile email", # "roles" => ["Admin"] # } ``` -------------------------------- ### Ruby - Catching OpenSSL::PKey::PKeyError Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Demonstrates how to rescue and handle OpenSSL::PKey::PKeyError when private key extraction from a certificate fails. This helps in identifying issues with the private key within the certificate. ```ruby begin strategy.client rescue OpenSSL::PKey::PKeyError => e puts "Private key extraction failed: #{e.message}" # Handle missing/invalid private key end ``` -------------------------------- ### Get Additional Data from Entra ID Token Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/entra-id-strategy.md Provides additional data from the auth hash, including raw token claims. Use this to access the complete set of claims present in the token. ```ruby def extra # Provides additional data in the auth hash, including raw token claims. # **Parameters:** None # **Returns:** Hash with key `:raw_info` containing the merged token claims # **Example:** extra_data = strategy.extra # => { raw_info: { oid: '...', tid: '...', email: '...', ... } } ``` -------------------------------- ### Extract User Info from Entra ID Token Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/entra-id-strategy.md Extracts standard user information from Entra ID token claims. Use this to get basic user details like name and email. ```ruby def info # Extracts standard user information from Entra ID token claims for the auth hash. # **Parameters:** None # **Returns:** Hash with keys: # - `:name` — Full name from `name` claim # - `:email` — Email from `email` claim # - `:nickname` — Unique name from `unique_name` claim # - `:first_name` — Given name from `given_name` claim # - `:last_name` — Family name from `family_name` claim # **Example:** user_info = strategy.info # => { # name: "John Doe", # email: "john@example.com", # nickname: "john@example.com", # first_name: "John", # last_name: "Doe" # } ``` -------------------------------- ### Ruby - Catching OpenSSL::PKCS12::PKCS12Error Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Shows how to rescue and handle OpenSSL::PKCS12::PKCS12Error when there are issues parsing the certificate file. This is useful for diagnosing certificate-related problems. ```ruby begin strategy.client rescue OpenSSL::PKCS12::PKCS12Error => e puts "Certificate parsing failed: #{e.message}" # Handle invalid certificate end ``` -------------------------------- ### China Cloud Entra ID Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Use this configuration for Entra ID services hosted in the Microsoft China cloud. Replace the example tenant ID with your actual China cloud tenant ID. ```yaml base_url: 'https://login.partner.microsoftonline.cn' tenant_id: '12345678-1234-1234-1234-123456789012' Authorization URL: https://login.partner.microsoftonline.cn/12345678-1234-1234-1234-123456789012/oauth2/v2.0/authorize Token URL: https://login.partner.microsoftonline.cn/12345678-1234-1234-1234-123456789012/oauth2/v2.0/token ``` -------------------------------- ### Catching Errno::ENOENT for Certificate File Not Found Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Use a begin-rescue block to catch `Errno::ENOENT` when the certificate file specified in the strategy configuration cannot be found. This allows for graceful handling of missing files. ```ruby begin strategy.client rescue Errno::ENOENT => e puts "Certificate file not found: #{e.message}" # Handle missing file end ``` -------------------------------- ### Constructing Authorization URL in Ruby Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Ruby code demonstrating how the authorization URL is built and used to redirect the user. This happens automatically when a user visits the authentication path. ```ruby # Happens automatically when user visits /auth/entra_id: # Strategy builds this URL: authorize_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" "client_id=12345678-..." "&response_type=code" "&redirect_uri=https://app.example.com/auth/entra_id/callback" "&scope=openid profile email" "&state=random-state-value" # User is redirected to this URL: redirect_to authorize_url # User logs in at Entra, grants consent (if needed) # Browser redirects back to /auth/entra_id/callback?code=...&state=... ``` -------------------------------- ### Access Dynamic Parameters in Provider Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Demonstrates how dynamic parameters like 'tenant_id' are accessed within the provider class from the strategy's request object. ```ruby # In the provider, access it via: @strategy.request.params['tenant_id'] ``` -------------------------------- ### Multi-Tenant Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/README.md Configure the strategy for multi-tenant use, allowing users from any Entra organization. The 'common' tenant ID is used by default. ```ruby provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], tenant_id: 'common' # Default } ``` -------------------------------- ### Securely Configure Entra ID Provider Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Illustrates the correct way to configure the Entra ID provider using environment variables for secrets, rather than hardcoding them. ```ruby # ❌ Never do this: provider :entra_id, { client_id: '12345678-வுகளை', client_secret: 'secret-value-in-code' } # ✅ Always use environment variables: provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'] } ``` -------------------------------- ### Set Domain Hint for Account Selection Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Use the `domain_hint` option for a convenient way to set `authorize_params['domain_hint']`. This can help skip the account selection dialog if the user's domain is known. ```ruby provider :entra_id, client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], domain_hint: 'example.com' # Equivalent to: # authorize_params: { domain_hint: 'example.com' } ``` -------------------------------- ### Ignore Tenant ID for User ID Uniqueness Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Set `ignore_tid` to `true` to use only the Object ID (OID) for the user's unique identifier. This is primarily for migration purposes from older versions and is not recommended for new installations. ```ruby provider :entra_id, client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], ignore_tid: true # For migration purposes only ``` -------------------------------- ### Configuring Consumer Accounts Application Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Sets up the OmniAuth Entra ID provider to allow personal Microsoft accounts by specifying 'consumers' as the tenant ID. ```ruby provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], tenant_id: 'consumers' } ``` -------------------------------- ### On-Premise AD FS Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Configure for on-premise Active Directory Federation Services (AD FS). Set 'adfs' to true and provide your AD FS server's base URL. ```yaml base_url: 'https://adfs.example.com' tenant_id: 'adfs' adfs: true Authorization URL: https://adfs.example.com/adfs/oauth2/authorize Token URL: https://adfs.example.com/adfs/oauth2/token ``` -------------------------------- ### Common Environment Variables for Entra ID Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md A typical configuration pattern using environment variables for Entra ID authentication. Recommended variables include client ID, secret, tenant ID, and base URL. ```ruby # In configuration { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], tenant_id: ENV['ENTRA_TENANT_ID'] || 'common', base_url: ENV['ENTRA_BASE_URL'] || 'https://login.microsoftonline.com' } ``` -------------------------------- ### Certificate-Based Authentication Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/README.md Configure authentication using a certificate (PKCS#12) instead of a client secret for enhanced security. Specify the path to your certificate file. ```ruby provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], certificate_path: '/path/to/certificate.p12', tenant_id: ENV['ENTRA_TENANT_ID'] } ``` -------------------------------- ### Configure OmniAuth with Dynamic Provider Class Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Use a custom dynamic provider class with OmniAuth::Builder for flexible configuration. Ensure the provider class is required. ```ruby # app/lib/entra_provider.rb class EntraProvider def initialize(strategy) @strategy = strategy end def client_id ENV['ENTRA_CLIENT_ID'] end def client_secret ENV['ENTRA_CLIENT_SECRET'] end def tenant_id @strategy.request&.params&.fetch('tenant_id', ENV['ENTRA_TENANT_ID']) || 'common' end def authorize_params { prompt: 'select_account' } end end # config/initializers/omniauth.rb require_relative '../app/lib/entra_provider' Rails.application.config.middleware.use OmniAuth::Builder do provider :entra_id, EntraProvider end ``` -------------------------------- ### Decode ID Token in Strategy Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/jwt-and-tokens.md This Ruby code demonstrates the first step in token verification within a strategy: decoding the ID token. ```ruby id_token_data = JWT.decode(access_token.params['id_token'], nil, false).first ``` -------------------------------- ### Provider Class Constructor Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/types.md Custom provider classes must accept the strategy instance in their constructor and store it. This allows access to request and session objects. ```ruby def initialize(strategy) @strategy = strategy # Instance of OmniAuth::Strategies::EntraId end ``` -------------------------------- ### Using Absolute Paths for Certificate Files Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md When configuring the certificate path, always use absolute paths to avoid issues with relative path resolution. This ensures the strategy can reliably locate the certificate file. ```ruby # ❌ Don't use relative paths certificate_path: 'config/certs/cert.p12' # ✅ Use absolute paths certificate_path: File.join(Rails.root, 'config', 'certs', 'cert.p12') ``` -------------------------------- ### Configuration with Refresh Tokens Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/README.md Configure the strategy to request refresh tokens by including 'offline_access' in the scope. This allows token refreshing without user interaction later. ```ruby provider :entra_id, { client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], scope: 'openid profile email offline_access' # Include offline_access } # Later: access auth.credentials.refresh_token to refresh without user interaction ``` -------------------------------- ### Store and Rotate Entra ID Tokens Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/oauth-flow.md Shows how to store user tokens and refresh them when they expire. ```ruby # Store refresh token with user user.update( entra_access_token: auth.credentials.token, entra_refresh_token: auth.credentials.refresh_token, entra_token_expires_at: Time.at(auth.credentials.expires_at) ) # Check if token expired and refresh if needed if user.entra_token_expires_at < Time.now # Refresh the token new_token = refresh_entra_token(user.entra_refresh_token) user.update(entra_access_token: new_token) end ``` -------------------------------- ### Create Users Table Migration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/usage-patterns.md Database migration to create the users table with columns for storing Entra ID information and authentication tokens. ```ruby class CreateUsers < ActiveRecord::Migration[6.1] def change create_table :users do |t| t.string :provider t.string :uid t.string :email t.string :name t.string :entra_oid t.string :entra_tid t.string :refresh_token t.datetime :token_expires_at t.timestamps end add_index :users, [:provider, :uid], unique: true end end ``` -------------------------------- ### Optional Accessor: Domain Hint Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/types.md Optionally implement `domain_hint` to set the `domain_hint` parameter in OAuth authorization requests. This can pre-fill the domain for sign-in. ```ruby def domain_hint # Returns: String or nil # Sets authorize_params['domain_hint'] end ``` -------------------------------- ### Public API Surface Exports Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Shows the main class and version constants exported after requiring the gem. ```ruby # Class (main export) OmniAuth::Strategies::EntraId # Version constants (internal use) OmniAuth::Entra::Id::VERSION OmniAuth::Entra::Id::DATE ``` -------------------------------- ### Add Authorization Parameters Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/configuration.md Include additional parameters in the OAuth authorization request to control the authentication flow. Use `prompt` to specify re-authentication behavior and `login_hint` to pre-fill the username. ```ruby provider :entra_id, client_id: ENV['ENTRA_CLIENT_ID'], client_secret: ENV['ENTRA_CLIENT_SECRET'], authorize_params: { prompt: 'select_account', login_hint: 'user@example.com' } ``` -------------------------------- ### Guest User Authentication with Elevated Permissions Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/usage-patterns.md Authenticate users based on their email domain, distinguishing between regular users and guest users from a specific domain. Guest users are signed in with a separate scope. ```ruby def entra_id auth = request.env['omniauth.auth'] # Check if user is from allowed guest domain email = auth.info.email guest_domain = ENV['GUEST_DOMAIN'] if email.end_with?("@#{guest_domain}") @user = GuestUser.from_omniauth(auth) sign_in(@user, scope: :guest_user) else @user = User.from_omniauth(auth) sign_in(@user) end redirect_to root_path end ``` -------------------------------- ### Using a Provider Class with OmniAuth Entra ID Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/api-reference/module-structure.md Demonstrates an alternative method for extending the OmniAuth Entra ID strategy by using a separate provider class, which is the recommended approach. ```ruby class MyProvider def initialize(strategy); @strategy = strategy; end # ... implement methods end provider :entra_id, MyProvider ``` -------------------------------- ### Multi-Organization SaaS Configuration Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/usage-patterns.md Configure Entra ID client credentials and tenant IDs dynamically based on the current organization. This is useful for SaaS applications serving multiple distinct Entra ID tenants. ```ruby class EntraSaasProvider def initialize(strategy) @strategy = strategy end def client_id # Use different client ID per organization current_org = @strategy.session&.fetch('current_org_id') if current_org ENV["ENTRA_CLIENT_ID_#{current_org}"] else ENV['ENTRA_CLIENT_ID'] end end def client_secret current_org = @strategy.session&.fetch('current_org_id') if current_org ENV["ENTRA_CLIENT_SECRET_#{current_org}"] else ENV['ENTRA_CLIENT_SECRET'] end end def tenant_id # Each organization has its own tenant current_org = @strategy.session&.fetch('current_org_id') if current_org Organization.find(current_org).entra_tenant_id else 'common' end end end ``` -------------------------------- ### JWT Verification Parameters Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md This snippet shows the required claims verified for all tokens, including audience, expiration, and not-before times with leeway. It also illustrates how the issuer is verified for single-tenant applications. ```ruby # Required claims verified for all tokens: verify_params = { aud: options.client_id, # Audience must match client ID exp: { leeway: options.jwt_leeway }, # Expiration with leeway nbf: { leeway: options.jwt_leeway } # Not-before with leeway } # Issuer verified only for single-tenant apps: verify_params[:iss] = issuer unless issuer.nil? # issuer is nil for: # - tenant_id == 'common' # - tenant_id == 'adfs' # - tenant_id is nil ``` -------------------------------- ### Ruby - Catching ArgumentError for Missing Credentials Source: https://github.com/pond/omniauth-entra-id/blob/master/_autodocs/errors.md Shows how to rescue and handle ArgumentError when the OmniAuth Entra ID strategy is initialized with missing credentials. This is useful for providing user-friendly error messages. ```ruby begin strategy.client rescue ArgumentError => e if e.message.include?("client_secret or certificate_path") # Handle missing credentials puts "Configure either client_secret or certificate_path+tenant_id" end end ```