omniauth-azure_active_directory_b2c 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0c0f7094e772b9c4aeeac8eba5ee0e5f8664c863
4
+ data.tar.gz: d48a92be522a55bb07ebbaca2d279a7fb84e825c
5
+ SHA512:
6
+ metadata.gz: 8dd3cdf6e26aec04a21bd50d7030ba583cc1d76a31b5e48dc7f726d1aa792bfacc2ef0dff1535b79a3e29aa52b7d7f8f4cafb28c09f543b308601042807bd567
7
+ data.tar.gz: 996b5025889be29a5dd93f4964f0aed570be8fe26ca2aef3506b4dcd34b53c82aa66e8c18906117242297f479f69c193bf80a453546ed2c4b87fff6d85c5f6ea
@@ -0,0 +1 @@
1
+ require_relative 'omniauth/strategies/azure_active_directory_b2c.rb'
@@ -0,0 +1,163 @@
1
+ require 'omniauth'
2
+ require 'proc_evaluate'
3
+
4
+ require_relative 'azure_active_directory_b2c/authentication_request.rb'
5
+ require_relative 'azure_active_directory_b2c/authentication_response.rb'
6
+ require_relative 'azure_active_directory_b2c/client.rb'
7
+ require_relative 'azure_active_directory_b2c/jwt_validator.rb'
8
+ require_relative 'azure_active_directory_b2c/policy_options.rb'
9
+ require_relative 'azure_active_directory_b2c/policy.rb'
10
+ require_relative 'azure_active_directory_b2c/version.rb'
11
+
12
+ module OmniAuth
13
+ module Strategies
14
+ class AzureActiveDirectoryB2C
15
+
16
+ include OmniAuth::Strategy
17
+
18
+ using ProcEvaluate # adds the `evaluate` refinement method to Object and Proc instances
19
+
20
+ #########################################
21
+ # Error definitions
22
+ #########################################
23
+
24
+ GenericError = Class.new(StandardError)
25
+
26
+ # Errors raised du to missing options or settings
27
+ MissingOptionError = Class.new(GenericError)
28
+
29
+ # Errors raised during the callback stage
30
+ CallbackError = Class.new(GenericError) do
31
+ attr_reader :failure_message_key
32
+ def self.failure_message_key(key)
33
+ define_method(:failure_message_key) { key }
34
+ end
35
+ end
36
+ InvalidCredentialsError = Class.new(CallbackError) { failure_message_key :invalid_credentials }
37
+ UnauthorizedError = Class.new(CallbackError) { failure_message_key :unauthorized }
38
+ MissingCodeError = Class.new(CallbackError) { failure_message_key :missing_code }
39
+ IdTokenValidationError = Class.new(CallbackError) { failure_message_key :id_token_validation_failed }
40
+
41
+ #########################################
42
+ # Strategy options
43
+ #########################################
44
+
45
+ option :name, 'azure_active_directory_b2c'
46
+ option :redirect_uri # the url to return to in the callback phase
47
+ option :policy_options
48
+
49
+ #########################################
50
+ # Strategy - setup
51
+ #########################################
52
+
53
+ def policy_options
54
+ @policy_options ||= options.policy_options || (raise MissingOptionError, '`policy_options` not defined')
55
+ end
56
+
57
+ def policy
58
+ @policy = Policy.new(**policy_options.symbolize_keys)
59
+ end
60
+
61
+ def redirect_uri
62
+ @redirect_uri ||= options.redirect_uri.evaluate(self) || (raise MissingOptionError, '`redirect_uri` not defined')
63
+ end
64
+
65
+ def setup_phase
66
+ end
67
+
68
+ #########################################
69
+ # Strategy - request
70
+ #########################################
71
+
72
+ def authentication_request
73
+ @authentication_request ||= AuthenticationRequest.new(policy, redirect_uri: redirect_uri)
74
+ end
75
+
76
+ def authorization_uri
77
+ authentication_request.authorization_uri
78
+ end
79
+
80
+ def set_session_variables!
81
+ # set the session details to check against in the callback_phase
82
+ session['omniauth.nonce'] = authentication_request.nonce
83
+ session['omniauth.state'] = authentication_request.state
84
+ end
85
+
86
+ def request_phase
87
+ # this phase needs to redirect to B2C with the correct params and record the state and nonce in the session to check against in the callback_phase
88
+ set_session_variables!
89
+ redirect authentication_request.authorization_uri
90
+ end
91
+
92
+ #########################################
93
+ # Strategy - callback
94
+ #########################################
95
+
96
+ def authentication_response
97
+ @authentication_response ||= AuthenticationResponse.new(policy, request.params['code'])
98
+ end
99
+
100
+ def callback_phase
101
+ validate_callback_response!
102
+ validate_id_token!
103
+ super # required to complete the callback phase
104
+
105
+ rescue UnauthorizedError => e
106
+ return Rack::Response.new(['401 Unauthorized'], 401).finish
107
+ rescue CallbackError => e
108
+ fail!(e.failure_message_key, e)
109
+ rescue ::Timeout::Error, ::Errno::ETIMEDOUT => e
110
+ fail!(:timeout, e)
111
+ rescue ::SocketError => e
112
+ fail!(:failed_to_connect, e)
113
+ end
114
+
115
+ def validate_callback_response!
116
+ state, code, error, error_reason, error_description = request.params.values_at('state', 'code', 'error', 'error_reason', 'error_description')
117
+
118
+ if error || error_reason || error_description
119
+ raise InvalidCredentialsError, [error, error_reason, error_description].compact.join('. ')
120
+ elsif state.to_s.empty? || state != session.delete('omniauth.state')
121
+ raise UnauthorizedError
122
+ elsif !code
123
+ raise MissingCodeError, 'Code was not returned from OpenID Connect Provider'
124
+ end
125
+ end
126
+
127
+ def validate_id_token!
128
+ results = authentication_response.validate_id_token
129
+ if results.has_errors?
130
+ raise IdTokenValidationError, results.full_messages.join('. ')
131
+ end
132
+ end
133
+
134
+ #########################################
135
+ # Auth Hash Schema
136
+ #########################################
137
+
138
+ def user_info
139
+ authentication_response.user_info
140
+ end
141
+
142
+ def subject_id
143
+ authentication_response.subject_id
144
+ end
145
+
146
+ def extra_info
147
+ authentication_response.extra_info
148
+ end
149
+
150
+ def credentials
151
+ authentication_response.credentials
152
+ end
153
+
154
+ # return the details required by OmniAuth
155
+ info { user_info }
156
+ uid { subject_id }
157
+ extra { extra_info }
158
+ credentials { credentials }
159
+
160
+ end
161
+ end
162
+ end
163
+ OmniAuth.config.add_camelization('azure_active_directory_b2c', 'AzureActiveDirectoryB2C')
@@ -0,0 +1,48 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class AzureActiveDirectoryB2C
4
+ class AuthenticationRequest
5
+
6
+ class ResponseType
7
+ # TODO: provide constants for each option
8
+ CODE = 'code'
9
+ end
10
+
11
+ attr_reader :policy, :client
12
+
13
+ def initialize(policy, redirect_uri:, **override_options)
14
+ @policy = policy
15
+ @client = policy.initialize_client({ redirect_uri: redirect_uri, **override_options })
16
+ end
17
+
18
+ def authorization_uri(**override_options)
19
+ options = default_authorization_uri_options.merge(override_options)
20
+ options = options.select {|k, v| v }
21
+ client.authorization_uri(options)
22
+ end
23
+
24
+ def state
25
+ @state ||= SecureRandom.hex(16)
26
+ end
27
+
28
+ def nonce
29
+ @nonce ||= SecureRandom.hex(16)
30
+ end
31
+
32
+ def response_type
33
+ ResponseType::CODE
34
+ end
35
+
36
+ def default_authorization_uri_options
37
+ {
38
+ response_type: response_type,
39
+ scope: policy.scope,
40
+ state: state,
41
+ nonce: nonce,
42
+ }
43
+ end
44
+
45
+ end # AuthenticationRequest
46
+ end # AzureActiveDirectoryB2C
47
+ end # Strategies
48
+ end # OmniAuth
@@ -0,0 +1,122 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class AzureActiveDirectoryB2C
4
+ class AuthenticationResponse
5
+
6
+ class AuthenticationMethod
7
+ BASIC = 'basic'
8
+ BODY = 'body'
9
+ POST = 'post'
10
+ end
11
+
12
+ attr_reader :policy, :client, :code
13
+
14
+ def initialize(policy, code, **override_options)
15
+ @policy = policy
16
+ @client = policy.initialize_client({ redirect_uri: nil, **override_options })
17
+ @client.authorization_code = code
18
+ @code = code
19
+ end
20
+
21
+ def access_token
22
+ @access_token ||= get_access_token!
23
+ end
24
+
25
+ def id_token
26
+ @id_token ||= get_id_token!
27
+ end
28
+
29
+ def refresh_token
30
+ access_token.refresh_token
31
+ end
32
+
33
+ def expires_in
34
+ access_token.expires_in
35
+ end
36
+
37
+ def subject_id
38
+ id_token.sub
39
+ end
40
+
41
+ def user_info
42
+ {
43
+ name: id_token.raw_attributes['name'],
44
+ email: ([*id_token.raw_attributes['emails']].first),
45
+ nickname: id_token.raw_attributes['preferred_username'],
46
+ first_name: id_token.raw_attributes['given_name'],
47
+ last_name: id_token.raw_attributes['family_name'],
48
+ gender: id_token.raw_attributes['gender'],
49
+ image: id_token.raw_attributes['picture'],
50
+ phone: id_token.raw_attributes['phone_number'],
51
+ urls: { website: id_token.raw_attributes['website'] }
52
+ }
53
+ end
54
+
55
+ def extra_info
56
+ { raw_info: id_token.raw_attributes }
57
+ end
58
+
59
+ def scope
60
+ policy.scope
61
+ end
62
+
63
+ def authentication_method
64
+ AuthenticationMethod::BODY
65
+ end
66
+
67
+ def credentials
68
+ {
69
+ id_token: id_token,
70
+ token: access_token.access_token,
71
+ refresh_token: refresh_token,
72
+ expires_in: expires_in,
73
+ scope: scope,
74
+ }
75
+ end
76
+
77
+ def default_access_token_options
78
+ {
79
+ scope: scope,
80
+ client_auth_method: authentication_method,
81
+ }
82
+ end
83
+
84
+ def get_access_token!
85
+ client.access_token!(default_access_token_options)
86
+ end
87
+
88
+ def get_id_token!
89
+ # TODO: if the id_token is not passed back, we could get the id token from the userinfo endpoint (or fail if no endpoint is defined?)
90
+ encrypted_id_token = access_token.id_token
91
+ decoded_id_token = decode_id_token!(encrypted_id_token)
92
+ end
93
+
94
+ def decode_id_token!(id_token)
95
+ ::OpenIDConnect::ResponseObject::IdToken.decode(id_token, public_key)
96
+ end
97
+
98
+ def public_key
99
+ if policy.jwk_signing_algorithm == :RS256 && policy.jwk_signing_keys
100
+ jwk_key
101
+ else
102
+ raise 'id_token signing algorithm is currently not supported: %s' % policy.jwk_signing_algorithm
103
+ end
104
+ end
105
+
106
+ def jwk_key
107
+ key = policy.jwk_signing_keys
108
+ if key.has_key?('keys')
109
+ JSON::JWK::Set.new(key['keys']) # a set of keys
110
+ else
111
+ JSON::JWK.new(key) # a single key
112
+ end
113
+ end
114
+
115
+ def validate_id_token(seconds_since_epoc = Time.now.to_i)
116
+ JwtValidator.validate(id_token.raw_attributes, public_key, policy, seconds_since_epoc)
117
+ end
118
+
119
+ end # AuthenticationResponse
120
+ end # AzureActiveDirectoryB2C
121
+ end # Strategies
122
+ end # OmniAuth
@@ -0,0 +1,14 @@
1
+ require 'openid_connect'
2
+
3
+ module OmniAuth
4
+ module Strategies
5
+ class AzureActiveDirectoryB2C
6
+
7
+ class Client < ::OpenIDConnect::Client
8
+ # developers can override this class as required
9
+ # Be sure to also override MicrosoftAzureB2C::Policy#initialize_client
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,93 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class AzureActiveDirectoryB2C
4
+
5
+ class ValidationResult
6
+ def error_messages
7
+ @error_messages ||= []
8
+ end
9
+
10
+ def add_error(key, message)
11
+ error_messages << { error: key, message: message }
12
+ end
13
+
14
+ def has_errors?
15
+ error_messages.any?
16
+ end
17
+
18
+ def okay?
19
+ error_messages.empty?
20
+ end
21
+
22
+ def full_messages
23
+ error_messages.collect {|err| err[:message] }
24
+ end
25
+ end # ValidationResult
26
+
27
+ class JwtValidator
28
+
29
+ EPOC_TIME_LEEWAY_SECONDS = 10
30
+
31
+ attr_reader :jwt, :jwt_key, :policy, :seconds_since_epoc
32
+
33
+ def self.validate(jwt, jwt_key, policy, seconds_since_epoc = Time.now.to_i)
34
+ new(jwt, jwt_key, policy, seconds_since_epoc).validate
35
+ end
36
+
37
+ def initialize(jwt, jwt_key, policy, seconds_since_epoc = Time.now.to_i)
38
+ @jwt = jwt
39
+ @jwt_key = jwt_key
40
+ @policy = policy
41
+ @seconds_since_epoc = seconds_since_epoc
42
+ end
43
+
44
+ def validate
45
+ results = ValidationResult.new
46
+ results.add_error(:algorithm_mismatch, "Signing algorithm mismatch: Expected `#{policy.jwk_signing_algorithm} but got #{jwt.algorithm}`") unless signing_algoritm_matches?
47
+ results.add_error(:issue_mismatch, "Issue mismatch: Expected `#{policy.issuer}` but got `#{jwt[:iss]}`") unless issuer_matches?
48
+ results.add_error(:audience_mismatch, "Audience mismatch: Expected `#{policy.aud}` but got `#{jwt[:aud]}`") unless audience_matches?
49
+ results.add_error(:before_start_time, "Token has not yet commenced: Valid at #{jwt[:nbf]} but currently #{seconds_since_epoc}") unless on_or_after_not_before_time?
50
+ results.add_error(:after_expiry_time, "Token has expired: Expired at #{jwt[:exp]} but currently #{seconds_since_epoc}") unless before_expiration_time?
51
+
52
+ begin
53
+ verify_signature!
54
+ rescue JSON::JWS::VerificationFailed
55
+ results.add_error(:signiture_verification_failed, 'Signture verification failed') unless signature_verified?
56
+ rescue JSON::JWS::UnexpectedAlgorithm
57
+ results.add_error(:unexpected_signiture_algorithm, 'Unexpected signature algorithm') unless signature_verified?
58
+ rescue => e
59
+ results.add_error(:signiture_verification_failed, e.message || 'Signature verification failed') unless signature_verified?
60
+ end
61
+
62
+ results # return results
63
+ end
64
+
65
+ def signing_algoritm_matches?
66
+ # An attacker may change the signing algorith to provide a forged signature
67
+ jwt.algorithm.to_sym == policy.jwk_signing_algorithm
68
+ end
69
+
70
+ def issuer_matches?
71
+ jwt[:iss] && jwt[:iss] != '' && jwt[:iss] == policy.issuer
72
+ end
73
+
74
+ def audience_matches?
75
+ jwt[:aud] && jwt[:aud] != '' && jwt[:aud] == policy.application_identifier
76
+ end
77
+
78
+ def on_or_after_not_before_time?
79
+ (seconds_since_epoc + EPOC_TIME_LEEWAY_SECONDS) >= jwt[:nbf]
80
+ end
81
+
82
+ def before_expiration_time?
83
+ (seconds_since_epoc - EPOC_TIME_LEEWAY_SECONDS) < jwt[:exp]
84
+ end
85
+
86
+ def verify_signature!
87
+ jwt.verify!(jwt_key)
88
+ end
89
+
90
+ end # JwtVerifier
91
+ end # AzureActiveDirectoryB2C
92
+ end # Strategies
93
+ end # OmniAuth
@@ -0,0 +1,26 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class AzureActiveDirectoryB2C
4
+ class Policy
5
+ include AzureActiveDirectoryB2C::PolicyOptions
6
+
7
+ attr_reader :application_identifier, :application_secret, :issuer, :tenant_name, :policy_name, :jwk_signing_algorithm, :jwk_signing_keys
8
+
9
+ def initialize(application_identifier:, application_secret:, issuer:, tenant_name:, policy_name:, jwk_signing_algorithm:, jwk_signing_keys:, scope: nil)
10
+ @application_identifier = application_identifier
11
+ @application_secret = application_secret
12
+ @issuer = issuer
13
+ @tenant_name = tenant_name
14
+ @policy_name = policy_name
15
+ @jwk_signing_algorithm = jwk_signing_algorithm
16
+ @jwk_signing_keys = jwk_signing_keys
17
+ @scope = *scope
18
+ end
19
+
20
+ def scope
21
+ @scope.any? ? @scope : super
22
+ end
23
+ end # Policy
24
+ end # AzureActiveDirectoryB2C
25
+ end # Strategies
26
+ end # OmniAuth
@@ -0,0 +1,97 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class AzureActiveDirectoryB2C
4
+ module PolicyOptions
5
+
6
+ def respond_to_missing?(method_name, *args)
7
+ self.class.instance_methods.include?("policy_#{method_name}".to_sym) || super
8
+ end
9
+
10
+ def method_missing(method_name, *args, &block)
11
+ policy_method_name = 'policy_%s' % method_name
12
+ if respond_to?(policy_method_name)
13
+ send(policy_method_name, *args, &block)
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ def policy_application_identifier
20
+ raise MissingOptionError, '`application_identifier` not defined'
21
+ end
22
+
23
+ def policy_application_secret
24
+ raise MissingOptionError, '`application_secret` not defined'
25
+ end
26
+
27
+ def policy_issuer
28
+ raise MissingOptionError, '`issuer` not defined'
29
+ end
30
+
31
+ def policy_tenant_name
32
+ raise MissingOptionError, '`tenant_name` not defined'
33
+ end
34
+
35
+ def policy_policy_name
36
+ raise MissingOptionError, '`policy_name` not defined'
37
+ end
38
+
39
+ def policy_host_name
40
+ 'https://login.microsoftonline.com/te/%s/%s' % [tenant_name, policy_name]
41
+ end
42
+
43
+ def policy_authorization_endpoint
44
+ '%s/oauth2/v2.0/authorize' % host_name
45
+ end
46
+
47
+ def policy_token_endpoint
48
+ '%s/oauth2/v2.0/token' % host_name
49
+ end
50
+
51
+ def policy_jwks_uri
52
+ '%s/discovery/v2.0/keys' % host_name
53
+ end
54
+
55
+ def policy_jwk_signing_algorithm
56
+ # this can be "discovered" from the `jwks_uri` property at .well-known/openid-configuration
57
+ 'RS256'.to_sym
58
+ end
59
+
60
+ def policy_id_token_signing_algorithm
61
+ policy_jwk_signing_algorithm
62
+ end
63
+
64
+ def policy_scope
65
+ [
66
+ :openid, # This requests an ID token
67
+ # :offline_access, # This requests a refresh token using Auth Code flows. See: https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-oauth-code).
68
+ # Request API permissions. See https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-access-tokens
69
+ ]
70
+ end
71
+
72
+ def policy_jwk_signing_keys
73
+ # public keys are listed at the url specified in the the `jwks_uri` property at .well-known/openid-configuration
74
+ # eg. https://login.microsoftonline.com/mipwtest.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_signupin
75
+ raise MissingOptionError, '`jwk_signing_keys` not defined'
76
+ end
77
+
78
+ def policy_default_client_options
79
+ {
80
+ identifier: application_identifier,
81
+ secret: application_secret,
82
+ authorization_endpoint: authorization_endpoint,
83
+ token_endpoint: token_endpoint,
84
+ jwks_uri: jwks_uri,
85
+ }
86
+ end
87
+
88
+ def policy_initialize_client(redirect_uri:, **override_options)
89
+ options = default_client_options.merge(override_options)
90
+ options[:redirect_uri] = redirect_uri
91
+ Client.new(options)
92
+ end
93
+
94
+ end # PolicyOptions
95
+ end # AzureActiveDirectoryB2C
96
+ end # Strategies
97
+ end # OmniAuth
@@ -0,0 +1,9 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class AzureActiveDirectoryB2C
4
+
5
+ VERSION = '0.2.0'
6
+
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-azure_active_directory_b2c
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Brent Jacobs
8
+ - NextFaze
9
+ - Meeco
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2017-10-27 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: omniauth
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.3'
29
+ - !ruby/object:Gem::Dependency
30
+ name: openid_connect
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.1'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.1'
43
+ - !ruby/object:Gem::Dependency
44
+ name: proc_evaluate
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '1.0'
57
+ description:
58
+ email: developers@meeco.me
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - lib/omniauth-azure_active_directory_b2c.rb
64
+ - lib/omniauth/strategies/azure_active_directory_b2c.rb
65
+ - lib/omniauth/strategies/azure_active_directory_b2c/authentication_request.rb
66
+ - lib/omniauth/strategies/azure_active_directory_b2c/authentication_response.rb
67
+ - lib/omniauth/strategies/azure_active_directory_b2c/client.rb
68
+ - lib/omniauth/strategies/azure_active_directory_b2c/jwt_validator.rb
69
+ - lib/omniauth/strategies/azure_active_directory_b2c/policy.rb
70
+ - lib/omniauth/strategies/azure_active_directory_b2c/policy_options.rb
71
+ - lib/omniauth/strategies/azure_active_directory_b2c/version.rb
72
+ homepage: https://github.com/Meeco/omniauth-azure_active_directory_b2c
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.6.12
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Azure AD B2C Strategy for OmniAuth.
96
+ test_files: []