Omniauth::MicrosoftBusinessCentral

This is an OmniAuth strategy for authenticating to Microsoft Dynamics 365 Business Central through the Microsoft identity platform (Entra ID, formerly Azure AD). It obtains an access token (and refresh token) for the Business Central API on behalf of the signed-in user.

Requires Ruby >= 3.2, OmniAuth 2.x and omniauth-oauth2 1.9.x.

Installation

Add this line to your application's Gemfile:

gem 'omniauth-microsoft_business_central'

And then execute:

$ bundle

Or install it yourself as:

$ gem install omniauth-microsoft_business_central

Usage

OmniAuth 2 only accepts POST for the request phase. In a Rails app, add omniauth-rails_csrf_protection to your Gemfile and link to the provider with button_to or link_to ..., method: :post:

gem 'omniauth-rails_csrf_protection'
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :microsoft_business_central,
           ENV['AZURE_APPLICATION_CLIENT_ID'],
           ENV['AZURE_APPLICATION_CLIENT_SECRET']
end

The auth hash carries the tokens under credentials and the user's identity — decoded from the Entra ID token claims — under uid/info:

auth = request.env['omniauth.auth']
auth['credentials']['token']         # Business Central access token
auth['credentials']['refresh_token'] # present because of the default offline_access scope
auth['uid']                          # the Entra object id (oid claim)
auth['info']['name']
auth['info']['email']
auth['extra']['tenant_id']           # the Entra tenant the user signed in from
auth['extra']['claims']              # all decoded token claims

Configuring

Scopes

The default scope is:

https://api.businesscentral.dynamics.com/user_impersonation offline_access

user_impersonation grants delegated access to the Business Central API, and offline_access makes Microsoft return a refresh token. Bare scope names are qualified against https://api.businesscentral.dynamics.com/; OpenID Connect scopes (openid, email, profile, offline_access) and absolute URLs pass through untouched:

provider :microsoft_business_central, client_id, client_secret,
         :scope => 'user_impersonation offline_access openid email profile'

Requesting openid makes Microsoft return an id_token, which the strategy then prefers over the access token when populating uid/info.

Restricting sign-in to one tenant

tenant_id defaults to common, which lets users from any Entra tenant sign in (what a multi-tenant app registration needs). Set it to a tenant GUID or domain to pin the strategy to a single directory:

provider :microsoft_business_central, client_id, client_secret,
         :tenant_id => 'contoso.onmicrosoft.com'

Per-request authorize params

login_hint, domain_hint, prompt, response_mode and scope may also be supplied per request and are passed through to Microsoft's authorize endpoint:

/auth/microsoft_business_central?login_hint=user@example.com

(With OmniAuth 2 the request phase is a POST; the params can be in the query string or the form body.)

Configure the callback URL

The strategy overrides callback_url to drop the query string that OmniAuth would otherwise append, because the Microsoft identity platform rejects a redirect_uri that does not match the registered one exactly. Pass callback_url to override it outright:

  :callback_url => 'https://app.example.com/users/auth/microsoft_business_central/callback'

Otherwise it is derived from the request, including the mount prefix — a strategy mounted under Devise's /users produces https://app.example.com/users/auth/microsoft_business_central/callback.

Client credentials in the token request

client_options[:auth_scheme] is pinned to :request_body, because the Microsoft identity platform expects client_id and client_secret in the token request body (client_secret_post). This was oauth2 1.x's default; oauth2 2.x defaults to :basic_auth.

Migrating from the dropstream fork of omniauth-microsoft_graph

This gem replaces dropstream/omniauth-microsoft_graph, a fork that pointed the omniauth-microsoft_graph gem's strategy at Business Central while keeping the Graph name. What changes:

  • The provider is :microsoft_business_central, so routes move from /auth/microsoft_graph to /auth/microsoft_business_central. Update the redirect URI on the Azure app registration to the new callback path.
  • Requires OmniAuth 2.x: the request phase is POST-only, and Rails apps need omniauth-rails_csrf_protection.
  • The auth hash now has uid, info and extra['claims'], decoded from the token claims. extra['params'] (the raw token response) is still there.
  • The Graph-era verify_token/access_token-in-params flows and the JSON-body code path were dropped; the strategy is a plain OAuth2 authorization-code flow.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Run bundle exec bundle-audit check --update to check the locked dependency tree against the advisory database; CI runs the same check weekly.

Releasing

Releases are cut from the GitHub Releases UI; GitHub Actions builds and publishes the gem to rubygems.org via RubyGems trusted publishing, so no API key is stored in the repo and the gemspec can keep rubygems_mfa_required.

  1. Bump VERSION in lib/omniauth-microsoft_business_central/version.rb and add a CHANGELOG.md section for it, along with a link definition for the new heading at the bottom of that file. Commit both on main.
  2. Draft a new release with the target set to main and the tag set to the version prefixed with vv0.1.0, not 0.1.0. The prefix is stripped before comparing against version.rb; the gem version itself carries no v.
  3. Click Generate release notes — the preferred method — then publish. The generated notes list the merged pull requests; CHANGELOG.md carries the curated prose and links back to each release.

Publishing creates the tag, which fires the Release workflow: it reruns the full test matrix, refuses to continue if the tag and version.rb disagree, and pushes the gem. Note that RubyGems versions are immutable — a bad release can be yanked, never replaced, so the version bump has to be committed before the release is published.

One-time RubyGems setup

Needed before the first release from CI, and again only if the gem is renamed or moved. Because the gem does not exist on rubygems.org yet, register a pending trusted publisher: on rubygems.org open your profile -> Trusted publishers -> Create, with gem name omniauth-microsoft_business_central, owner dropstream, repository omniauth-microsoft_business_central, workflow release.yml, and environment release. Pending publishers expire after 12 hours, so do this shortly before publishing the first release. The repo also needs an environment named release.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/dropstream/omniauth-microsoft_business_central.

License

Available as open source under the terms of the MIT License. Portions derived from omniauth-microsoft_graph (MIT).