omniauth-azure-activedirectory
omniauth-azure-activedirectory copied to clipboard
Returned nonce did not match.
When I try to login using activedirectory I get the follow error and stacktrace:
JWT::DecodeError (Returned nonce did not match.):
omniauth-azure-activedirectory (1.0.0) lib/omniauth/strategies/azure_activedirectory.rb:289:in `validate_and_parse_id_token'
omniauth-azure-activedirectory (1.0.0) lib/omniauth/strategies/azure_activedirectory.rb:92:in `callback_phase'
omniauth (1.3.1) lib/omniauth/strategy.rb:227:in `callback_call'
omniauth (1.3.1) lib/omniauth/strategy.rb:184:in `call!'
omniauth (1.3.1) lib/omniauth/strategy.rb:164:in `call'
omniauth (1.3.1) lib/omniauth/strategy.rb:408:in `call_app!'
omniauth (1.3.1) lib/omniauth/strategy.rb:362:in `callback_phase'
omniauth-azure-activedirectory (1.0.0) lib/omniauth/strategies/azure_activedirectory.rb:94:in `callback_phase'
omniauth (1.3.1) lib/omniauth/strategy.rb:227:in `callback_call'
omniauth (1.3.1) lib/omniauth/strategy.rb:184:in `call!'
omniauth (1.3.1) lib/omniauth/strategy.rb:164:in `call'
omniauth (1.3.1) lib/omniauth/builder.rb:63:in `call'
warden (1.2.6) lib/warden/manager.rb:35:in `block in call'
warden (1.2.6) lib/warden/manager.rb:34:in `catch'
warden (1.2.6) lib/warden/manager.rb:34:in `call'
It fails here https://github.com/AzureAD/omniauth-azure-activedirectory/blob/master/lib/omniauth/strategies/azure_activedirectory.rb#L289
I'm trying to use ominauth-azure_activedirectory in combination with Devise, my config looks like this:
config.omniauth :azure_activedirectory, ENV['AAD_CLIENT_ID'], ENV['AAD_TENANT']
Is my setup wrong? Or is it a bug in this library?
I am also receiving this bug On the exact same line ... any update on this?
Additional note: Very odd behavior I have just uncovered. I seem to be able to reproduce problem in Safari and Chrome but not in Firefox ... This is very confusing!
Ok I found a workaround. The fact that browsers seemed to be the variance I guessed it was tied to cookies.
I changed the code and added the following:
use Rack::Session::Cookie, :key => 'rack.session', :path => '/', :expire_after => 14400, :secret => 'your_secret_goes_here'
This seems to work just fine!
@adammartin is 'your_secret_goes_here' the nonce (or nonce related)?
@adammartin Setting Rails to use Rack::Session::Cookie in application.rb
seems to help indeed.
module RailsApp
class Application < Rails::Application
config.middleware.use Rack::Session::Cookie, key: 'rack.session', path: '/', expire_after: 14400, secret: ENV['SECRET_KEY_BASE']
end
end
I have the same issue here, anyone has a solution using rails's ActionDispatch::Session::CookieStore
? Do I have to use rack ?
Edit : worked fine with
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
in application.rb
I tested and found the session[:session_id]
is different once Azure AD redirects back to your application, but not if you have FireFox. Perhaps security adjustments were made to Chrome and other browsers to reset the session in some cases?
If you are using Rails, this patch should solve the problem and doesn't require messing with middleware. Note the patch is based on the more popular fork of this repo. Although it seems long it actually replaces very little code.
Gemfile:
gem 'omniauth-azure-activedirectory', github: 'planio-gmbh/omniauth-azure-activedirectory
Create file config/initializers/azure_ad_patch.rb:
#
# The original gem writes and reads the nonce from the session. This works on firefox but not on chrome where the session
# is reset for an unknown reason once you the user is redirected from Azure back to the application. This patch places the
# nonce in the Rails cache which should still be secure since the purpose of a nonce is "number used once" which is still
# the case with this patch.
#
OmniAuth::Strategies::AzureActiveDirectory.class_eval do
def nonce_cache_key(nonce)
"omniauth_azure_activedirectory:nonce:#{nonce}"
end
def claim_nonce!(nonce)
if Rails.cache.read(nonce_cache_key(nonce))
Rails.cache.delete(nonce_cache_key(nonce))
true
else
false
end
end
def new_nonce
nonce = SecureRandom.uuid
Rails.cache.write(nonce_cache_key(nonce), true, expires_in: 7.days)
nonce
end
def validate_and_parse_id_token(id_token)
# The second parameter is the public key to verify the signature.
# However, that key is overridden by the value of the executed block
# if one is present.
#
# If you're thinking that this looks ugly with the raw nil and boolean,
# see https://github.com/jwt/ruby-jwt/issues/59.
jwt_claims, jwt_header =
JWT.decode(id_token, nil, true, verify_options) do |header|
# There should always be one key from the discovery endpoint that
# matches the id in the JWT header.
unless key = signing_keys.find{|k|
k['kid'] == header['kid']
}
fail JWT::VerificationError, 'No keys from key endpoint match the id token'
end
# The key also contains other fields, such as n and e, that are
# redundant. x5c is sufficient to verify the id token.
if x5c = key['x5c'] and !x5c.empty?
OpenSSL::X509::Certificate.new(JWT::Base64.url_decode(x5c.first)).public_key
# no x5c, so we resort to e and n
elsif exp = key['e'] and mod = key['n']
key = OpenSSL::PKey::RSA.new
mod = openssl_bn_for mod
exp = openssl_bn_for exp
if key.respond_to? :set_key
# Ruby 2.4 ff
key.set_key mod, exp, nil
else
# Ruby < 2.4
key.e = exp
key.n = mod
end
key.public_key
else
fail JWT::VerificationError, 'Key has no info for verification'
end
end
return jwt_claims, jwt_header if claim_nonce!(jwt_claims['nonce'])
fail JWT::DecodeError, 'Returned nonce did not match.'
end
end
Did some googling and tinkering a bit today, and I really like the solution that @pierre-pretorius suggested. It works without having to set SameSite to :none
on the session cookie.
In order to work in development though, you will need to run bundle exec rails dev:cache
, which may not be desirable. I'm wondering if the patch could be modified to handle that some other way when Rails.application.config.action_controller.perform_caching
is false (perhaps using Pstore ...is there a better option?).
I also don't know where to submit a patch. @Microsoft / @AzureAD doesn't seem to care about this repo of theirs, and according to @davevanfleet's comment on #54, @planio-gmbh's fork is no longer super active, either.
If you submit a PR on my repo I'm happy to merge it in. I don't mind maintaining the repo (but could probably use some help updating the original tests, since most of them are currently failing after updating the dependencies to allow this to work with the more up-to-date google omniauth gem - once the tests and CI/CD are back up and running smoothly maintaining the repo will be fine). https://github.com/davevanfleet/omniauth-activedirectory
This happens with Rails 6.1+ because it uses a SameSite policy for cookies by default. This can be bypassed for AD authentication by adding the following to your application.rb
:
config.action_dispatch.cookies_same_site_protection = lambda { |request|
# No SameSite attribute for azureactivedirectory since it uses
# session cookie to send a nonce and verify it on the callback.
return if request.fullpath.include?("azureactivedirectory")
:lax
}
This works without having to use any other workarounds or patching the strategy to use cache.
@pulkit110 I feel like you just saved half a day of my life with this - thank you.