omniauth-entra-id 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +229 -0
- data/UPGRADING.md +97 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/omniauth/entra_id/version.rb +8 -0
- data/lib/omniauth/entra_id.rb +2 -0
- data/lib/omniauth/strategies/entra_id.rb +206 -0
- data/lib/omniauth-entra-id.rb +1 -0
- data/omniauth-entra-id.gemspec +53 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 12d51e6e7781fc58e98afb3631c673ffe033f6b606ddb549df20e8baaae1c5af
|
4
|
+
data.tar.gz: 8b507bae0a8bbf2e72601e3e3be9c9d0513977528988ee04f9f70b5a8ce1672d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3595a1f659a7429216de6f00a39949b4a70655e4505bf6d126e9ea158569b977f0a7af8ebddb3df7791f89e85ad1ca35b53d926fc8bc6727d5477a06b20fc18a
|
7
|
+
data.tar.gz: c090da890e3c2c2edd5a4f36e922f5eadae2caf498c5cd9ba398eaba6c62c2ef055ff4cf4da487dc353cf79438abd8004c2c6cb09e9fe8024184c51b2e77642d
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## v3.0.0 (2024-10-22)
|
4
|
+
|
5
|
+
* To upgrade from the Azure ActiveDirectory V2 gem, please see [`UPGRADING.md`](UPGRADING.md)
|
6
|
+
* Branched from `omniauth-azure-activedirectory-v2` version 2.4.0 and renamed to `omniauth-entra-id`
|
7
|
+
* Can specify `tenant_name` in options via #31 (thanks to @Jureamer) for B2C login
|
8
|
+
* Supports authenticating with a certificate instead of client secret via #32 (thanks to @juliaducey)
|
9
|
+
* ID token extraction and validation is improved; long-standing fault with UID generation from OIDs (see #33) addressed via #34 (thanks to @tom-brouwer-bex)
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at dev@ripaglobal.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [https://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: https://contributor-covenant.org
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 RIPA Global
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
# OmniAuth::Entra::Id
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/omniauth-entra-id.svg)](https://rubygems.org/gems/omniauth-entra-id)
|
4
|
+
[![Build Status](https://github.com/RIPAGlobal/omniauth-entra-id/actions/workflows/master.yml/badge.svg)](https://github.com/RIPAGlobal/omniauth-entra-id/actions)
|
5
|
+
[![License](https://img.shields.io/github/license/RIPAGlobal/omniauth-entra-id.svg)](LICENSE.txt)
|
6
|
+
|
7
|
+
OAuth 2 authentication with [Entra ID API](https://learn.microsoft.com/en-us/entra/identity-platform/v2-overview). Rationale:
|
8
|
+
|
9
|
+
* https://github.com/marknadig/omniauth-azure-oauth2 is no longer maintained.
|
10
|
+
* https://github.com/marknadig/omniauth-azure-oauth2/pull/29 contains important additions.
|
11
|
+
|
12
|
+
This gem combines the two and makes some changes to support the Entra API. The old ActiveDirectory V1 API used OpenID Connect. If you need this, a gem from Microsoft [is available here](https://github.com/AzureAD/omniauth-azure-activedirectory), but seems to be abandoned.
|
13
|
+
|
14
|
+
**If upgrading from older versions of this gem under its old name of "Azure ActiveDirectory V2", please follow the instructions in [`UPGRADING.md`](UPGRADING.md).**
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'omniauth-entra-id'
|
24
|
+
```
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
```shell
|
29
|
+
$ bundle install
|
30
|
+
```
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
```shell
|
35
|
+
$ gem install omniauth-entra-id
|
36
|
+
```
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
Please start by reading https://github.com/marknadig/omniauth-azure-oauth2 for basic configuration and background information. Note that with this gem, you must use strategy name `entra_id` rather than `azure_oauth2`. Additional configuration information is given below.
|
43
|
+
|
44
|
+
### Entra ID server configuration
|
45
|
+
|
46
|
+
In most cases, you only want to receive 'verified' email addresses in your application. For older app registrations in the Entra portal, this may need to be [enabled explicitly](https://learn.microsoft.com/en-us/graph/applications-authenticationbehaviors?tabs=http#prevent-the-issuance-of-email-claims-with-unverified-domain-owners). It's [enabled by default](https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization#how-do-i-protect-my-application-immediately) for new multi-tenant app registrations made after June 2023.
|
47
|
+
|
48
|
+
### Implementation
|
49
|
+
#### With `OmniAuth::Builder`
|
50
|
+
|
51
|
+
You can do something like this for a static / fixed configuration:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
55
|
+
provider(
|
56
|
+
:entra_id,
|
57
|
+
{
|
58
|
+
client_id: ENV['ENTRA_CLIENT_ID'],
|
59
|
+
client_secret: ENV['ENTRA_CLIENT_SECRET']
|
60
|
+
}
|
61
|
+
)
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
...or, if using a custom provider class (called `YouTenantProvider` in this example, described in more detail later):
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
69
|
+
provider(
|
70
|
+
:entra_id,
|
71
|
+
YouTenantProvider
|
72
|
+
)
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
#### With Devise
|
77
|
+
|
78
|
+
In your `config/initializers/devise.rb` file you can do something like this for a static / fixed configuration:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
config.omniauth(
|
82
|
+
:entra_id,
|
83
|
+
{
|
84
|
+
client_id: ENV['ENTRA_CLIENT_ID'],
|
85
|
+
client_secret: ENV['ENTRA_CLIENT_SECRET']
|
86
|
+
}
|
87
|
+
)
|
88
|
+
```
|
89
|
+
|
90
|
+
...or, if using a custom provider class (called `YouTenantProvider` in this example, described in more detail later):
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
config.omniauth(
|
94
|
+
:entra_id,
|
95
|
+
YouTenantProvider
|
96
|
+
)
|
97
|
+
```
|
98
|
+
|
99
|
+
### Configuration options
|
100
|
+
|
101
|
+
All of the items listed below are optional, unless noted otherwise. They can be provided either in a static configuration Hash as shown in examples above, or via *read accessor instance methods* in a provider class (more on this later).
|
102
|
+
|
103
|
+
To have your application authenticate with Entra via a client secret, specify `client_secret`. If you instead want to use certificate-based authentication via client assertion, give the `certificate_path` and `tenant_id` instead. You should provide only `client_secret` or `certificate_path`, not both.
|
104
|
+
|
105
|
+
If you're using the client assertion flow, you need to register your certificate in the Entra portal. For more information, please see [the documentation](https://learn.microsoft.com/en-us/entra/identity-platform/certificate-credentials).
|
106
|
+
|
107
|
+
| Option | Use |
|
108
|
+
| ------ | --- |
|
109
|
+
| `client_id` | **Mandatory.** Client ID for the 'application' (integration) configured on the Entra side. Found via the Entra UI. |
|
110
|
+
| `client_secret` | **Mandatory for client secret flow.** Client secret for the 'application' (integration) configured on the Entra side. Found via the Entra UI. Don't give this if using client assertion flow. |
|
111
|
+
| `certificate_path` | **Mandatory for client assertion flow.** Don't give this if using a client secret instead of client assertion. This should be the filepath to a PKCS#12 file. |
|
112
|
+
| `tenant_id` | **Mandatory for client assertion flow.** Entra Tenant ID for multi-tenanted use. Default is `common`. Forms part of the Entra OAuth URL - `{base}/{tenant_id}/oauth2/v2.0/...` |
|
113
|
+
| `base_url` | Location of Entra login page, for specialised requirements; default is `OmniAuth::Strategies::EntraId::BASE_URL` (at the time of writing, this is `https://login.microsoftonline.com`). |
|
114
|
+
| `tenant_name` | For what is currently known by its old name of "Azure ActiveDirectory B2C" (and only active if `custom_policy` is also provided - see below), set the tenancy name to constructs the correct B2C endpoint of `{tenant_name}.b2clogin.com/{tenant_name}.onmicrosoft.com/{custom_policy>}" and uses that for auth calls. This is a convenience feature; the `base_entra_url` option could also be manually built up in the same way. |
|
115
|
+
| `custom_policy` | Custom policy. Default is nil. Used in conjunction with `tenant_name`- see above. |
|
116
|
+
| `authorize_params` | Additional parameters passed as URL query data in the initial OAuth redirection to Microsoft. See below for more. Empty Hash default. |
|
117
|
+
| `domain_hint` | If defined, sets (overwriting, if already present) `domain_hint` inside `authorize_params`. Default `nil` / none. |
|
118
|
+
| `scope` | If defined, sets (overwriting, if already present) `scope` inside `authorize_params`. Default is `OmniAuth::Strategies::EntraId::DEFAULT_SCOPE` (at the time of writing, this is `'openid profile email'`). |
|
119
|
+
| `adfs` | If defined, modifies the URLs so they work with an on premise ADFS server. In order to use this you also need to set the `base_url` correctly and fill the `tenant_id` with `'adfs'`. |
|
120
|
+
|
121
|
+
In addition, as a special case, if the request URL contains a query parameter `prompt`, then this will be written into `authorize_params` under that key, overwriting if present any other value there. Note that this comes from the current request URL at the time OAuth flow is commencing, _not_ via static options Hash data or via a custom provider class - but you _could_ just as easily set `scope` inside a custom `authorize_params` returned from a provider class, as shown in an example later; the request URL query mechanism is just another way of doing the same thing.
|
122
|
+
|
123
|
+
#### Explaining `custom_policy` and `tenant_name`
|
124
|
+
|
125
|
+
When using Azure ActiveDirectory B2C - which seems to be distinct from Entra ID and not renamed as of October 2024 - tenants can define custom policies. With normal OAuth use cases, when the underlying `oauth2` gem creates the request for getting a token via POST, it places all `params` (which would include anything you've provided in the normal configuration to name your custom policy) in the `body` of the request. This would not work. Microsoft's documentation indicates that when [requesting a token](https://learn.microsoft.com/en-us/azure/active-directory-b2c/access-tokens#request-a-token), they want the name of custom policies to be given in the URL rather than in the body of the request. They ignore a custom policy specified in the body.
|
126
|
+
|
127
|
+
Solve this for B2C use cases by giving your tenant name and custom policy name in the relevant configuration options. This causes a base URL to be constructed as follows:
|
128
|
+
|
129
|
+
```
|
130
|
+
<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/<policy-name>/oauth2/v2.0/...
|
131
|
+
```
|
132
|
+
|
133
|
+
#### Explaining `authorize_params`
|
134
|
+
|
135
|
+
The `authorize_params` hash-like object contains key-value pairs which are added to existing standard OAuth data in the initial `POST` request made by this gem from your web site, to the Microsoft Entra login page, at the start of OAuth flow. You can find these listed some way down the table just below an OAuth URL example at:
|
136
|
+
|
137
|
+
* https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow#request-an-authorization-code
|
138
|
+
|
139
|
+
...looking for in particular items from `prompt` onwards. For example, Microsoft say that a prompt option of `select_account` will always lead to the account selection UI to be shown at login, whether or not the user is currently signed into a Microsoft Entra ID account in that browser session. You would active it using options that look something like this in your OmniAuth Builder or Devise setup code:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
{
|
143
|
+
client_id: ENV['ENTRA_CLIENT_ID'],
|
144
|
+
client_secret: ENV['ENTRA_CLIENT_SECRET']
|
145
|
+
authorize_params: {
|
146
|
+
prompt: 'select_account'
|
147
|
+
}
|
148
|
+
}
|
149
|
+
```
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
#### Dynamic options via a custom provider class
|
154
|
+
|
155
|
+
Documentation mentioned earlier at https://github.com/marknadig/omniauth-azure-oauth2#usage gives an example of setting tenant ID dynamically via a custom provider class. We can also use that class in other ways. For example, let's rewrite it thus:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
class YouTenantProvider
|
159
|
+
def initialize(strategy)
|
160
|
+
@strategy = strategy
|
161
|
+
end
|
162
|
+
|
163
|
+
def client_id
|
164
|
+
ENV['ENTRA_CLIENT_ID']
|
165
|
+
end
|
166
|
+
|
167
|
+
def client_secret
|
168
|
+
ENV['ENTRA_CLIENT_SECRET']
|
169
|
+
end
|
170
|
+
|
171
|
+
def authorize_params
|
172
|
+
ap = {}
|
173
|
+
|
174
|
+
if @strategy.request && @strategy.request.params['login_hint']
|
175
|
+
ap['login_hint'] = @strategy.request.params['login_hint']
|
176
|
+
end
|
177
|
+
|
178
|
+
# (...and/or set other options such as 'prompt' here...)
|
179
|
+
|
180
|
+
return ap
|
181
|
+
end
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
In this example, we're providing custom `authorize_params`. You can just return a standard Ruby Hash here, using lower case String or Symbol keys. The `strategy` value given to the initializer is an instance of [`OmniAuth::Strategies::EntraId`](https://github.com/RIPAGlobal/omniauth-entra-id/blob/master/lib/omniauth/strategies/entra_id.rb) which is a subclass of [`OmniAuth::Strategies::OAuth2`](https://www.rubydoc.info/gems/omniauth-oauth2/1.8.0/OmniAuth/Strategies/OAuth2), but that's not all that helpful! What's more useful is to know that **the Rails `request` object is available via `@strategy.request` and, likewise, the session store via `@strategy.session`**. This gives you a lot of flexibility for responding to an inbound request or user session, varying the parameters used for the Entra OAuth flow.
|
186
|
+
|
187
|
+
In method `#authorize_params` above, the request object is used to look for a `login_hint` query string entry, set in whichever view(s) is/are presented by your application for use when your users need to be redirected to the OmniAuth controller in order to kick off OAuth with Entra. The value is copied into the `authorize_params` Hash. Earlier, it was mentioned that there was a special case of `prompt` being pulled from the request URL query data, but that this could also be done via a custom provider - here, you can see how; just check `@strategy.request.params['prompt']` and copy that into `authorize_params` if preset.
|
188
|
+
|
189
|
+
> **NB:** Naming things is hard! The predecessor gem used the name `YouTenantProvider` since it was focused on custom tenant provision, but if using this in a more generic way, perhaps consider a more generic name such as, say, `CustomOmniAuthEntraProvider`.
|
190
|
+
|
191
|
+
#### Special case scope override
|
192
|
+
|
193
|
+
If required and more convenient, you can specify a custom `scope` value via generation of an authorisation URL including that required `scope`, rather than by using a custom provider class with `def scope...end` method. Include the `scope` value in your call to generate the URL thus:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
omniauth_authorize_url('resource_name_eg_user', 'entra_id', scope: '...')
|
197
|
+
```
|
198
|
+
|
199
|
+
|
200
|
+
|
201
|
+
## Contributing
|
202
|
+
|
203
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/RIPAGlobal/omniauth-entra-id. This project is intended to be a safe, welcoming space for collaboration so contributors must adhere to the [code of conduct](https://github.com/RIPAGlobal/omniauth-entra-id/blob/master/CODE_OF_CONDUCT.md).
|
204
|
+
|
205
|
+
### Getting running
|
206
|
+
|
207
|
+
* Fork the repository
|
208
|
+
* Check out your fork
|
209
|
+
* `cd` into the repository
|
210
|
+
* `bin/setup`
|
211
|
+
* `bundle exec rspec` to make sure all the tests run
|
212
|
+
|
213
|
+
### Making changes
|
214
|
+
|
215
|
+
* Make your change
|
216
|
+
* Add tests and check that `bundle exec rspec` still runs successfully
|
217
|
+
* For new features (rather than bug fixes), update `README.md` with details
|
218
|
+
|
219
|
+
|
220
|
+
|
221
|
+
## License
|
222
|
+
|
223
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
## Code of Conduct
|
228
|
+
|
229
|
+
Everyone interacting in this project's codebases, issue trackers, chat rooms and mailing lists must follow the [code of conduct](https://github.com/RIPAGlobal/omniauth-entra-id/blob/master/CODE_OF_CONDUCT.md).
|
data/UPGRADING.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Upgrading from `omniauth-azure-activedirectory-v2`
|
2
|
+
|
3
|
+
This guide assumes you were on v2.3 or v2.4 of the old-named gem. The basic steps are:
|
4
|
+
|
5
|
+
* Update your code to account for the rename
|
6
|
+
* Update your code to account for breaking changes
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
## Updates due to the gem rename
|
11
|
+
|
12
|
+
All gem users will likely need to follow these steps.
|
13
|
+
|
14
|
+
* In general, searching project-wide for `azure_activedirectory_v2` and replacing with `entra_id` and, likewise, for the hyphenated `azure-activedirectory-v2` and replacing with `entra-id`, will cover a lot of use cases
|
15
|
+
* `README.md` always included examples with environment variables that were named as illustrations only; these have changed from e.g. `AZURE_CLIENT_ID` to `ENTRA_CLIENT_ID` just for internal consistency, but while renaming your own related environment variables or constants (should you use any) may help with code base understanding and consistency, it's not essential. Those names are part of _your_ code base, not part of code in this gem.
|
16
|
+
|
17
|
+
### Configuration
|
18
|
+
|
19
|
+
Rename the strategy in your configuration block:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
config.omniauth(
|
23
|
+
:azure_activedirectory_v2,
|
24
|
+
# ...
|
25
|
+
)
|
26
|
+
```
|
27
|
+
|
28
|
+
...becomes:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
config.omniauth(
|
32
|
+
:entra_id,
|
33
|
+
# ...
|
34
|
+
)
|
35
|
+
```
|
36
|
+
|
37
|
+
### Callback routes
|
38
|
+
|
39
|
+
Depending on how you handle callbacks from OmniAuth, you might need to update routes or controllers handling shared routes to account for the name change. The old callback URL of:
|
40
|
+
|
41
|
+
```
|
42
|
+
https://example.com/v1/auth/azure_activedirectory_v2/callback
|
43
|
+
```
|
44
|
+
|
45
|
+
...is now:
|
46
|
+
|
47
|
+
```
|
48
|
+
https://example.com/v1/auth/entra/callback
|
49
|
+
```
|
50
|
+
|
51
|
+
### URL generation
|
52
|
+
|
53
|
+
Change things like this:
|
54
|
+
|
55
|
+
```
|
56
|
+
omniauth_authorize_url('resource_name_eg_user', 'azure_activedirectory_v2', scope: '...')
|
57
|
+
```
|
58
|
+
|
59
|
+
...to this:
|
60
|
+
|
61
|
+
```
|
62
|
+
omniauth_authorize_url('resource_name_eg_user', 'entra_id', scope: '...')
|
63
|
+
```
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
## Updates due to other breaking changes
|
68
|
+
|
69
|
+
### Critical breaking change for all gem users
|
70
|
+
|
71
|
+
This change is for UIDs and is the main reason for creating a V3 gem, whether or not it included the Entra name change.
|
72
|
+
|
73
|
+
* The UID returned by OmniAuth for a user previously depended upon the `oid` (object ID) returned by Microsoft. As noted in #33 and fixed in #34, this _might not be unique_ and tenant ID (`tid`) is supposed to be considered too.
|
74
|
+
* Out-of-box, Entra ID will do this. If you were an Azure ActiveDirectory V2 (old-name gem, version 2.x) user, then you will have been receiving different UIDs based only on the `oid` from Microsoft.
|
75
|
+
* **The change of OID might break the connection between a previously-registered and logged in user and a new login** as usually, you need to store the OmniAuth UID somewhere alongside or within your User records when a user is "connected to" an external OAuth service such as Entra ID.
|
76
|
+
|
77
|
+
You have two options, should the issue affect you (and it almost certainly will).
|
78
|
+
|
79
|
+
* If you can determine the tenant IDs for all users in your database, you can just migrate the UIDs. The new UID is just a simple concatenation of tenant ID and object ID, so treating the UID as a string, add the tenant ID as a prefix without any other changes in your migration and things should work fine thereafter.
|
80
|
+
* Otherwise, you should lazy-migrate:
|
81
|
+
- As usual, in your OAuth callback handler, `request.env['omniauth.auth'].uid` gives the UID - but now that's the "new" Entra gem's value which includes tenant ID.
|
82
|
+
- If you can find a user with that ID, then all good - they've been migrated already or got connected to Entra *after* you started using the updated gem
|
83
|
+
- Otherwise, check `request.env['omniauth.auth'].extra.dig('raw_info', 'oid')` - this gives the value that the *old Azure ActiveDirectory V2 gem* used as UID
|
84
|
+
- Look up the user with this ID. If you find them, great; remember to migrate their record by updating their stored auth ID to the new `request.env['omniauth.auth'].uid` value.
|
85
|
+
- For better security add something like an indexed boolean column indicating whether or not the user has been thus migrated and only perform old OID lookups on users which have not yet been migrated.
|
86
|
+
- If the user can't be found by either means, then they've not been connected to your system yet. Your existing handling path for such a condition applies.
|
87
|
+
|
88
|
+
### Applications that handle multiple OAuth providers
|
89
|
+
|
90
|
+
If your user records contain users that have 'connected' to more than one kind of OAuth provider, then as well as the third party's UID being stored for future logins, you'll most likely have stored the OmniAuth provider name too so that the UID can be looked up in a provider's context (there's no guarantee, of course, that UIDs are unique *between providers* since they're entirely independent entities with their own strategies for allocating unique IDs).
|
91
|
+
|
92
|
+
In that case, you will need to migrate records from the old `azure_activedirectory_v2` name to `entra_id`. **Zero-downtime deployment of this change would be very hard since your codebase would need to update from the Azure ActiveDirectory V2 gem to the Entra ID gem with the migration running simultaneously**, so if you need to do such a migration, then you probably should plan for a small maintenance window. At the scheduled time, go into maintenance mode, migrate, deploy, and restore normal service. Even without this, though, the 'worst that can happen' (in theory!) would be temporary user login failures. Either the Entra gem will be causing you to look for a user with an `entra_id` provider but the migration to set this hasn't run yet, or the other way round, with the old gem looking for the old provider name but it's already updated.
|
93
|
+
|
94
|
+
### Breaking changes that depend on whether or not you use a certain feature
|
95
|
+
|
96
|
+
* If you refer to `OmniAuth::Strategies::AzureActivedirectoryV2` at all, then this becomes `OmniAuth::Strategies::EntraId` (note lower case "d").
|
97
|
+
* `base_azure_url` option renamed to just `base_url` with corresponding rename of `OmniAuth::Strategies::AzureActivedirectoryV2::BASE_AZURE_URL` to `OmniAuth::Strategies::EntraId::BASE_URL`.
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "omniauth/entra/id"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'omniauth-oauth2'
|
4
|
+
|
5
|
+
module OmniAuth
|
6
|
+
module Strategies
|
7
|
+
class EntraId < OmniAuth::Strategies::OAuth2
|
8
|
+
BASE_URL = 'https://login.microsoftonline.com'
|
9
|
+
|
10
|
+
option :name, 'entra_id'
|
11
|
+
option :tenant_provider, nil
|
12
|
+
option :jwt_leeway, 60
|
13
|
+
|
14
|
+
DEFAULT_SCOPE = 'openid profile email'
|
15
|
+
COMMON_TENANT_ID = 'common'
|
16
|
+
|
17
|
+
# The tenant_provider must return client_id, client_secret and,
|
18
|
+
# optionally, tenant_id and base_url.
|
19
|
+
#
|
20
|
+
args [:tenant_provider]
|
21
|
+
|
22
|
+
def client
|
23
|
+
provider = if options.tenant_provider
|
24
|
+
options.tenant_provider.new(self)
|
25
|
+
else
|
26
|
+
options
|
27
|
+
end
|
28
|
+
|
29
|
+
options.client_id = provider.client_id
|
30
|
+
|
31
|
+
if provider.respond_to?(:client_secret) && provider.client_secret
|
32
|
+
options.client_secret = provider.client_secret
|
33
|
+
elsif provider.respond_to?(:certificate_path) && provider.respond_to?(:tenant_id) && provider.certificate_path && provider.tenant_id
|
34
|
+
options.token_params = {
|
35
|
+
tenant: provider.tenant_id,
|
36
|
+
client_id: provider.client_id,
|
37
|
+
client_assertion: client_assertion(provider.tenant_id, provider.client_id, provider.certificate_path),
|
38
|
+
client_assertion_type: client_assertion_type
|
39
|
+
}
|
40
|
+
else
|
41
|
+
raise ArgumentError, "You must provide either client_secret or certificate_path and tenant_id"
|
42
|
+
end
|
43
|
+
|
44
|
+
options.tenant_id = if provider.respond_to?(:tenant_id)
|
45
|
+
provider.tenant_id
|
46
|
+
else
|
47
|
+
COMMON_TENANT_ID
|
48
|
+
end
|
49
|
+
|
50
|
+
options.base_url = if provider.respond_to?(:base_url )
|
51
|
+
provider.base_url
|
52
|
+
else
|
53
|
+
BASE_URL
|
54
|
+
end
|
55
|
+
|
56
|
+
options.tenant_name = provider.tenant_name if provider.respond_to?(:tenant_name)
|
57
|
+
options.custom_policy = provider.custom_policy if provider.respond_to?(:custom_policy)
|
58
|
+
options.authorize_params = provider.authorize_params if provider.respond_to?(:authorize_params)
|
59
|
+
options.authorize_params.domain_hint = provider.domain_hint if provider.respond_to?(:domain_hint) && provider.domain_hint
|
60
|
+
options.authorize_params.prompt = request.params['prompt'] if defined?(request) && request.params['prompt']
|
61
|
+
|
62
|
+
options.authorize_params.scope = if defined?(request) && request.params['scope']
|
63
|
+
request.params['scope']
|
64
|
+
elsif provider.respond_to?(:scope) && provider.scope
|
65
|
+
provider.scope
|
66
|
+
else
|
67
|
+
DEFAULT_SCOPE
|
68
|
+
end
|
69
|
+
|
70
|
+
oauth2 = if provider.respond_to?(:adfs?) && provider.adfs?
|
71
|
+
'oauth2'
|
72
|
+
else
|
73
|
+
'oauth2/v2.0'
|
74
|
+
end
|
75
|
+
|
76
|
+
tenanted_endpoint_base_url = if options.custom_policy && options.tenant_name
|
77
|
+
"https://#{options.tenant_name}.b2clogin.com/#{options.tenant_name}.onmicrosoft.com/#{options.custom_policy}"
|
78
|
+
else
|
79
|
+
"#{options.base_url}/#{options.tenant_id}"
|
80
|
+
end
|
81
|
+
|
82
|
+
options.client_options.authorize_url = "#{tenanted_endpoint_base_url}/#{oauth2}/authorize"
|
83
|
+
options.client_options.token_url = "#{tenanted_endpoint_base_url}/#{oauth2}/token"
|
84
|
+
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
uid do
|
89
|
+
#
|
90
|
+
# https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization
|
91
|
+
#
|
92
|
+
# OID alone might not be unique; TID must be included. An alternative
|
93
|
+
# would be to use 'sub' but this is only unique in client/app
|
94
|
+
# registration context. If a different app registration is used, the
|
95
|
+
# 'sub' values can be different too.
|
96
|
+
#
|
97
|
+
raw_info['tid'] + raw_info['oid']
|
98
|
+
end
|
99
|
+
|
100
|
+
info do
|
101
|
+
{
|
102
|
+
name: raw_info['name'],
|
103
|
+
email: raw_info['email'],
|
104
|
+
nickname: raw_info['unique_name'],
|
105
|
+
first_name: raw_info['given_name'],
|
106
|
+
last_name: raw_info['family_name']
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
extra do
|
111
|
+
{ raw_info: raw_info }
|
112
|
+
end
|
113
|
+
|
114
|
+
def callback_url
|
115
|
+
full_host + callback_path
|
116
|
+
end
|
117
|
+
|
118
|
+
# https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens
|
119
|
+
#
|
120
|
+
# Some account types from Microsoft seem to only have a decodable ID token,
|
121
|
+
# with JWT unable to decode the access token. Information is limited in those
|
122
|
+
# cases. Other account types provide an expanded set of data inside the auth
|
123
|
+
# token, which does decode as a JWT.
|
124
|
+
#
|
125
|
+
# Merge the two, allowing the expanded auth token data to overwrite the ID
|
126
|
+
# token data if keys collide, and use this as raw info.
|
127
|
+
#
|
128
|
+
def raw_info
|
129
|
+
if @raw_info.nil?
|
130
|
+
id_token_data = begin
|
131
|
+
::JWT.decode(access_token.params['id_token'], nil, false).first
|
132
|
+
rescue StandardError
|
133
|
+
{}
|
134
|
+
end
|
135
|
+
|
136
|
+
# For multi-tenant apps (the 'common' tenant_id) it doesn't make any
|
137
|
+
# sense to verify the token issuer, because the value of 'iss' in the
|
138
|
+
# token depends on the 'tid' in the token itself.
|
139
|
+
#
|
140
|
+
issuer = if options.tenant_id.nil? || options.tenant_id == COMMON_TENANT_ID
|
141
|
+
nil
|
142
|
+
else
|
143
|
+
"#{options.base_url || BASE_URL}/#{options.tenant_id}/v2.0"
|
144
|
+
end
|
145
|
+
|
146
|
+
# https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens#validate-tokens
|
147
|
+
#
|
148
|
+
JWT::Verify.verify_claims(
|
149
|
+
id_token_data,
|
150
|
+
verify_iss: !issuer.nil?,
|
151
|
+
iss: issuer,
|
152
|
+
verify_aud: true,
|
153
|
+
aud: options.client_id,
|
154
|
+
verify_expiration: true,
|
155
|
+
verify_not_before: true,
|
156
|
+
leeway: options[:jwt_leeway]
|
157
|
+
)
|
158
|
+
|
159
|
+
auth_token_data = begin
|
160
|
+
::JWT.decode(access_token.token, nil, false).first
|
161
|
+
rescue StandardError
|
162
|
+
{}
|
163
|
+
end
|
164
|
+
|
165
|
+
id_token_data.merge!(auth_token_data)
|
166
|
+
@raw_info = id_token_data
|
167
|
+
end
|
168
|
+
|
169
|
+
@raw_info
|
170
|
+
end
|
171
|
+
|
172
|
+
# https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow#request-an-access-token-with-a-certificate-credential
|
173
|
+
#
|
174
|
+
# The below methods support the flow for using certificate-based client
|
175
|
+
# assertion authentication.
|
176
|
+
#
|
177
|
+
def client_assertion_type
|
178
|
+
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
|
179
|
+
end
|
180
|
+
|
181
|
+
def client_assertion_claims(tenant_id, client_id)
|
182
|
+
{
|
183
|
+
'aud' => "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token",
|
184
|
+
'exp' => Time.now.to_i + 300,
|
185
|
+
'iss' => client_id,
|
186
|
+
'jti' => SecureRandom.uuid,
|
187
|
+
'nbf' => Time.now.to_i,
|
188
|
+
'sub' => client_id,
|
189
|
+
'iat' => Time.now.to_i
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
def client_assertion(tenant_id, client_id, certificate_path)
|
194
|
+
certificate_file = OpenSSL::PKCS12.new(File.read(certificate_path))
|
195
|
+
certificate_thumbprint ||= Digest::SHA1.digest(certificate_file.certificate.to_der)
|
196
|
+
private_key = OpenSSL::PKey::RSA.new(certificate_file.key)
|
197
|
+
|
198
|
+
claims = client_assertion_claims(tenant_id, client_id)
|
199
|
+
x5c = Base64.strict_encode64(certificate_file.certificate.to_der)
|
200
|
+
x5t = Base64.strict_encode64(certificate_thumbprint)
|
201
|
+
|
202
|
+
JWT.encode(claims, private_key, 'RS256', { 'x5c': [x5c], 'x5t': x5t })
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join('omniauth', 'entra_id')
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
$:.push File.expand_path( '../lib', __FILE__ )
|
5
|
+
require 'omniauth/entra_id/version'
|
6
|
+
|
7
|
+
# https://guides.rubygems.org/specification-reference/
|
8
|
+
#
|
9
|
+
Gem::Specification.new do |s|
|
10
|
+
s.name = 'omniauth-entra-id'
|
11
|
+
s.version = OmniAuth::Entra::Id::VERSION
|
12
|
+
s.date = OmniAuth::Entra::Id::DATE
|
13
|
+
s.summary = 'OAuth 2 authentication with the Entra ID API.'
|
14
|
+
s.authors = [ 'RIPA Global' ]
|
15
|
+
s.email = [ 'dev@ripaglobal.com' ]
|
16
|
+
s.licenses = [ 'MIT' ]
|
17
|
+
s.homepage = 'https://github.com/RIPAGlobal/omniauth-entra-id'
|
18
|
+
|
19
|
+
s.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
s.bindir = 'exe'
|
22
|
+
s.files = %w{
|
23
|
+
README.md
|
24
|
+
CHANGELOG.md
|
25
|
+
CODE_OF_CONDUCT.md
|
26
|
+
UPGRADING.md
|
27
|
+
LICENSE.txt
|
28
|
+
|
29
|
+
Gemfile
|
30
|
+
bin/console
|
31
|
+
bin/setup
|
32
|
+
|
33
|
+
lib/omniauth-entra-id.rb
|
34
|
+
lib/omniauth/entra_id.rb
|
35
|
+
lib/omniauth/entra_id/version.rb
|
36
|
+
lib/omniauth/strategies/entra_id.rb
|
37
|
+
|
38
|
+
omniauth-entra-id.gemspec
|
39
|
+
}
|
40
|
+
|
41
|
+
s.metadata = {
|
42
|
+
'homepage_uri' => 'https://www.ripaglobal.com/',
|
43
|
+
'bug_tracker_uri' => 'https://github.com/RIPAGlobal/omniauth-entra-id/issues/',
|
44
|
+
'changelog_uri' => 'https://github.com/RIPAGlobal/omniauth-entra-id/blob/master/CHANGELOG.md',
|
45
|
+
'source_code_uri' => 'https://github.com/RIPAGlobal/omniauth-entra-id'
|
46
|
+
}
|
47
|
+
|
48
|
+
s.add_runtime_dependency('omniauth-oauth2', '~> 1.8')
|
49
|
+
|
50
|
+
s.add_development_dependency('debug', '~> 1.9 ')
|
51
|
+
s.add_development_dependency('rake', '~> 13.2 ')
|
52
|
+
s.add_development_dependency('rspec', '~> 3.13')
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: omniauth-entra-id
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- RIPA Global
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-10-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: omniauth-oauth2
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: debug
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.9'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.13'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.13'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- dev@ripaglobal.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- CHANGELOG.md
|
77
|
+
- CODE_OF_CONDUCT.md
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- UPGRADING.md
|
82
|
+
- bin/console
|
83
|
+
- bin/setup
|
84
|
+
- lib/omniauth-entra-id.rb
|
85
|
+
- lib/omniauth/entra_id.rb
|
86
|
+
- lib/omniauth/entra_id/version.rb
|
87
|
+
- lib/omniauth/strategies/entra_id.rb
|
88
|
+
- omniauth-entra-id.gemspec
|
89
|
+
homepage: https://github.com/RIPAGlobal/omniauth-entra-id
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
metadata:
|
93
|
+
homepage_uri: https://www.ripaglobal.com/
|
94
|
+
bug_tracker_uri: https://github.com/RIPAGlobal/omniauth-entra-id/issues/
|
95
|
+
changelog_uri: https://github.com/RIPAGlobal/omniauth-entra-id/blob/master/CHANGELOG.md
|
96
|
+
source_code_uri: https://github.com/RIPAGlobal/omniauth-entra-id
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 3.0.0
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubygems_version: 3.5.21
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: OAuth 2 authentication with the Entra ID API.
|
116
|
+
test_files: []
|