diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/INonceCache.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/INonceCache.cs
index a1f2d35437..a56117560c 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/INonceCache.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/INonceCache.cs
@@ -5,8 +5,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
public interface INonceCache
{
- string AddNonce(string nonce);
+ bool TryAddNonce(string nonce);
+
bool TryRemoveNonce(string nonce);
- bool HasNonce(string nonce);
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationDefaults.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationDefaults.cs
index 21dc57f482..cecc57a74e 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationDefaults.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationDefaults.cs
@@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
///
/// The prefix used to for the a nonce in the cookie
///
- internal const string CookieNoncePrefix = ".AspNet.OpenIdConnect.Nonce.";
+ public const string CookieNoncePrefix = ".AspNet.OpenIdConnect.Nonce.";
///
/// The property for the RedirectUri that was used when asking for a 'authorizationCode'
@@ -36,6 +36,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
///
/// Constant used to identify state in openIdConnect protocal message
///
- internal const string AuthenticationPropertiesKey = "OpenIdConnect.AuthenticationProperties";
+ public const string AuthenticationPropertiesKey = "OpenIdConnect.AuthenticationProperties";
}
}
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationExtensions.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationExtensions.cs
index 989aac48e1..4ced947e2f 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationExtensions.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationExtensions.cs
@@ -26,5 +26,16 @@ namespace Microsoft.AspNet.Builder
Name = optionsName
});
}
+
+ ///
+ /// Adds the into the ASP.NET runtime.
+ ///
+ /// The application builder
+ /// Options which control the processing of the OpenIdConnect protocol and token validation.
+ /// The application builder
+ public static IApplicationBuilder UseOpenIdConnectAuthentication(this IApplicationBuilder app, IOptions options)
+ {
+ return app.UseMiddleware(options);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs
index 32f7a69c0f..c0efcd0f61 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs
@@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
// Set End_Session_Endpoint in order:
// 1. properties.Redirect
- // 2. Options.Wreply
+ // 2. Options.PostLogoutRedirectUri
var properties = new AuthenticationProperties(signout.Properties);
if (!string.IsNullOrEmpty(properties.RedirectUri))
{
@@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
string redirectUri = notification.ProtocolMessage.CreateLogoutRequestUrl();
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
{
- _logger.LogWarning("The logout redirect URI is malformed: {0}", (redirectUri ?? "null"));
+ _logger.LogWarning(Resources.OIDCH_0051_RedirectUriLogoutIsNotWellFormed, redirectUri);
}
Response.Redirect(redirectUri);
@@ -115,28 +115,37 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// Responds to a 401 Challenge. Sends an OpenIdConnect message to the 'identity authority' to obtain an identity.
///
///
+ /// Uses log id's OIDCH-0026 - OIDCH-0050, next num: 37
protected override async Task ApplyResponseChallengeAsync()
{
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug(Resources.OIDCH_0026_ApplyResponseChallengeAsync, this.GetType());
+ }
+
if (ShouldConvertChallengeToForbidden())
{
+ _logger.LogDebug(Resources.OIDCH_0027_401_ConvertedTo_403);
Response.StatusCode = 403;
return;
}
if (Response.StatusCode != 401)
{
+ _logger.LogDebug(Resources.OIDCH_0028_StatusCodeNot401, Response.StatusCode);
return;
}
// When Automatic should redirect on 401 even if there wasn't an explicit challenge.
if (ChallengeContext == null && !Options.AutomaticAuthentication)
{
+ _logger.LogDebug(Resources.OIDCH_0029_ChallengeContextEqualsNull);
return;
}
- // order for redirect_uri
+ // order for local RedirectUri
// 1. challenge.Properties.RedirectUri
- // 2. CurrentUri
+ // 2. CurrentUri if Options.DefaultToCurrentUriOnRedirect is true)
AuthenticationProperties properties;
if (ChallengeContext == null)
{
@@ -147,12 +156,22 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
properties = new AuthenticationProperties(ChallengeContext.Properties);
}
- if (string.IsNullOrEmpty(properties.RedirectUri))
+ if (!string.IsNullOrWhiteSpace(properties.RedirectUri))
{
+ _logger.LogDebug(Resources.OIDCH_0030_Using_Properties_RedirectUri, properties.RedirectUri);
+ }
+ else if (Options.DefaultToCurrentUriOnRedirect)
+ {
+ _logger.LogDebug(Resources.OIDCH_0032_UsingCurrentUriRedirectUri, CurrentUri);
properties.RedirectUri = CurrentUri;
}
- // this value will be passed to the AuthorizationCodeReceivedNotification
+ if (!string.IsNullOrWhiteSpace(Options.RedirectUri))
+ {
+ _logger.LogDebug(Resources.OIDCH_0031_Using_Options_RedirectUri, Options.RedirectUri);
+ }
+
+ // When redeeming a 'code' for an AccessToken, this value is needed
if (!string.IsNullOrWhiteSpace(Options.RedirectUri))
{
properties.Dictionary.Add(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey, Options.RedirectUri);
@@ -163,14 +182,15 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
- OpenIdConnectMessage openIdConnectMessage = new OpenIdConnectMessage
+ var message = new OpenIdConnectMessage
{
ClientId = Options.ClientId,
- IssuerAddress = _configuration == null ? string.Empty : (_configuration.AuthorizationEndpoint ?? string.Empty),
+ IssuerAddress = _configuration?.AuthorizationEndpoint ?? string.Empty,
RedirectUri = Options.RedirectUri,
+ // [brentschmaltz] - this should be a property on RedirectToIdentityProviderNotification not on the OIDCMessage.
RequestType = OpenIdConnectRequestType.AuthenticationRequest,
Resource = Options.Resource,
- ResponseMode = OpenIdConnectResponseModes.FormPost,
+ ResponseMode = Options.ResponseMode,
ResponseType = Options.ResponseType,
Scope = Options.Scope,
State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + Uri.EscapeDataString(Options.StateDataFormat.Protect(properties))
@@ -178,33 +198,45 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
if (Options.ProtocolValidator.RequireNonce)
{
- openIdConnectMessage.Nonce = Options.ProtocolValidator.GenerateNonce();
+ message.Nonce = Options.ProtocolValidator.GenerateNonce();
if (Options.NonceCache != null)
{
- Options.NonceCache.AddNonce(openIdConnectMessage.Nonce);
+ if (!Options.NonceCache.TryAddNonce(message.Nonce))
+ {
+ _logger.LogError(Resources.OIDCH_0033_TryAddNonceFailed, message.Nonce);
+ throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0033_TryAddNonceFailed, message.Nonce));
+ }
}
else
{
- RememberNonce(openIdConnectMessage.Nonce);
+ WriteNonceCookie(message.Nonce);
}
}
- var notification = new RedirectToIdentityProviderNotification(Context, Options)
+ var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification(Context, Options)
{
- ProtocolMessage = openIdConnectMessage
+ ProtocolMessage = message
};
- await Options.Notifications.RedirectToIdentityProvider(notification);
- if (!notification.HandledResponse)
+ await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
+ if (redirectToIdentityProviderNotification.HandledResponse)
{
- string redirectUri = notification.ProtocolMessage.CreateAuthenticationRequestUrl();
- if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
- {
- _logger.LogWarning("Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: {0}", (redirectUri ?? "null"));
- }
-
- Response.Redirect(redirectUri);
+ _logger.LogInformation(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
+ return;
}
+ else if (redirectToIdentityProviderNotification.Skipped)
+ {
+ _logger.LogInformation(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
+ return;
+ }
+
+ string redirectUri = redirectToIdentityProviderNotification.ProtocolMessage.CreateAuthenticationRequestUrl();
+ if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
+ {
+ _logger.LogWarning(Resources.OIDCH_0036_UriIsNotWellFormed, redirectUri);
+ }
+
+ Response.Redirect(redirectUri);
}
protected override AuthenticationTicket AuthenticateCore()
@@ -216,15 +248,21 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// Invoked to process incoming OpenIdConnect messages.
///
/// An if successful.
+ /// Uses log id's OIDCH-0000 - OIDCH-0025
protected override async Task AuthenticateCoreAsync()
{
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug(Resources.OIDCH_0000_AuthenticateCoreAsync, this.GetType());
+ }
+
// Allow login to be constrained to a specific path. Need to make this runtime configurable.
if (Options.CallbackPath.HasValue && Options.CallbackPath != (Request.PathBase + Request.Path))
{
return null;
}
- OpenIdConnectMessage openIdConnectMessage = null;
+ OpenIdConnectMessage message = null;
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
@@ -235,180 +273,215 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
IFormCollection form = await Request.ReadFormAsync();
Request.Body.Seek(0, SeekOrigin.Begin);
-
- openIdConnectMessage = new OpenIdConnectMessage(form);
+ message = new OpenIdConnectMessage(form);
}
- if (openIdConnectMessage == null)
+ if (message == null)
{
return null;
}
try
{
- var messageReceivedNotification = new MessageReceivedNotification(Context, Options)
+ if (_logger.IsEnabled(LogLevel.Debug))
{
- ProtocolMessage = openIdConnectMessage
- };
+ _logger.LogDebug(Resources.OIDCH_0001_MessageReceived, message.BuildRedirectUrl());
+ }
+
+ var messageReceivedNotification =
+ new MessageReceivedNotification(Context, Options)
+ {
+ ProtocolMessage = message
+ };
await Options.Notifications.MessageReceived(messageReceivedNotification);
if (messageReceivedNotification.HandledResponse)
{
+ _logger.LogInformation(Resources.OIDCH_0002_MessageReceivedNotificationHandledResponse);
return messageReceivedNotification.AuthenticationTicket;
}
if (messageReceivedNotification.Skipped)
{
+ _logger.LogInformation(Resources.OIDCH_0003_MessageReceivedNotificationSkipped);
return null;
}
- // runtime always adds state, if we don't find it OR we failed to 'unprotect' it this is not a message we
- // should process.
- AuthenticationProperties properties = GetPropertiesFromState(openIdConnectMessage.State);
+ // runtime always adds state, if we don't find it OR we failed to 'unprotect' it this is not a message we should process.
+ if (string.IsNullOrWhiteSpace(message.State))
+ {
+ _logger.LogError(Resources.OIDCH_0004_MessageStateIsNullOrWhiteSpace);
+ return null;
+ }
+
+ var properties = GetPropertiesFromState(message.State);
if (properties == null)
{
- _logger.LogWarning("The state field is missing or invalid.");
+ _logger.LogError(Resources.OIDCH_0005_MessageStateIsInvalid);
return null;
}
// devs will need to hook AuthenticationFailedNotification to avoid having 'raw' runtime errors displayed to users.
- if (!string.IsNullOrWhiteSpace(openIdConnectMessage.Error))
+ if (!string.IsNullOrWhiteSpace(message.Error))
{
- throw new OpenIdConnectProtocolException(
- string.Format(CultureInfo.InvariantCulture,
- openIdConnectMessage.Error,
- Resources.Exception_OpenIdConnectMessageError, openIdConnectMessage.ErrorDescription ?? string.Empty, openIdConnectMessage.ErrorUri ?? string.Empty));
+ _logger.LogError(Resources.OIDCH_0006_MessageErrorNotNull, message.Error);
+ throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageErrorNotNull, message.Error));
}
- // code is only accepted with id_token, in this version, hence check for code is inside this if
- // OpenIdConnect protocol allows a Code to be received without the id_token
- if (string.IsNullOrWhiteSpace(openIdConnectMessage.IdToken))
- {
- _logger.LogWarning("The id_token is missing.");
- return null;
- }
-
- var securityTokenReceivedNotification = new SecurityTokenReceivedNotification(Context, Options)
- {
- ProtocolMessage = openIdConnectMessage
- };
-
- await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
- if (securityTokenReceivedNotification.HandledResponse)
- {
- return securityTokenReceivedNotification.AuthenticationTicket;
- }
-
- if (securityTokenReceivedNotification.Skipped)
- {
- return null;
- }
+ AuthenticationTicket ticket = null;
+ JwtSecurityToken jwt = null;
if (_configuration == null && Options.ConfigurationManager != null)
{
+ _logger.LogDebug(Resources.OIDCH_0007_UpdatingConfiguration);
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
- // Copy and augment to avoid cross request race conditions for updated configurations.
- TokenValidationParameters validationParameters = Options.TokenValidationParameters.Clone();
- if (_configuration != null)
+ // OpenIdConnect protocol allows a Code to be received without the id_token
+ if (!string.IsNullOrWhiteSpace(message.IdToken))
{
- if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer))
- {
- validationParameters.ValidIssuer = _configuration.Issuer;
- }
- else if (!string.IsNullOrWhiteSpace(_configuration.Issuer))
- {
- validationParameters.ValidIssuers = (validationParameters.ValidIssuers == null ? new[] { _configuration.Issuer } : validationParameters.ValidIssuers.Concat(new[] { _configuration.Issuer }));
- }
-
- validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys == null ? _configuration.SigningKeys : validationParameters.IssuerSigningKeys.Concat(_configuration.SigningKeys));
- }
-
- AuthenticationTicket ticket;
- SecurityToken validatedToken = null;
- ClaimsPrincipal principal = null;
- JwtSecurityToken jwt = null;
-
- foreach (var validator in Options.SecurityTokenValidators)
- {
- if (validator.CanReadToken(openIdConnectMessage.IdToken))
- {
- principal = validator.ValidateToken(openIdConnectMessage.IdToken, validationParameters, out validatedToken);
- jwt = validatedToken as JwtSecurityToken;
- if (jwt == null)
+ _logger.LogDebug(Resources.OIDCH_0020_IdTokenReceived, message.IdToken);
+ var securityTokenReceivedNotification =
+ new SecurityTokenReceivedNotification(Context, Options)
{
- throw new InvalidOperationException("Validated Security Token must be a JwtSecurityToken was: " + (validatedToken == null ? "null" : validatedToken.GetType().ToString()));
+ ProtocolMessage = message
+ };
+
+ await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
+ if (securityTokenReceivedNotification.HandledResponse)
+ {
+ _logger.LogInformation(Resources.OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse);
+ return securityTokenReceivedNotification.AuthenticationTicket;
+ }
+
+ if (securityTokenReceivedNotification.Skipped)
+ {
+ _logger.LogInformation(Resources.OIDCH_0009_SecurityTokenReceivedNotificationSkipped);
+ return null;
+ }
+
+ // Copy and augment to avoid cross request race conditions for updated configurations.
+ TokenValidationParameters validationParameters = Options.TokenValidationParameters.Clone();
+ if (_configuration != null)
+ {
+ if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer))
+ {
+ validationParameters.ValidIssuer = _configuration.Issuer;
+ }
+ else if (!string.IsNullOrWhiteSpace(_configuration.Issuer))
+ {
+ validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(new[] { _configuration.Issuer }) ?? new[] { _configuration.Issuer };
+ }
+
+ validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys) ?? _configuration.SigningKeys;
+ }
+
+ SecurityToken validatedToken = null;
+ ClaimsPrincipal principal = null;
+ foreach (var validator in Options.SecurityTokenValidators)
+ {
+ if (validator.CanReadToken(message.IdToken))
+ {
+ principal = validator.ValidateToken(message.IdToken, validationParameters, out validatedToken);
+ jwt = validatedToken as JwtSecurityToken;
+ if (jwt == null)
+ {
+ _logger.LogError(Resources.OIDCH_0010_ValidatedSecurityTokenNotJwt, validatedToken?.GetType());
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0010_ValidatedSecurityTokenNotJwt, validatedToken?.GetType()));
+ }
}
}
- }
- if (validatedToken == null)
- {
- throw new InvalidOperationException("No SecurityTokenValidator found for token: " + openIdConnectMessage.IdToken);
- }
-
- ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);
- if (!string.IsNullOrWhiteSpace(openIdConnectMessage.SessionState))
- {
- ticket.Properties.Dictionary[OpenIdConnectSessionProperties.SessionState] = openIdConnectMessage.SessionState;
- }
-
- if (_configuration != null && !string.IsNullOrWhiteSpace(_configuration.CheckSessionIframe))
- {
- ticket.Properties.Dictionary[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe;
- }
-
- if (Options.UseTokenLifetime)
- {
- // Override any session persistence to match the token lifetime.
- DateTime issued = validatedToken.ValidFrom;
- if (issued != DateTime.MinValue)
+ if (validatedToken == null)
{
- ticket.Properties.IssuedUtc = issued;
+ _logger.LogError(Resources.OIDCH_0011_UnableToValidateToken, message.IdToken);
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0011_UnableToValidateToken, message.IdToken));
}
- DateTime expires = validatedToken.ValidTo;
- if (expires != DateTime.MinValue)
+ ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);
+ if (!string.IsNullOrWhiteSpace(message.SessionState))
{
- ticket.Properties.ExpiresUtc = expires;
+ ticket.Properties.Dictionary[OpenIdConnectSessionProperties.SessionState] = message.SessionState;
}
- ticket.Properties.AllowRefresh = false;
+ if (_configuration != null && !string.IsNullOrWhiteSpace(_configuration.CheckSessionIframe))
+ {
+ ticket.Properties.Dictionary[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe;
+ }
+
+ // Rename?
+ if (Options.UseTokenLifetime)
+ {
+ DateTime issued = validatedToken.ValidFrom;
+ if (issued != DateTime.MinValue)
+ {
+ ticket.Properties.IssuedUtc = issued;
+ }
+
+ DateTime expires = validatedToken.ValidTo;
+ if (expires != DateTime.MinValue)
+ {
+ ticket.Properties.ExpiresUtc = expires;
+ }
+ }
+
+ var securityTokenValidatedNotification =
+ new SecurityTokenValidatedNotification(Context, Options)
+ {
+ AuthenticationTicket = ticket,
+ ProtocolMessage = message
+ };
+
+ await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);
+ if (securityTokenValidatedNotification.HandledResponse)
+ {
+ _logger.LogInformation(Resources.OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse);
+ return securityTokenValidatedNotification.AuthenticationTicket;
+ }
+
+ if (securityTokenValidatedNotification.Skipped)
+ {
+ _logger.LogInformation(Resources.OIDCH_0013_SecurityTokenValidatedNotificationSkipped);
+ return null;
+ }
+
+ string nonce = jwt.Payload.Nonce;
+ if (Options.NonceCache != null)
+ {
+ // if the nonce cannot be removed, it was used
+ if (!Options.NonceCache.TryRemoveNonce(nonce))
+ {
+ nonce = null;
+ }
+ }
+ else
+ {
+ nonce = ReadNonceCookie(nonce);
+ }
+
+ var protocolValidationContext = new OpenIdConnectProtocolValidationContext
+ {
+ AuthorizationCode = message.Code,
+ Nonce = nonce,
+ };
+
+ Options.ProtocolValidator.Validate(jwt, protocolValidationContext);
}
- var securityTokenValidatedNotification = new SecurityTokenValidatedNotification(Context, Options)
+ if (message.Code != null)
{
- AuthenticationTicket = ticket,
- ProtocolMessage = openIdConnectMessage
- };
+ _logger.LogDebug(Resources.OIDCH_0014_CodeReceived, message.Code);
+ if (ticket == null)
+ {
+ ticket = new AuthenticationTicket(properties, Options.AuthenticationScheme);
+ }
- await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);
- if (securityTokenValidatedNotification.HandledResponse)
- {
- return securityTokenValidatedNotification.AuthenticationTicket;
- }
-
- if (securityTokenValidatedNotification.Skipped)
- {
- return null;
- }
-
- var protocolValidationContext = new OpenIdConnectProtocolValidationContext
- {
- AuthorizationCode = openIdConnectMessage.Code,
- Nonce = RetrieveNonce(jwt.Payload.Nonce),
- };
-
- Options.ProtocolValidator.Validate(jwt, protocolValidationContext);
- if (openIdConnectMessage.Code != null)
- {
var authorizationCodeReceivedNotification = new AuthorizationCodeReceivedNotification(Context, Options)
{
AuthenticationTicket = ticket,
- Code = openIdConnectMessage.Code,
+ Code = message.Code,
JwtSecurityToken = jwt,
- ProtocolMessage = openIdConnectMessage,
+ ProtocolMessage = message,
RedirectUri = ticket.Properties.Dictionary.ContainsKey(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey) ?
ticket.Properties.Dictionary[OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey] : string.Empty,
};
@@ -416,11 +489,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
await Options.Notifications.AuthorizationCodeReceived(authorizationCodeReceivedNotification);
if (authorizationCodeReceivedNotification.HandledResponse)
{
+ _logger.LogInformation(Resources.OIDCH_0015_CodeReceivedNotificationHandledResponse);
return authorizationCodeReceivedNotification.AuthenticationTicket;
}
if (authorizationCodeReceivedNotification.Skipped)
{
+ _logger.LogInformation(Resources.OIDCH_0016_CodeReceivedNotificationSkipped);
return null;
}
}
@@ -429,7 +504,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
catch (Exception exception)
{
- _logger.LogError("Exception occurred while processing message", exception);
+ _logger.LogError(Resources.OIDCH_0017_ExceptionOccurredWhileProcessingMessage, exception);
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification.
if (Options.RefreshOnIssuerKeyNotFound && exception.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
@@ -437,20 +512,23 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
Options.ConfigurationManager.RequestRefresh();
}
- var authenticationFailedNotification = new AuthenticationFailedNotification(Context, Options)
- {
- ProtocolMessage = openIdConnectMessage,
- Exception = exception
- };
+ var authenticationFailedNotification =
+ new AuthenticationFailedNotification(Context, Options)
+ {
+ ProtocolMessage = message,
+ Exception = exception
+ };
await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);
if (authenticationFailedNotification.HandledResponse)
{
+ _logger.LogInformation(Resources.OIDCH_0018_AuthenticationFailedNotificationHandledResponse);
return authenticationFailedNotification.AuthenticationTicket;
}
if (authenticationFailedNotification.Skipped)
{
+ _logger.LogInformation(Resources.OIDCH_0019_AuthenticationFailedNotificationSkipped);
return null;
}
@@ -464,7 +542,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// the nonce to remember.
/// is called to add a cookie with the name: 'OpenIdConnectAuthenticationDefaults.Nonce + (nonce)'.
/// The value of the cookie is: "N".
- private void RememberNonce(string nonce)
+ private void WriteNonceCookie(string nonce)
{
if (string.IsNullOrWhiteSpace(nonce))
{
@@ -484,13 +562,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
///
/// Searches for a matching nonce.
///
- /// the nonce that was found in the jwt token.
- /// 'nonceExpectedValue' if a cookie is found that matches, null otherwise.
+ /// the nonce that we are looking for.
+ /// echos 'nonce' if a cookie is found that matches, null otherwise.
/// Examine that start with the prefix: 'OpenIdConnectAuthenticationDefaults.Nonce'.
/// is used to obtain the actual 'nonce'. If the nonce is found, then is called.
- private string RetrieveNonce(string nonceExpectedValue)
+ private string ReadNonceCookie(string nonce)
{
- if (nonceExpectedValue == null)
+ if (nonce == null)
{
return null;
}
@@ -502,7 +580,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
try
{
string nonceDecodedValue = Options.StringDataFormat.Unprotect(nonceKey.Substring(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix.Length, nonceKey.Length - OpenIdConnectAuthenticationDefaults.CookieNoncePrefix.Length));
- if (nonceDecodedValue == nonceExpectedValue)
+ if (nonceDecodedValue == nonce)
{
var cookieOptions = new CookieOptions
{
@@ -511,7 +589,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
};
Response.Cookies.Delete(nonceKey, cookieOptions);
- return nonceExpectedValue;
+ return nonce;
}
}
catch (Exception ex)
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
index e58b7fff85..ee8eec0e5a 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
@@ -30,9 +30,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
///
/// Initializes a
///
- /// The next middleware in the ASP.NET pipeline to invoke
- /// The ASP.NET application
- /// Configuration options for the middleware
+ /// The next middleware in the ASP.NET pipeline to invoke.
+ /// provider for creating a data protector.
+ /// factory for creating a .
+ /// a instance that will supply
+ /// if configureOptions is null.
+ /// a instance that will be passed to an instance of
+ /// that is retrieved by calling where string == provides runtime configuration.
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
public OpenIdConnectAuthenticationMiddleware(
[NotNull] RequestDelegate next,
@@ -40,21 +44,15 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
[NotNull] ILoggerFactory loggerFactory,
[NotNull] IOptions externalOptions,
[NotNull] IOptions options,
- ConfigureOptions configureOptions)
+ ConfigureOptions configureOptions = null)
: base(next, options, configureOptions)
{
_logger = loggerFactory.CreateLogger();
-
- if (string.IsNullOrEmpty(Options.SignInScheme))
+ if (string.IsNullOrEmpty(Options.SignInScheme) && !string.IsNullOrEmpty(externalOptions.Options.SignInScheme))
{
Options.SignInScheme = externalOptions.Options.SignInScheme;
}
- if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.AuthenticationType))
- {
- Options.TokenValidationParameters.AuthenticationType = Options.AuthenticationScheme;
- }
-
if (Options.StateDataFormat == null)
{
var dataProtector = dataProtectionProvider.CreateProtector(
@@ -152,7 +150,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
- throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
+ throw new InvalidOperationException(Resources.OIDCH_0102_ExceptionValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationOptions.cs
index f7bfde804b..e5a3793d38 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationOptions.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationOptions.cs
@@ -52,32 +52,31 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions.set_Caption(System.String)", Justification = "Not a LOC field")]
public OpenIdConnectAuthenticationOptions(string authenticationScheme)
{
- // REVIEW: why was this active by default??
- //AuthenticationMode = AuthenticationMode.Active;
AuthenticationScheme = authenticationScheme;
BackchannelTimeout = TimeSpan.FromMinutes(1);
Caption = OpenIdConnectAuthenticationDefaults.Caption;
ProtocolValidator = new OpenIdConnectProtocolValidator();
RefreshOnIssuerKeyNotFound = true;
+ ResponseMode = OpenIdConnectResponseModes.FormPost;
ResponseType = OpenIdConnectResponseTypes.CodeIdToken;
Scope = OpenIdConnectScopes.OpenIdProfile;
TokenValidationParameters = new TokenValidationParameters();
UseTokenLifetime = true;
}
+ ///
+ /// Gets or sets the expected audience for any received JWT token.
+ ///
+ ///
+ /// The expected audience for any received JWT token.
+ ///
+ public string Audience { get; set; }
+
///
/// Gets or sets the Authority to use when making OpenIdConnect calls.
///
public string Authority { get; set; }
- ///
- /// An optional constrained path on which to process the authentication callback.
- /// If not provided and RedirectUri is available, this value will be generated from RedirectUri.
- ///
- /// If you set this value, then the will only listen for posts at this address.
- /// If the IdentityProvider does not post to this address, you may end up in a 401 -> IdentityProvider -> Client -> 401 -> ...
- public PathString CallbackPath { get; set; }
-
#if DNX451
///
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
@@ -112,7 +111,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
if (value <= TimeSpan.Zero)
{
- throw new ArgumentOutOfRangeException("BackchannelTimeout", value, Resources.ArgsException_BackchallelLessThanZero);
+ throw new ArgumentOutOfRangeException("BackchannelTimeout", value, Resources.OIDCH_0101_BackChallnelLessThanZero);
}
_backchannelTimeout = value;
@@ -128,6 +127,14 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
set { Description.Caption = value; }
}
+ ///
+ /// An optional constrained path on which to process the authentication callback.
+ /// If not provided and RedirectUri is available, this value will be generated from RedirectUri.
+ ///
+ /// If you set this value, then the will only listen for posts at this address.
+ /// If the IdentityProvider does not post to this address, you may end up in a 401 -> IdentityProvider -> Client -> 401 -> ...
+ public PathString CallbackPath { get; set; }
+
///
/// Gets or sets the 'client_id'.
///
@@ -145,11 +152,16 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
public OpenIdConnectConfiguration Configuration { get; set; }
///
- /// The OpenIdConnect protocol http://openid.net/specs/openid-connect-core-1_0.html
- /// recommends adding a nonce to a request as a mitigation against replay attacks when requesting id_tokens.
- /// By default the runtime uses cookies with unique names generated from a hash of the nonce.
+ /// Responsible for retrieving, caching, and refreshing the configuration from metadata.
+ /// If not provided, then one will be created using the MetadataAddress and Backchannel properties.
///
- public INonceCache NonceCache { get; set; }
+ public IConfigurationManager ConfigurationManager { get; set; }
+
+ ///
+ /// Gets or sets a value controlling if the 'CurrentUri' should be used as the 'local redirect' post authentication
+ /// if AuthenticationProperties.RedirectUri is null or empty.
+ ///
+ public bool DefaultToCurrentUriOnRedirect { get; set; }
///
/// Gets or sets the discovery endpoint for obtaining metadata
@@ -157,24 +169,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
public string MetadataAddress { get; set; }
///
- /// Gets or sets the expected audience for any received JWT token.
+ /// The OpenIdConnect protocol http://openid.net/specs/openid-connect-core-1_0.html
+ /// recommends adding a nonce to a request as a mitigation against replay attacks when requesting id_tokens.
+ /// By default the runtime uses cookies with unique names generated from a hash of the nonce.
///
- ///
- /// The expected audience for any received JWT token.
- ///
- public string Audience { get; set; }
-
- ///
- /// Responsible for retrieving, caching, and refreshing the configuration from metadata.
- /// If not provided, then one will be created using the MetadataAddress and Backchannel properties.
- ///
- public IConfigurationManager ConfigurationManager { get; set; }
-
- ///
- /// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic
- /// recovery in the event of a signature key rollover. This is enabled by default.
- ///
- public bool RefreshOnIssuerKeyNotFound { get; set; }
+ public INonceCache NonceCache { get; set; }
///
/// Gets or sets the to notify when processing OpenIdConnect messages.
@@ -217,11 +216,22 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By Design")]
public string RedirectUri { get; set; }
+ ///
+ /// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic
+ /// recovery in the event of a signature key rollover. This is enabled by default.
+ ///
+ public bool RefreshOnIssuerKeyNotFound { get; set; }
+
///
/// Gets or sets the 'resource'.
///
public string Resource { get; set; }
+ ///
+ /// Gets or sets the 'response_mode'.
+ ///
+ public string ResponseMode { get; private set; }
+
///
/// Gets or sets the 'response_type'.
///
@@ -233,10 +243,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
public string Scope { get; set; }
///
- /// Gets or sets the authentication scheme corresponding to the middleware
- /// responsible of persisting user's identity after a successful authentication.
- /// This value typically corresponds to a cookie middleware registered in the Startup class.
- /// When omitted, is used as a fallback value.
+ /// Gets or sets the SignInScheme which will be used to set the .
///
public string SignInScheme { get; set; }
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs
index 6f55448890..2e55b96fa4 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs
@@ -15,7 +15,7 @@ namespace Microsoft.Framework.DependencyInjection
{
public static IServiceCollection ConfigureOpenIdConnectAuthentication([NotNull] this IServiceCollection services, [NotNull] Action configure)
{
- return services.ConfigureOpenIdConnectAuthentication(configure, optionsName: "");
+ return ConfigureOpenIdConnectAuthentication(services, configure, null);
}
public static IServiceCollection ConfigureOpenIdConnectAuthentication([NotNull] this IServiceCollection services, [NotNull] Action configure, string optionsName)
@@ -25,7 +25,7 @@ namespace Microsoft.Framework.DependencyInjection
public static IServiceCollection ConfigureOpenIdConnectAuthentication([NotNull] this IServiceCollection services, [NotNull] IConfiguration config)
{
- return services.ConfigureOpenIdConnectAuthentication(config, optionsName: "");
+ return ConfigureOpenIdConnectAuthentication(services, config, null);
}
public static IServiceCollection ConfigureOpenIdConnectAuthentication([NotNull] this IServiceCollection services, [NotNull] IConfiguration config, string optionsName)
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.Designer.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.Designer.cs
index ee19db0116..b95de83180 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.Designer.cs
@@ -1,18 +1,9 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.34014
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace Microsoft.AspNet.Authentication.OpenIdConnect {
- using System;
+//
+namespace Microsoft.AspNet.Authentication.OpenIdConnect
+{
+ using System.Globalization;
using System.Reflection;
-
-
+ using System.Resources;
///
/// A strongly-typed resource class, for looking up localized strings, etc.
@@ -24,78 +15,337 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
-
+ internal class Resources
+ {
private static global::System.Resources.ResourceManager resourceMan;
-
+
private static global::System.Globalization.CultureInfo resourceCulture;
-
+
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
+ internal Resources()
+ {
}
-
+
///
/// Returns the cached ResourceManager instance used by this class.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Owin.Security.OpenIdConnect.Resources", IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if (object.ReferenceEquals(resourceMan, null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Authentication.OpenIdConnect.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
-
+
///
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
return resourceCulture;
}
- set {
+ set
+ {
resourceCulture = value;
}
}
-
+
///
- /// Looks up a localized string similar to BackchannelTimeout cannot be less or equal to TimeSpan.Zero..
+ /// OIDCH_0101: BackchannelTimeout cannot be less or equal to TimeSpan.Zero.
///
- internal static string ArgsException_BackchallelLessThanZero {
- get {
- return ResourceManager.GetString("ArgsException_BackchallelLessThanZero", resourceCulture);
- }
+ internal static string OIDCH_0101_BackChallnelLessThanZero
+ {
+ get { return ResourceManager.GetString("OIDCH_0101_BackChallnelLessThanZero"); }
}
-
+
///
- /// Looks up a localized string similar to "OpenIdConnectMessage.Error was not null, indicating an error. Error: '{0}'. Error_Description (may be empty): '{1}'. Error_Uri (may be empty): '{2}'.".
+ /// OIDCH0102: An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.
///
- internal static string Exception_OpenIdConnectMessageError {
- get {
- return ResourceManager.GetString("Exception_OpenIdConnectMessageError", resourceCulture);
- }
+ internal static string OIDCH_0102_ExceptionValidatorHandlerMismatch
+ {
+ get { return ResourceManager.GetString("OIDCH_0102_Exception_ValidatorHandlerMismatch"); }
}
-
+
///
- /// Looks up a localized string similar to OIDC_20001: The query string for Logout is not a well formed URI. The runtime cannot redirect. Redirect uri: '{0}'..
+ /// OIDCH_0051: The query string for Logout is not a well formed URI. The runtime cannot redirect. Redirect uri: '{0}'.
///
- internal static string Exception_RedirectUri_LogoutQueryString_IsNotWellFormed {
- get {
- return ResourceManager.GetString("Exception_RedirectUri_LogoutQueryString_IsNotWellFormed", resourceCulture);
- }
+ internal static string OIDCH_0051_RedirectUriLogoutIsNotWellFormed
+ {
+ get { return ResourceManager.GetString("OIDCH_0051_RedirectUriLogoutIsNotWellFormed"); }
}
-
+
///
- /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler..
+ /// OIDCH_0026: Entering: '{0}'
///
- internal static string Exception_ValidatorHandlerMismatch {
- get {
- return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture);
- }
+ internal static string OIDCH_0026_ApplyResponseChallengeAsync
+ {
+ get { return ResourceManager.GetString("OIDCH_0026_ApplyResponseChallengeAsync"); }
+ }
+
+ ///
+ /// OIDCH_0027: converted 401 to 403.
+ ///
+ internal static string OIDCH_0027_401_ConvertedTo_403
+ {
+ get { return ResourceManager.GetString("OIDCH_0027_401_ConvertedTo_403"); }
+ }
+
+ ///
+ /// OIDCH_0028: Response.StatusCode != 401, StatusCode: '{0}'."
+ ///
+ internal static string OIDCH_0028_StatusCodeNot401
+ {
+ get { return ResourceManager.GetString("OIDCH_0028_StatusCodeNot401"); }
+ }
+
+ ///
+ /// OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication
+ ///
+ internal static string OIDCH_0029_ChallengeContextEqualsNull
+ {
+ get { return ResourceManager.GetString("OIDCH_0029_ChallengeContextEqualsNull"); }
+ }
+
+ ///
+ /// OIDCH_0030: using properties.RedirectUri for 'local redirect' post authentication: '{0}'.
+ ///
+ internal static string OIDCH_0030_Using_Properties_RedirectUri
+ {
+ get { return ResourceManager.GetString("OIDCH_0030_Using_Properties_RedirectUri"); }
+ }
+
+ ///
+ /// OIDCH_0031: using Options.RedirectUri for 'redirect_uri': '{0}'.
+ ///
+ internal static string OIDCH_0031_Using_Options_RedirectUri
+ {
+ get { return ResourceManager.GetString("OIDCH_0031_Using_Options_RedirectUri"); }
+ }
+
+ ///
+ /// OIDCH_0032: using the CurrentUri for 'local redirect' post authentication: '{0}'.
+ ///
+ internal static string OIDCH_0032_UsingCurrentUriRedirectUri
+ {
+ get { return ResourceManager.GetString("OIDCH_0032_UsingCurrentUriRedirectUri"); }
+ }
+
+ ///
+ /// OIDCH_0033: ProtocolValidator.RequireNonce == true. Options.NonceCache.TryAddNonce returned false. This usually indicates the nonce is not unique or has been used. The nonce is: '{0}'.
+ ///
+ internal static string OIDCH_0033_TryAddNonceFailed
+ {
+ get { return ResourceManager.GetString("OIDCH_0033_TryAddNonceFailed"); }
+ }
+
+ ///
+ /// OIDCH_0034: redirectToIdentityProviderNotification.HandledResponse
+ ///
+ internal static string OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse
+ {
+ get { return ResourceManager.GetString("OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse"); }
+ }
+
+ ///
+ /// OIDCH_0035: redirectToIdentityProviderNotification.Skipped
+ ///
+ internal static string OIDCH_0035_RedirectToIdentityProviderNotificationSkipped
+ {
+ get { return ResourceManager.GetString("OIDCH_0035_RedirectToIdentityProviderNotificationSkipped"); }
+ }
+
+ ///
+ /// OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: {0}", (redirectUri ?? "null"))
+ ///
+ internal static string OIDCH_0036_UriIsNotWellFormed
+ {
+ get { return ResourceManager.GetString("OIDCH_0036_UriIsNotWellFormed"); }
+ }
+
+ ///
+ /// OIDCH_0000: Entering: '{0}'.
+ ///
+ internal static string OIDCH_0000_AuthenticateCoreAsync
+ {
+ get { return ResourceManager.GetString("OIDCH_0000_AuthenticateCoreAsync"); }
+ }
+
+ ///
+ /// OIDCH_0001: MessageReceived: '{0}'.
+ ///
+ internal static string OIDCH_0001_MessageReceived
+ {
+ get { return ResourceManager.GetString("OIDCH_0001_MessageReceived"); }
+ }
+
+ ///
+ /// OIDCH_0001: MessageReceived: '{0}'.
+ ///
+ internal static string FormatOIDCH_0001_MessageReceived(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, ResourceManager.GetString("OIDCH_0001_MessageReceived"), p0);
+ }
+
+ ///
+ /// OIDCH_0002: messageReceivedNotification.HandledResponse
+ ///
+ internal static string OIDCH_0002_MessageReceivedNotificationHandledResponse
+ {
+ get { return ResourceManager.GetString("OIDCH_0002_MessageReceivedNotificationHandledResponse"); }
+ }
+
+ ///
+ /// OIDCH_0003: messageReceivedNotification.Skipped
+ ///
+ internal static string OIDCH_0003_MessageReceivedNotificationSkipped
+ {
+ get { return ResourceManager.GetString("OIDCH_0003_MessageReceivedNotificationSkipped"); }
+ }
+
+ ///
+ /// OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or whitespace. State is required to process the message.
+ ///
+ internal static string OIDCH_0004_MessageStateIsNullOrWhiteSpace
+ {
+ get { return ResourceManager.GetString("OIDCH_0004_MessageStateIsNullOrWhiteSpace"); }
+ }
+
+ ///
+ /// OIDCH_0005: unable to unprotect the message.State
+ ///
+ internal static string OIDCH_0005_MessageStateIsInvalid
+ {
+ get { return ResourceManager.GetString("OIDCH_0005_MessageStateIsInvalid"); }
+ }
+
+ ///
+ /// OIDCH_0006_MessageErrorNotNull: '{0}'.
+ ///
+ internal static string OIDCH_0006_MessageErrorNotNull
+ {
+ get { return ResourceManager.GetString("OIDCH_0006_MessageErrorNotNull"); }
+ }
+
+ ///
+ /// OIDCH_0007: updating configuration
+ ///
+ internal static string OIDCH_0007_UpdatingConfiguration
+ {
+ get { return ResourceManager.GetString("OIDCH_0007_UpdatingConfiguration"); }
+ }
+
+ ///
+ /// OIDCH_0008: securityTokenReceivedNotification.HandledResponse
+ ///
+ internal static string OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse
+ {
+ get { return ResourceManager.GetString("OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse"); }
+ }
+
+ ///
+ /// OIDCH_0009: securityTokenReceivedNotification.Skipped
+ ///
+ internal static string OIDCH_0009_SecurityTokenReceivedNotificationSkipped
+ {
+ get { return ResourceManager.GetString("OIDCH_0009_SecurityTokenReceivedNotificationSkipped:"); }
+ }
+
+ ///
+ /// OIDCH_0010: Validated Security Token must be a JwtSecurityToken was: '{0}'.
+ ///
+ internal static string OIDCH_0010_ValidatedSecurityTokenNotJwt
+ {
+ get { return ResourceManager.GetString("OIDCH_0010_ValidatedSecurityTokenNotJwt"); }
+ }
+
+ ///
+ /// OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: {0}."
+ ///
+ internal static string OIDCH_0011_UnableToValidateToken
+ {
+ get { return ResourceManager.GetString("OIDCH_0011_UnableToValidateToken"); }
+ }
+
+ ///
+ /// OIDCH_0012: securityTokenValidatedNotification.HandledResponse
+ ///
+ internal static string OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse
+ {
+ get { return ResourceManager.GetString("OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse"); }
+ }
+
+ ///
+ /// OIDCH_0013: securityTokenValidatedNotification.Skipped
+ ///
+ internal static string OIDCH_0013_SecurityTokenValidatedNotificationSkipped
+ {
+ get { return ResourceManager.GetString("OIDCH_0013_SecurityTokenValidatedNotificationSkipped"); }
+ }
+
+ ///
+ /// OIDCH_0014: 'code' received: '{0}'
+ ///
+ internal static string OIDCH_0014_CodeReceived
+ {
+ get { return ResourceManager.GetString("OIDCH_0014_CodeReceived"); }
+ }
+
+ ///
+ /// OIDCH_0015: codeReceivedNotification.HandledResponse")
+ ///
+ internal static string OIDCH_0015_CodeReceivedNotificationHandledResponse
+ {
+ get { return ResourceManager.GetString("OIDCH_0015_CodeReceivedNotificationHandledResponse"); }
+ }
+
+ ///
+ /// OIDCH_0016: codeReceivedNotification.Skipped
+ ///
+ internal static string OIDCH_0016_CodeReceivedNotificationSkipped
+ {
+ get { return ResourceManager.GetString("OIDCH_0016_CodeReceivedNotificationSkipped"); }
+ }
+
+ ///
+ /// OIDCH_0017: Exception occurred while processing message
+ ///
+ internal static string OIDCH_0017_ExceptionOccurredWhileProcessingMessage
+ {
+ get { return ResourceManager.GetString("OIDCH_0017_ExceptionOccurredWhileProcessingMessage"); }
+ }
+
+ ///
+ /// OIDCH_0018: authenticationFailedNotification.HandledResponse
+ ///
+ internal static string OIDCH_0018_AuthenticationFailedNotificationHandledResponse
+ {
+ get { return ResourceManager.GetString("OIDCH_0018_AuthenticationFailedNotificationHandledResponse"); }
+ }
+
+ ///
+ /// OIDCH_0019: authenticationFailedNotification.Skipped
+ ///
+ internal static string OIDCH_0019_AuthenticationFailedNotificationSkipped
+ {
+ get { return ResourceManager.GetString("OIDCH_0019_AuthenticationFailedNotificationSkipped"); }
+ }
+
+ ///
+ /// OIDCH_0020: 'id_token' received: '{0}'
+ ///
+ internal static string OIDCH_0020_IdTokenReceived
+ {
+ get { return ResourceManager.GetString("OIDCH_0020_IdTokenReceived"); }
}
}
}
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx
index 7abad90eb0..454dae209b 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx
@@ -117,16 +117,109 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- BackchannelTimeout cannot be less or equal to TimeSpan.Zero.
+
+ OIDCH_0101: BackchannelTimeout cannot be less or equal to TimeSpan.Zero.
-
- "OpenIdConnectMessage.Error was not null, indicating an error. Error: '{0}'. Error_Description (may be empty): '{1}'. Error_Uri (may be empty): '{2}'."
+
+ OIDCH_0102: An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.
-
- OIDC_20001: The query string for Logout is not a well formed URI. The runtime cannot redirect. Redirect uri: '{0}'.
+
+ OIDC_0051: The query string for Logout is not a well formed URI. The runtime cannot redirect. Redirect uri: '{0}'.
-
- An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.
+
+ OIDCH_0026: Entering: '{0}'
-
\ No newline at end of file
+
+ OIDCH_0027: converted 401 to 403.
+
+
+ OIDCH_0028: Response.StatusCode != 401, StatusCode: '{0}'.
+
+
+ OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication
+
+
+ OIDCH_0030: using properties.RedirectUri for 'local redirect' post authentication: '{0}'.
+
+
+ OIDCH_0031: using Options.RedirectUri for 'redirect_uri': '{0}'.
+
+
+ OIDCH_0032: using the CurrentUri for 'local redirect' post authentication: '{0}'.
+
+
+ OIDCH_0033: ProtocolValidator.RequireNonce == true. Options.NonceCache.TryAddNonce returned false. This usually indicates the nonce is not unique or has been used. The nonce is: '{0}'.
+
+
+ OIDCH_0034: redirectToIdentityProviderNotification.HandledResponse
+
+
+ OIDCH_0035: redirectToIdentityProviderNotification.Skipped
+
+
+ OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: {0}", (redirectUri ?? "null"))
+
+
+ OIDCH_0000: Entering: '{0}'.
+
+
+ OIDCH_0001: MessageReceived: '{0}'.
+
+
+ OIDCH_0002: messageReceivedNotification.HandledResponse
+
+
+ OIDCH_0003: messageReceivedNotification.Skipped
+
+
+ OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or whitespace. State is required to process the message.
+
+
+ OIDCH_0005: unable to unprotect the message.State
+
+
+ OIDCH_0006_MessageErrorNotNull: '{0}'.
+
+
+ OIDCH_0007: updating configuration
+
+
+ OIDCH_0008: securityTokenReceivedNotification.HandledResponse
+
+
+ OIDCH_0009: securityTokenReceivedNotification.Skipped
+
+
+ OIDCH_0010: Validated Security Token must be a JwtSecurityToken was: '{0}'.
+
+
+ OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: {0}."
+
+
+ OIDCH_0012: securityTokenValidatedNotification.HandledResponse
+
+
+ OIDCH_0013: securityTokenValidatedNotification.Skipped
+
+
+ OIDCH_0014: 'code' received: '{0}'
+
+
+ OIDCH_0015: codeReceivedNotification.HandledResponse
+
+
+ OIDCH_0016: codeReceivedNotification.Skipped
+
+
+ OIDCH_0017: Exception occurred while processing message
+
+
+ OIDCH_0018: authenticationFailedNotification.HandledResponse
+
+
+ OIDCH_0019: authenticationFailedNotification.Skipped
+
+
+ OIDCH_0020: 'id_token' received: '{0}'
+
+
diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerTests.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerTests.cs
new file mode 100644
index 0000000000..5817615f2b
--- /dev/null
+++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerTests.cs
@@ -0,0 +1,768 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// this controls if the logs are written to the console.
+// they can be reviewed for general content.
+//#define _Verbose
+
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Authentication.Notifications;
+using Microsoft.AspNet.Authentication.OpenIdConnect;
+using Microsoft.AspNet.Builder;
+using Microsoft.AspNet.DataProtection;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Http.Authentication;
+using Microsoft.AspNet.TestHost;
+using Microsoft.Framework.DependencyInjection;
+using Microsoft.Framework.Logging;
+using Microsoft.Framework.OptionsModel;
+using Microsoft.IdentityModel.Protocols;
+using Shouldly;
+using Xunit;
+
+namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
+{
+ ///
+ /// These tests are designed to test OpenIdConnectAuthenticationHandler.
+ ///
+ public class OpenIdConnectHandlerTests
+ {
+ static List CompleteLogEntries;
+ static Dictionary LogEntries;
+
+ static OpenIdConnectHandlerTests()
+ {
+ LogEntries =
+ new Dictionary()
+ {
+ { "OIDCH_0000:", LogLevel.Debug },
+ { "OIDCH_0001:", LogLevel.Debug },
+ { "OIDCH_0002:", LogLevel.Information },
+ { "OIDCH_0003:", LogLevel.Information },
+ { "OIDCH_0004:", LogLevel.Error },
+ { "OIDCH_0005:", LogLevel.Error },
+ { "OIDCH_0006:", LogLevel.Error },
+ { "OIDCH_0007:", LogLevel.Error },
+ { "OIDCH_0008:", LogLevel.Debug },
+ { "OIDCH_0009:", LogLevel.Debug },
+ { "OIDCH_0010:", LogLevel.Error },
+ { "OIDCH_0011:", LogLevel.Error },
+ { "OIDCH_0012:", LogLevel.Debug },
+ { "OIDCH_0013:", LogLevel.Debug },
+ { "OIDCH_0014:", LogLevel.Debug },
+ { "OIDCH_0015:", LogLevel.Debug },
+ { "OIDCH_0016:", LogLevel.Debug },
+ { "OIDCH_0017:", LogLevel.Error },
+ { "OIDCH_0018:", LogLevel.Debug },
+ { "OIDCH_0019:", LogLevel.Debug },
+ { "OIDCH_0020:", LogLevel.Debug },
+ { "OIDCH_0026:", LogLevel.Error },
+ };
+
+ BuildLogEntryList();
+ }
+
+ ///
+ /// Builds the complete list of log entries that are available in the runtime.
+ ///
+ private static void BuildLogEntryList()
+ {
+ CompleteLogEntries = new List();
+ foreach (var entry in LogEntries)
+ {
+ CompleteLogEntries.Add(new LogEntry { State = entry.Key, Level = entry.Value });
+ }
+ }
+
+ ///
+ /// Sanity check that logging is filtering, hi / low water marks are checked
+ ///
+ [Fact]
+ public void LoggingLevel()
+ {
+ var logger = new CustomLogger(LogLevel.Debug);
+ logger.IsEnabled(LogLevel.Critical).ShouldBe(true);
+ logger.IsEnabled(LogLevel.Debug).ShouldBe(true);
+ logger.IsEnabled(LogLevel.Error).ShouldBe(true);
+ logger.IsEnabled(LogLevel.Information).ShouldBe(true);
+ logger.IsEnabled(LogLevel.Verbose).ShouldBe(true);
+ logger.IsEnabled(LogLevel.Warning).ShouldBe(true);
+
+ logger = new CustomLogger(LogLevel.Critical);
+ logger.IsEnabled(LogLevel.Critical).ShouldBe(true);
+ logger.IsEnabled(LogLevel.Debug).ShouldBe(false);
+ logger.IsEnabled(LogLevel.Error).ShouldBe(false);
+ logger.IsEnabled(LogLevel.Information).ShouldBe(false);
+ logger.IsEnabled(LogLevel.Verbose).ShouldBe(false);
+ logger.IsEnabled(LogLevel.Warning).ShouldBe(false);
+ }
+
+ ///
+ /// Test produces expected logs.
+ /// Each call to 'RunVariation' is configured with an and .
+ /// The list of expected log entries is checked and any errors reported.
+ /// captures the logs so they can be prepared.
+ ///
+ ///
+ [Fact]
+ public async Task AuthenticateCore()
+ {
+ //System.Diagnostics.Debugger.Launch();
+
+ var propertiesFormatter = new AuthenticationPropertiesFormater();
+ var protectedProperties = propertiesFormatter.Protect(new AuthenticationProperties());
+ var state = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + Uri.EscapeDataString(protectedProperties);
+ var code = Guid.NewGuid().ToString();
+ var message =
+ new OpenIdConnectMessage
+ {
+ Code = code,
+ State = state,
+ };
+
+ var errors = new Dictionary>>();
+
+ var logsEntriesExpected = new int[] { 0, 1, 7, 14, 15 };
+ await RunVariation(LogLevel.Debug, message, CodeReceivedHandledOptions, errors, logsEntriesExpected);
+
+ logsEntriesExpected = new int[] { 0, 1, 7, 14, 16 };
+ await RunVariation(LogLevel.Debug, message, CodeReceivedSkippedOptions, errors, logsEntriesExpected);
+
+ logsEntriesExpected = new int[] { 0, 1, 7, 14 };
+ await RunVariation(LogLevel.Debug, message, DefaultOptions, errors, logsEntriesExpected);
+
+ // each message below should return before processing the idtoken
+ message.IdToken = "invalid_token";
+
+ logsEntriesExpected = new int[] { 0, 1, 2 };
+ await RunVariation(LogLevel.Debug, message, MessageReceivedHandledOptions, errors, logsEntriesExpected);
+
+ logsEntriesExpected = new int[]{ 2 };
+ await RunVariation(LogLevel.Information, message, MessageReceivedHandledOptions, errors, logsEntriesExpected);
+
+ logsEntriesExpected = new int[] { 0, 1, 3 };
+ await RunVariation(LogLevel.Debug, message, MessageReceivedSkippedOptions, errors, logsEntriesExpected);
+
+ logsEntriesExpected = new int[] { 3 };
+ await RunVariation(LogLevel.Information, message, MessageReceivedSkippedOptions, errors, logsEntriesExpected);
+
+ logsEntriesExpected = new int[] {0, 1, 7, 20, 8 };
+ await RunVariation(LogLevel.Debug, message, SecurityTokenReceivedHandledOptions, errors, logsEntriesExpected);
+
+ logsEntriesExpected = new int[] {0, 1, 7, 20, 9 };
+ await RunVariation(LogLevel.Debug, message, SecurityTokenReceivedSkippedOptions, errors, logsEntriesExpected);
+
+#if _Verbose
+ Console.WriteLine("\n ===== \n");
+ DisplayErrors(errors);
+#endif
+ errors.Count.ShouldBe(0);
+ }
+
+ ///
+ /// Tests that processes a messaage as expected.
+ /// The test runs two independant paths: Using and
+ ///
+ /// for this variation
+ /// the that has arrived
+ /// the delegate used for setting the options.
+ /// container for propogation of errors.
+ /// the expected log entries
+ /// a Task
+ private async Task RunVariation(LogLevel logLevel, OpenIdConnectMessage message, Action action, Dictionary>> errors, int[] logsEntriesExpected)
+ {
+ var expectedLogs = PopulateLogEntries(logsEntriesExpected);
+ string variation = action.Method.ToString().Substring(5, action.Method.ToString().IndexOf('(') - 5);
+#if _Verbose
+ Console.WriteLine(Environment.NewLine + "=====" + Environment.NewLine + "Variation: " + variation + ", LogLevel: " + logLevel.ToString() + Environment.NewLine + Environment.NewLine + "Expected Logs: ");
+ DisplayLogs(expectedLogs);
+ Console.WriteLine(Environment.NewLine + "Logs using ConfigureOptions:");
+#endif
+ var form = new FormUrlEncodedContent(message.Parameters);
+ var loggerFactory = new CustomLoggerFactory(logLevel);
+ var server = CreateServer(new CustomConfigureOptions(action), loggerFactory);
+ await server.CreateClient().PostAsync("http://localhost", form);
+ CheckLogs(variation + ":ConfigOptions", loggerFactory.Logger.Logs, expectedLogs, errors);
+
+#if _Verbose
+ Console.WriteLine(Environment.NewLine + "Logs using IOptions:");
+#endif
+ form = new FormUrlEncodedContent(message.Parameters);
+ loggerFactory = new CustomLoggerFactory(logLevel);
+ server = CreateServer(new Options(action), loggerFactory);
+ await server.CreateClient().PostAsync("http://localhost", form);
+ CheckLogs(variation + ":IOptions", loggerFactory.Logger.Logs, expectedLogs, errors);
+ }
+
+ ///
+ /// Populates a list of expected log entries for a test variation.
+ ///
+ /// the index for the in CompleteLogEntries of interest.
+ /// a that represents the expected entries for a test variation.
+ private List PopulateLogEntries(int[] items)
+ {
+ var entries = new List();
+ foreach(var item in items)
+ {
+ entries.Add(CompleteLogEntries[item]);
+ }
+
+ return entries;
+ }
+
+ private void DisplayLogs(List logs)
+ {
+ foreach (var logentry in logs)
+ {
+ Console.WriteLine(logentry.ToString());
+ }
+ }
+
+ private void DisplayErrors(Dictionary>> errors)
+ {
+ if (errors.Count > 0)
+ {
+ foreach (var error in errors)
+ {
+ Console.WriteLine("Error in Variation: " + error.Key);
+ foreach (var logError in error.Value)
+ {
+ Console.WriteLine("*Captured*, *Expected* : *" + (logError.Item1?.ToString() ?? "null") + "*, *" + (logError.Item2?.ToString() ?? "null") + "*");
+ }
+ Console.WriteLine(Environment.NewLine);
+ }
+ }
+ }
+
+ ///
+ /// Adds to errors if a variation if any are found.
+ ///
+ /// if this has been seen before, errors will be appended, test results are easier to understand if this is unique.
+ /// these are the logs the runtime generated
+ /// these are the errors that were expected
+ /// the dictionary to record any errors
+ private void CheckLogs(string variation, List capturedLogs, List expectedLogs, Dictionary>> errors)
+ {
+ var localErrors = new List>();
+
+ if (capturedLogs.Count >= expectedLogs.Count)
+ {
+ for (int i = 0; i < capturedLogs.Count; i++)
+ {
+ if (i + 1 > expectedLogs.Count)
+ {
+ localErrors.Add(new Tuple(capturedLogs[i], null));
+ }
+ else
+ {
+ if (!TestUtilities.AreEqual(capturedLogs[i], expectedLogs[i]))
+ {
+ localErrors.Add(new Tuple(capturedLogs[i], expectedLogs[i]));
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < expectedLogs.Count; i++)
+ {
+ if (i + 1 > capturedLogs.Count)
+ {
+ localErrors.Add(new Tuple(null, expectedLogs[i]));
+ }
+ else
+ {
+ if (!TestUtilities.AreEqual(expectedLogs[i], capturedLogs[i]))
+ {
+ localErrors.Add(new Tuple(capturedLogs[i], expectedLogs[i]));
+ }
+ }
+ }
+ }
+
+ if (localErrors.Count != 0)
+ {
+ if (errors.ContainsKey(variation))
+ {
+ foreach (var error in localErrors)
+ {
+ errors[variation].Add(error);
+ }
+ }
+ else
+ {
+ errors[variation] = localErrors;
+ }
+ }
+ }
+
+ #region Configure Options
+
+ private static void CodeReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ DefaultOptions(options);
+ options.Notifications =
+ new OpenIdConnectAuthenticationNotifications
+ {
+ AuthorizationCodeReceived = (notification) =>
+ {
+ notification.HandleResponse();
+ return Task.FromResult