-
Notifications
You must be signed in to change notification settings - Fork 107
The 'nonce' found in the jwt token did not match the expected nonce
Occasionally, OpenID Connect authentication can fail due to a known issue in the OWIN middleware. The issue is tracked here and here; a fix is already in place in the latest nightly builds of the OWIN packages and will be included in the next stable version of the OWIN middleware. If you would like to implement a workaround using the currently released stable version (3.0.0), follow the steps described below.
Note: This wiki is a temporary location for this workaround, and will be moved to a new location in the near future. Please do not rely on the AzureADSamples wikis for future workarounds and how-to's.
The issue occurs when a second OpenID Connect authentication challenge is issued before a first is completed. It results in the error message 'The 'nonce' found in the jwt token did not match the expected nonce.' Specifically, this error can occur when:
- A user browses to a site protected by Azure AD, attempts to sign in, and pauses at the AAD login page.
- The user opens a new tab in the same browser, navigates to the same site, attempts to sign in, and pauses once more at the AAD login page.
- The user returns to the first tab, completes authentication, and is returned to the site, receiving the
The 'nonce' found in the jwt...error.
The error occurs because the OWIN middleware uses the same cookie for temporarily persisting the nonce on each OpenID Connect authentication challenge. The second challenge overwrites the cookie set by the first challenge, so when the first challenge is completed, the persisted nonce and the nonce returned by Azure AD upon successful authentication do not match.
As a shortcut, it is possible to circumvent this error by setting the RequireNonce parameter as follows. However, this is not recommended as the nonce is used by OpenID Connect to mitigate several security threats.
new OpenIdConnectAuthenticationOptions
{
...
ProtocolValidator = new OpenIdConnectProtocolValidator
{
RequireNonce = false,
}
...
}
In order to fix the root cause of the issue, override the OWIN middleware to set a unique cookie on each authentication request as follows. First, attach the custom authentication middleware to the OWIN pipeline in your OWIN Startup class:
OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority
};
app.Use(typeof(MyOpenIDConnectAuthenticationMiddleware), app, options);
Next, extend the OpenIdConnectAuthenticationMiddleware to use a custom OpenIdConnectAuthenticationHandler:
public class MyOpenIDConnectAuthenticationMiddleware : OpenIdConnectAuthenticationMiddleware
{
private readonly ILogger _logger;
public MyOpenIDConnectAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, OpenIdConnectAuthenticationOptions options) : base(next, app, options)
{
_logger = app.CreateLogger<MyOpenIDConnectAuthenticationMiddleware>();
}
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new MyOpenIDConnectAuthenticationHandler(_logger);
}
}
Finally, extend OpenIdConnectAuthenticationHandler to set request-specific cookies:
public class MyOpenIDConnectAuthenticationHandler : OpenIdConnectAuthenticationHandler
{
private const string NonceProperty = "nonceProperty";
private const string NoncePrefix = OpenIdConnectAuthenticationDefaults.CookiePrefix + "nonce.";
private readonly ILogger _logger;
public MyOpenIDConnectAuthenticationHandler(ILogger logger) : base(logger)
{
_logger = logger;
}
protected override void AddNonceToMessage(OpenIdConnectMessage message)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
AuthenticationProperties properties = new AuthenticationProperties();
string nonce = Options.ProtocolValidator.GenerateNonce();
properties.Dictionary.Add(NonceProperty, nonce);
message.Nonce = nonce;
//computing the hash of nonce and appending it to the cookie name
string nonceKey = GetNonceKey(nonce);
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsSecure,
};
string nonceId = Convert.ToBase64String(Encoding.UTF8.GetBytes((Options.StateDataFormat.Protect(properties))));
Response.Cookies.Append(nonceKey, nonceId, cookieOptions);
}
protected override string RetrieveNonce(OpenIdConnectMessage message)
{
if (message.IdToken == null)
{
return null;
}
JwtSecurityToken token = new JwtSecurityToken(message.IdToken);
if (token == null)
{
return null;
}
//computing the hash of nonce and appending it to the cookie name
string nonceKey = GetNonceKey(token.Payload.Nonce);
string nonceCookie = Request.Cookies[nonceKey];
if (string.IsNullOrWhiteSpace(nonceCookie))
{
_logger.WriteWarning("The nonce cookie was not found.");
return null;
}
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsSecure
};
Response.Cookies.Delete(nonceKey, cookieOptions);
string nonce = null;
AuthenticationProperties nonceProperties = Options.StateDataFormat.Unprotect(Encoding.UTF8.GetString(Convert.FromBase64String(nonceCookie)));
if (nonceProperties != null)
{
nonceProperties.Dictionary.TryGetValue(NonceProperty, out nonce);
}
else
{
_logger.WriteWarning("Failed to un-protect the nonce cookie.");
}
return nonce;
}
private string GetNonceKey(string nonce)
{
using(HashAlgorithm hash = SHA256.Create())
{
return NoncePrefix + Convert.ToBase64String(hash.ComputeHash(Encoding.UTF8.GetBytes(nonce)));
}
}
}
Thanks to @tushargupta51 for the quick fix. For his entire pull request, please see here.