Adding support for signing in using "code flow"

This commit is contained in:
tushar gupta 2015-07-08 17:05:57 -07:00
parent c6aa9371c7
commit 8d7f052cf4
12 changed files with 830 additions and 226 deletions

View File

@ -0,0 +1,36 @@
using System;
using Microsoft.AspNet.Authentication.OpenIdConnect;
using Microsoft.AspNet.Http;
using Microsoft.IdentityModel.Protocols;
namespace Microsoft.AspNet.Authentication.Notifications
{
/// <summary>
/// This Notification can be used to be informed when an 'AuthorizationCode' is redeemed for tokens at the token endpoint.
/// </summary>
public class AuthorizationCodeRedeemedNotification : BaseNotification<OpenIdConnectAuthenticationOptions>
{
/// <summary>
/// Creates a <see cref="AuthorizationCodeRedeemedNotification"/>
/// </summary>
public AuthorizationCodeRedeemedNotification(HttpContext context, OpenIdConnectAuthenticationOptions options) : base(context, options)
{
}
/// <summary>
/// Gets or sets the 'code'.
/// </summary>
public string Code { get; set; }
/// <summary>
/// Gets or sets the <see cref="OpenIdConnectTokenEndpointResponse"/> that contains the tokens and json response received after redeeming the code at the token endpoint.
/// </summary>
public OpenIdConnectTokenEndpointResponse TokenEndpointResponse { get; set; }
/// <summary>
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
/// </summary>
public OpenIdConnectMessage ProtocolMessage { get; set; }
}
}

View File

@ -6,6 +6,8 @@ using System.Globalization;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.Notifications;
@ -16,6 +18,7 @@ using Microsoft.Framework.Caching.Distributed;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Microsoft.IdentityModel.Protocols;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
@ -28,6 +31,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
private const string UriSchemeDelimiter = "://";
private OpenIdConnectConfiguration _configuration;
protected HttpClient Backchannel { get; private set; }
public OpenIdConnectAuthenticationHandler(HttpClient backchannel)
{
Backchannel = backchannel;
}
/// <summary>
/// Handles Signout
/// </summary>
@ -116,17 +126,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
properties.RedirectUri = CurrentUri;
}
if (!string.IsNullOrEmpty(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.IsNullOrEmpty(Options.RedirectUri))
{
properties.Items.Add(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey, Options.RedirectUri);
}
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
@ -195,6 +194,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
message = redirectToIdentityProviderNotification.ProtocolMessage;
}
var redirectUriForCode = message.RedirectUri;
if (string.IsNullOrEmpty(redirectUriForCode))
{
Logger.LogDebug(Resources.OIDCH_0031_Using_Options_RedirectUri, Options.RedirectUri);
redirectUriForCode = Options.RedirectUri;
}
if (!string.IsNullOrEmpty(redirectUriForCode))
{
// When redeeming a 'code' for an AccessToken, this value is needed
properties.Items.Add(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey, redirectUriForCode);
}
message.State = Options.StateDataFormat.Protect(properties);
var redirectUri = message.CreateAuthenticationRequestUrl();
@ -243,27 +255,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
try
{
if (Logger.IsEnabled(LogLevel.Debug))
{
Logger.LogDebug(Resources.OIDCH_0001_MessageReceived, message.BuildRedirectUrl());
}
var messageReceivedNotification =
new MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
ProtocolMessage = message
};
await Options.Notifications.MessageReceived(messageReceivedNotification);
var messageReceivedNotification = await RunMessageReceivedNotificationAsync(message);
if (messageReceivedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0002_MessageReceivedNotificationHandledResponse);
return messageReceivedNotification.AuthenticationTicket;
}
if (messageReceivedNotification.Skipped)
else if (messageReceivedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0003_MessageReceivedNotificationSkipped);
return null;
}
@ -302,178 +300,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
AuthenticationTicket ticket = null;
JwtSecurityToken jwt = null;
// OpenIdConnect protocol allows a Code to be received without the id_token
if (!string.IsNullOrEmpty(message.IdToken))
if (string.IsNullOrEmpty(message.IdToken) && !string.IsNullOrEmpty(message.Code))
{
Logger.LogDebug(Resources.OIDCH_0020_IdTokenReceived, message.IdToken);
var securityTokenReceivedNotification =
new SecurityTokenReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
ProtocolMessage = message,
};
await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
if (securityTokenReceivedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse);
return securityTokenReceivedNotification.AuthenticationTicket;
}
if (securityTokenReceivedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0009_SecurityTokenReceivedNotificationSkipped);
return null;
}
// Copy and augment to avoid cross request race conditions for updated configurations.
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
if (string.IsNullOrEmpty(validationParameters.ValidIssuer))
{
validationParameters.ValidIssuer = _configuration.Issuer;
}
else if (!string.IsNullOrEmpty(_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 SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0010_ValidatedSecurityTokenNotJwt, validatedToken?.GetType()));
}
}
}
if (validatedToken == null)
{
Logger.LogError(Resources.OIDCH_0011_UnableToValidateToken, message.IdToken);
throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0011_UnableToValidateToken, message.IdToken));
}
ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);
if (!string.IsNullOrEmpty(message.SessionState))
{
ticket.Properties.Items[OpenIdConnectSessionProperties.SessionState] = message.SessionState;
}
if (_configuration != null && !string.IsNullOrEmpty(_configuration.CheckSessionIframe))
{
ticket.Properties.Items[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe;
}
// Rename?
if (Options.UseTokenLifetime)
{
var issued = validatedToken.ValidFrom;
if (issued != DateTime.MinValue)
{
ticket.Properties.IssuedUtc = issued;
}
var expires = validatedToken.ValidTo;
if (expires != DateTime.MinValue)
{
ticket.Properties.ExpiresUtc = expires;
}
}
var securityTokenValidatedNotification =
new SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
AuthenticationTicket = ticket,
ProtocolMessage = message
};
await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);
if (securityTokenValidatedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse);
return securityTokenValidatedNotification.AuthenticationTicket;
}
if (securityTokenValidatedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0013_SecurityTokenValidatedNotificationSkipped);
return null;
}
string nonce = jwt.Payload.Nonce;
if (Options.CacheNonces)
{
if (await Options.NonceCache.GetAsync(nonce) != null)
{
await Options.NonceCache.RemoveAsync(nonce);
}
else
{
// If the nonce cannot be removed, it was
// already used and MUST be rejected.
nonce = null;
}
}
else
{
nonce = ReadNonceCookie(nonce);
}
var protocolValidationContext = new OpenIdConnectProtocolValidationContext
{
AuthorizationCode = message.Code,
Nonce = nonce,
};
Options.ProtocolValidator.Validate(jwt, protocolValidationContext);
return await HandleCodeOnlyFlow(message, properties);
}
if (message.Code != null)
else if (!string.IsNullOrEmpty(message.IdToken))
{
Logger.LogDebug(Resources.OIDCH_0014_AuthorizationCodeReceived, message.Code);
if (ticket == null)
{
ticket = new AuthenticationTicket(properties, Options.AuthenticationScheme);
}
var authorizationCodeReceivedNotification = new AuthorizationCodeReceivedNotification(Context, Options)
{
AuthenticationTicket = ticket,
Code = message.Code,
JwtSecurityToken = jwt,
ProtocolMessage = message,
RedirectUri = ticket.Properties.Items.ContainsKey(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey) ?
ticket.Properties.Items[OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey] : string.Empty,
};
await Options.Notifications.AuthorizationCodeReceived(authorizationCodeReceivedNotification);
if (authorizationCodeReceivedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0015_AuthorizationCodeReceivedNotificationHandledResponse);
return authorizationCodeReceivedNotification.AuthenticationTicket;
}
if (authorizationCodeReceivedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0016_AuthorizationCodeReceivedNotificationSkipped);
return null;
}
return await HandleIdTokenFlows(message, properties);
}
else
{
Logger.LogDebug(Resources.OIDCH_0045_Id_Token_Code_Missing);
return null;
}
return ticket;
}
catch (Exception exception)
{
@ -489,23 +328,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
}
var authenticationFailedNotification =
new AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
ProtocolMessage = message,
Exception = exception
};
await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);
var authenticationFailedNotification = await RunAuthenticationFailedNotificationAsync(message, exception);
if (authenticationFailedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0018_AuthenticationFailedNotificationHandledResponse);
return authenticationFailedNotification.AuthenticationTicket;
}
if (authenticationFailedNotification.Skipped)
else if (authenticationFailedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0019_AuthenticationFailedNotificationSkipped);
return null;
}
@ -513,6 +342,215 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
}
private async Task<AuthenticationTicket> HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties)
{
AuthenticationTicket ticket = null;
JwtSecurityToken jwt = null;
OpenIdConnectTokenEndpointResponse tokenEndpointResponse = null;
string idToken = null;
var authorizationCodeReceivedNotification = await RunAuthorizationCodeReceivedNotificationAsync(message, properties, ticket, jwt);
if (authorizationCodeReceivedNotification.HandledResponse)
{
return authorizationCodeReceivedNotification.AuthenticationTicket;
}
else if (authorizationCodeReceivedNotification.Skipped)
{
return null;
}
// Redeeming authorization code for tokens
Logger.LogDebug(Resources.OIDCH_0038_Redeeming_Auth_Code, message.Code);
tokenEndpointResponse = await RedeemAuthorizationCodeAsync(message.Code, authorizationCodeReceivedNotification.RedirectUri);
idToken = tokenEndpointResponse.Message.IdToken;
var authorizationCodeRedeemedNotification = await RunAuthorizationCodeRedeemedNotificationAsync(message, tokenEndpointResponse);
if (authorizationCodeRedeemedNotification.HandledResponse)
{
return authorizationCodeRedeemedNotification.AuthenticationTicket;
}
else if (authorizationCodeRedeemedNotification.Skipped)
{
return null;
}
// no need to validate signature when token is received using "code flow" as per spec [http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation].
var validationParameters = Options.TokenValidationParameters.Clone();
validationParameters.ValidateSignature = false;
ticket = ValidateToken(idToken, message, properties, validationParameters, out jwt);
if (Options.GetClaimsFromUserInfoEndpoint)
{
Logger.LogDebug(Resources.OIDCH_0040_Sending_Request_UIEndpoint);
ticket = await GetUserInformationAsync(properties, tokenEndpointResponse.Message, ticket);
}
var securityTokenValidatedNotification = await RunSecurityTokenValidatedNotificationAsync(message, ticket);
if (securityTokenValidatedNotification.HandledResponse)
{
return securityTokenValidatedNotification.AuthenticationTicket;
}
else if (securityTokenValidatedNotification.Skipped)
{
return null;
}
// If id_token is received using code only flow, no need to validate chash.
await ValidateOpenIdConnectProtocolAsync(jwt, message, false);
return ticket;
}
private async Task<AuthenticationTicket> HandleIdTokenFlows(OpenIdConnectMessage message, AuthenticationProperties properties)
{
AuthenticationTicket ticket = null;
JwtSecurityToken jwt = null;
var securityTokenReceivedNotification = await RunSecurityTokenReceivedNotificationAsync(message);
if (securityTokenReceivedNotification.HandledResponse)
{
return securityTokenReceivedNotification.AuthenticationTicket;
}
else if (securityTokenReceivedNotification.Skipped)
{
return null;
}
var validationParameters = Options.TokenValidationParameters.Clone();
ticket = ValidateToken(message.IdToken, message, properties, validationParameters, out jwt);
var securityTokenValidatedNotification = await RunSecurityTokenValidatedNotificationAsync(message, ticket);
if (securityTokenValidatedNotification.HandledResponse)
{
return securityTokenValidatedNotification.AuthenticationTicket;
}
else if (securityTokenValidatedNotification.Skipped)
{
return null;
}
await ValidateOpenIdConnectProtocolAsync(jwt, message);
if (message.Code != null)
{
var authorizationCodeReceivedNotification = await RunAuthorizationCodeReceivedNotificationAsync(message, properties, ticket, jwt);
if (authorizationCodeReceivedNotification.HandledResponse)
{
return authorizationCodeReceivedNotification.AuthenticationTicket;
}
else if (authorizationCodeReceivedNotification.Skipped)
{
return null;
}
}
return ticket;
}
/// <summary>
/// Redeems the authorization code for tokens at the token endpoint
/// </summary>
/// <param name="authorizationCode">The authorization code to redeem.</param>
/// <param name="redirectUri">Uri that was passed in the request sent for the authorization code.</param>
/// <returns>OpenIdConnect message that has tokens inside it.</returns>
protected virtual async Task<OpenIdConnectTokenEndpointResponse> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri)
{
var openIdMessage = new OpenIdConnectMessage()
{
ClientId = Options.ClientId,
ClientSecret = Options.ClientSecret,
Code = authorizationCode,
GrantType = "authorization_code",
RedirectUri = redirectUri
};
var requestMessage = new HttpRequestMessage(HttpMethod.Post, _configuration.TokenEndpoint);
requestMessage.Content = new FormUrlEncodedContent(openIdMessage.Parameters);
var responseMessage = await Backchannel.SendAsync(requestMessage);
responseMessage.EnsureSuccessStatusCode();
var tokenResonse = await responseMessage.Content.ReadAsStringAsync();
var jsonTokenResponse = JObject.Parse(tokenResonse);
return new OpenIdConnectTokenEndpointResponse(jsonTokenResponse);
}
/// <summary>
/// Goes to UserInfo endpoint to retrieve additional claims and add any unique claims to the given identity.
/// </summary>
/// <param name="properties">Authentication Properties</param>
/// <param name="message">message that is being processed</param>
/// <param name="ticket">authentication ticket with claims principal and identities</param>
/// <returns>Authentication ticket with identity with additional claims, if any.</returns>
protected virtual async Task<AuthenticationTicket> GetUserInformationAsync(AuthenticationProperties properties, OpenIdConnectMessage message, AuthenticationTicket ticket)
{
string userInfoEndpoint = null;
if (_configuration != null)
{
userInfoEndpoint = _configuration.UserInfoEndpoint;
}
if (string.IsNullOrEmpty(userInfoEndpoint))
{
Logger.LogWarning(Resources.OIDCH_0046_UserInfo_Endpoint_Not_Set);
return ticket;
}
var requestMessage = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", message.AccessToken);
var responseMessage = await Backchannel.SendAsync(requestMessage);
responseMessage.EnsureSuccessStatusCode();
var userInfoResponse = await responseMessage.Content.ReadAsStringAsync();
var user = JObject.Parse(userInfoResponse);
var identity = (ClaimsIdentity)ticket.Principal.Identity;
var subjectClaimType = identity.FindFirst(ClaimTypes.NameIdentifier);
if (subjectClaimType == null)
{
Logger.LogError(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0041_Subject_Claim_Not_Found, identity.ToString()));
return ticket;
}
var userInfoSubjectClaimValue = user.Value<string>(JwtRegisteredClaimNames.Sub);
// check if the sub claim matches
if (userInfoSubjectClaimValue == null || !string.Equals(userInfoSubjectClaimValue, subjectClaimType.Value, StringComparison.Ordinal))
{
Logger.LogError(Resources.OIDCH_0039_Subject_Claim_Mismatch);
return ticket;
}
foreach (var claim in identity.Claims)
{
// If this claimType is mapped by the JwtSeurityTokenHandler, then this property will be set
var shortClaimTypeName = claim.Properties.ContainsKey(JwtSecurityTokenHandler.ShortClaimTypeProperty) ?
claim.Properties[JwtSecurityTokenHandler.ShortClaimTypeProperty] : string.Empty;
// checking if claim in the identity (generated from id_token) has the same type as a claim retrieved from userinfo endpoint
JToken value;
var isClaimIncluded = user.TryGetValue(claim.Type, out value) || user.TryGetValue(shortClaimTypeName, out value);
// if a same claim exists (matching both type and value) both in id_token identity and userinfo response, remove the json entry from the userinfo response
if (isClaimIncluded && claim.Value.Equals(value.ToString(), StringComparison.Ordinal))
{
if (!user.Remove(claim.Type))
{
user.Remove(shortClaimTypeName);
}
}
}
// adding remaining unique claims from userinfo endpoint to the identity
foreach (var pair in user)
{
JToken value;
var claimValue = user.TryGetValue(pair.Key, out value) ? value.ToString() : null;
identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, Options.ClaimsIssuer));
}
return new AuthenticationTicket(new ClaimsPrincipal(identity), ticket.Properties, ticket.AuthenticationScheme);
}
/// <summary>
/// Adds the nonce to <see cref="HttpResponse.Cookies"/>.
/// </summary>
@ -609,6 +647,251 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
}
private async Task<MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>> RunMessageReceivedNotificationAsync(OpenIdConnectMessage message)
{
Logger.LogDebug(Resources.OIDCH_0001_MessageReceived, message.BuildRedirectUrl());
var messageReceivedNotification =
new MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
ProtocolMessage = message
};
await Options.Notifications.MessageReceived(messageReceivedNotification);
if (messageReceivedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0002_MessageReceivedNotificationHandledResponse);
}
else if (messageReceivedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0003_MessageReceivedNotificationSkipped);
}
return messageReceivedNotification;
}
private async Task<AuthorizationCodeReceivedNotification> RunAuthorizationCodeReceivedNotificationAsync(OpenIdConnectMessage message, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt)
{
var redirectUri = properties.Items.ContainsKey(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey) ?
properties.Items[OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey] : Options.RedirectUri;
Logger.LogDebug(Resources.OIDCH_0014_AuthorizationCodeReceived, message.Code);
var authorizationCodeReceivedNotification = new AuthorizationCodeReceivedNotification(Context, Options)
{
Code = message.Code,
ProtocolMessage = message,
RedirectUri = redirectUri,
AuthenticationTicket = ticket,
JwtSecurityToken = jwt
};
await Options.Notifications.AuthorizationCodeReceived(authorizationCodeReceivedNotification);
if (authorizationCodeReceivedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0015_AuthorizationCodeReceivedNotificationHandledResponse);
}
else if (authorizationCodeReceivedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0016_AuthorizationCodeReceivedNotificationSkipped);
}
return authorizationCodeReceivedNotification;
}
private async Task<AuthorizationCodeRedeemedNotification> RunAuthorizationCodeRedeemedNotificationAsync(OpenIdConnectMessage message, OpenIdConnectTokenEndpointResponse tokenEndpointResponse)
{
Logger.LogDebug(Resources.OIDCH_0042_AuthorizationCodeRedeemed, message.Code);
var authorizationCodeRedeemedNotification = new AuthorizationCodeRedeemedNotification(Context, Options)
{
Code = message.Code,
ProtocolMessage = message,
TokenEndpointResponse = tokenEndpointResponse
};
await Options.Notifications.AuthorizationCodeRedeemed(authorizationCodeRedeemedNotification);
if (authorizationCodeRedeemedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0043_AuthorizationCodeRedeemedNotificationHandledResponse);
}
else if (authorizationCodeRedeemedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0044_AuthorizationCodeRedeemedNotificationSkipped);
}
return authorizationCodeRedeemedNotification;
}
private async Task<SecurityTokenReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>> RunSecurityTokenReceivedNotificationAsync(OpenIdConnectMessage message)
{
Logger.LogDebug(Resources.OIDCH_0020_IdTokenReceived, message.IdToken);
var securityTokenReceivedNotification =
new SecurityTokenReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
ProtocolMessage = message,
};
await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
if (securityTokenReceivedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse);
}
else if (securityTokenReceivedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0009_SecurityTokenReceivedNotificationSkipped);
}
return securityTokenReceivedNotification;
}
private async Task<SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>> RunSecurityTokenValidatedNotificationAsync(OpenIdConnectMessage message, AuthenticationTicket ticket)
{
var securityTokenValidatedNotification =
new SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
AuthenticationTicket = ticket,
ProtocolMessage = message
};
await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);
if (securityTokenValidatedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse);
}
else if (securityTokenValidatedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0013_SecurityTokenValidatedNotificationSkipped);
}
return securityTokenValidatedNotification;
}
private async Task<AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>> RunAuthenticationFailedNotificationAsync(OpenIdConnectMessage message, Exception exception)
{
var authenticationFailedNotification =
new AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
ProtocolMessage = message,
Exception = exception
};
await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);
if (authenticationFailedNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0018_AuthenticationFailedNotificationHandledResponse);
}
else if (authenticationFailedNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0019_AuthenticationFailedNotificationSkipped);
}
return authenticationFailedNotification;
}
private AuthenticationTicket ValidateToken(string idToken, OpenIdConnectMessage message, AuthenticationProperties properties, TokenValidationParameters validationParameters, out JwtSecurityToken jwt)
{
AuthenticationTicket ticket = null;
jwt = null;
if (_configuration != null)
{
if (string.IsNullOrEmpty(validationParameters.ValidIssuer))
{
validationParameters.ValidIssuer = _configuration.Issuer;
}
else if (!string.IsNullOrEmpty(_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(idToken))
{
principal = validator.ValidateToken(idToken, validationParameters, out validatedToken);
jwt = validatedToken as JwtSecurityToken;
if (jwt == null)
{
Logger.LogError(Resources.OIDCH_0010_ValidatedSecurityTokenNotJwt, validatedToken?.GetType());
throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0010_ValidatedSecurityTokenNotJwt, validatedToken?.GetType()));
}
}
}
if (validatedToken == null)
{
Logger.LogError(Resources.OIDCH_0011_UnableToValidateToken, idToken);
throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0011_UnableToValidateToken, idToken));
}
ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);
if (!string.IsNullOrEmpty(message.SessionState))
{
ticket.Properties.Items[OpenIdConnectSessionProperties.SessionState] = message.SessionState;
}
if (_configuration != null && !string.IsNullOrEmpty(_configuration.CheckSessionIframe))
{
ticket.Properties.Items[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe;
}
// Rename?
if (Options.UseTokenLifetime)
{
var issued = validatedToken.ValidFrom;
if (issued != DateTime.MinValue)
{
ticket.Properties.IssuedUtc = issued;
}
var expires = validatedToken.ValidTo;
if (expires != DateTime.MinValue)
{
ticket.Properties.ExpiresUtc = expires;
}
}
return ticket;
}
private async Task ValidateOpenIdConnectProtocolAsync(JwtSecurityToken jwt, OpenIdConnectMessage message, bool ValidateCHash = true)
{
string nonce = jwt.Payload.Nonce;
if (Options.CacheNonces)
{
if (await Options.NonceCache.GetAsync(nonce) != null)
{
await Options.NonceCache.RemoveAsync(nonce);
}
else
{
// If the nonce cannot be removed, it was
// already used and MUST be rejected.
nonce = null;
}
}
else
{
nonce = ReadNonceCookie(nonce);
}
var protocolValidationContext = new OpenIdConnectProtocolValidationContext
{
Nonce = nonce
};
// If authorization code is null, protocol validator does not validate the chash
if (ValidateCHash)
{
protocolValidationContext.AuthorizationCode = message.Code;
}
Options.ProtocolValidator.Validate(jwt, protocolValidationContext);
}
/// <summary>
/// Calls InvokeReplyPathAsync
/// </summary>

View File

@ -103,6 +103,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
Options.TokenValidationParameters.ValidAudience = Options.ClientId;
}
Backchannel = new HttpClient(ResolveHttpMessageHandler(Options));
Backchannel.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET OpenIdConnect middleware");
Backchannel.Timeout = Options.BackchannelTimeout;
Backchannel.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
if (Options.ConfigurationManager == null)
{
if (Options.Configuration != null)
@ -122,10 +127,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
Options.MetadataAddress += ".well-known/openid-configuration";
}
var httpClient = new HttpClient(ResolveHttpMessageHandler(Options));
httpClient.Timeout = Options.BackchannelTimeout;
httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
Options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(Options.MetadataAddress, httpClient);
Options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(Options.MetadataAddress, Backchannel);
}
}
@ -137,13 +139,15 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
}
protected HttpClient Backchannel { get; private set; }
/// <summary>
/// Provides the <see cref="AuthenticationHandler"/> object for processing authentication-related requests.
/// </summary>
/// <returns>An <see cref="AuthenticationHandler"/> configured with the <see cref="OpenIdConnectAuthenticationOptions"/> supplied to the constructor.</returns>
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new OpenIdConnectAuthenticationHandler();
return new OpenIdConnectAuthenticationHandler(Backchannel);
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]

View File

@ -20,6 +20,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
AuthenticationFailed = notification => Task.FromResult(0);
AuthorizationCodeReceived = notification => Task.FromResult(0);
AuthorizationCodeRedeemed = notificaion => Task.FromResult(0);
MessageReceived = notification => Task.FromResult(0);
SecurityTokenReceived = notification => Task.FromResult(0);
SecurityTokenValidated = notification => Task.FromResult(0);
@ -36,6 +37,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary>
public Func<AuthorizationCodeReceivedNotification, Task> AuthorizationCodeReceived { get; set; }
/// <summary>
/// Invoked after "authorization code" is redeemed for tokens at the token endpoint.
/// </summary>
public Func<AuthorizationCodeRedeemedNotification, Task> AuthorizationCodeRedeemed { get; set; }
/// <summary>
/// Invoked when a protocol message is first received.
/// </summary>

View File

@ -56,6 +56,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
AuthenticationScheme = authenticationScheme;
BackchannelTimeout = TimeSpan.FromMinutes(1);
Caption = OpenIdConnectAuthenticationDefaults.Caption;
GetClaimsFromUserInfoEndpoint = false;
ProtocolValidator = new OpenIdConnectProtocolValidator();
RefreshOnIssuerKeyNotFound = true;
ResponseMode = OpenIdConnectResponseModes.FormPost;
@ -164,6 +165,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary>
public bool DefaultToCurrentUriOnRedirect { get; set; }
/// <summary>
/// Boolean to set whether the middleware should go to user info endpoint to retrieve additional claims or not after creating an identity from id_token received from token endpoint.
/// </summary>
public bool GetClaimsFromUserInfoEndpoint { get; set; }
/// <summary>
/// Gets or sets the discovery endpoint for obtaining metadata
/// </summary>

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
/// <summary>
/// Class to store the response returned from token endpoint
/// </summary>
public class OpenIdConnectTokenEndpointResponse
{
public OpenIdConnectTokenEndpointResponse(JObject jsonResponse)
{
JsonResponse = jsonResponse;
Message = new OpenIdConnectMessage()
{
AccessToken = JsonResponse.Value<string>(OpenIdConnectParameterNames.AccessToken),
IdToken = JsonResponse.Value<string>(OpenIdConnectParameterNames.IdToken),
TokenType = JsonResponse.Value<string>(OpenIdConnectParameterNames.TokenType),
ExpiresIn = JsonResponse.Value<string>(OpenIdConnectParameterNames.ExpiresIn)
};
}
/// <summary>
/// OpenIdConnect message that contains the id token and access tokens
/// </summary>
public OpenIdConnectMessage Message { get; set; }
/// <summary>
/// Json response returned from the token endpoint
/// </summary>
public JObject JsonResponse { get; set; }
}
}

View File

@ -180,6 +180,78 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
get { return ResourceManager.GetString("OIDCH_0037_RedirectUri"); }
}
/// <summary>
/// OIDCH_0038: Id Token is null. Redeeming code : {0} for tokens.
/// </summary>
internal static string OIDCH_0038_Redeeming_Auth_Code
{
get { return ResourceManager.GetString("OIDCH_0038_Redeeming_Auth_Code"); }
}
/// <summary>
/// OIDCH_0039: Subject claim received from userinfo endpoint does not match the one in the id token.
/// </summary>
internal static string OIDCH_0039_Subject_Claim_Mismatch
{
get { return ResourceManager.GetString("OIDCH_0039_Subject_Claim_Mismatch"); }
}
/// <summary>
/// OIDCH_0040: Sending request to user info endpoint for retrieving claims.
/// </summary>
internal static string OIDCH_0040_Sending_Request_UIEndpoint
{
get { return ResourceManager.GetString("OIDCH_0040_Sending_Request_UIEndpoint"); }
}
/// <summary>
/// OIDCH_0041: Subject claim not found in {0}.
/// </summary>
internal static string OIDCH_0041_Subject_Claim_Not_Found
{
get { return ResourceManager.GetString("OIDCH_0041_Subject_Claim_Not_Found"); }
}
/// <summary>
/// OIDCH_0042: AuthorizationCode redeemed: '{0}'
/// </summary>
internal static string OIDCH_0042_AuthorizationCodeRedeemed
{
get { return ResourceManager.GetString("OIDCH_0042_AuthorizationCodeRedeemed"); }
}
/// <summary>
/// OIDCH_0043: AuthorizationCodeRedeemedNotification.HandledResponse
/// </summary>
internal static string OIDCH_0043_AuthorizationCodeRedeemedNotificationHandledResponse
{
get { return ResourceManager.GetString("OIDCH_0043_AuthorizationCodeRedeemedNotificationHandledResponse"); }
}
/// <summary>
/// OIDCH_0044: AuthorizationCodeRedeemedNotification.Skipped
/// </summary>
internal static string OIDCH_0044_AuthorizationCodeRedeemedNotificationSkipped
{
get { return ResourceManager.GetString("OIDCH_0044_AuthorizationCodeRedeemedNotificationSkipped"); }
}
/// <summary>
/// OIDCH_0045: Cannot process the message.Both id_token and code are missing.
/// </summary>
internal static string OIDCH_0045_Id_Token_Code_Missing
{
get { return ResourceManager.GetString("OIDCH_0045_Id_Token_Code_Missing"); }
}
/// <summary>
/// OIDCH_0046: UserInfo endpoint is not set. Request to retrieve claims from userinfo endpoint cannot be completed.
/// </summary>
internal static string OIDCH_0046_UserInfo_Endpoint_Not_Set
{
get { return ResourceManager.GetString("OIDCH_0046_UserInfo_Endpoint_Not_Set"); }
}
/// <summary>
/// OIDCH_0000: Entering: '{0}'.
/// </summary>

View File

@ -162,6 +162,15 @@
<data name="OIDCH_0037_RedirectUri" xml:space="preserve">
<value>OIDCH_0037: RedirectUri is: '{0}'.</value>
</data>
<data name="OIDCH_0038_Redeeming_Auth_Code" xml:space="preserve">
<value>OIDCH_0038: Id Token is null. Redeeming code : {0} for tokens.</value>
</data>
<data name="OIDCH_0039_Subject_Claim_Mismatch" xml:space="preserve">
<value>OIDCH_0039: Subject claim received from userinfo endpoint does not match the one in the id token.</value>
</data>
<data name="OIDCH_0040_Sending_Request_UIEndpoint" xml:space="preserve">
<value>OIDCH_0040: Sending request to user info endpoint for retrieving claims.</value>
</data>
<data name="OIDCH_0000_AuthenticateCoreAsync" xml:space="preserve">
<value>OIDCH_0000: Entering: '{0}'.</value>
</data>
@ -228,4 +237,22 @@
<data name="OIDCH_0021_AutomaticConfigurationRefresh" xml:space="preserve">
<value>OIDCH_0021: exception of type 'SecurityTokenSignatureKeyNotFoundException' thrown, Options.ConfigurationManager.RequestRefresh() called.</value>
</data>
</root>
<data name="OIDCH_0041_Subject_Claim_Not_Found" xml:space="preserve">
<value>OIDCH_0041: Subject claim not found in {0}.</value>
</data>
<data name="OIDCH_0042_AuthorizationCodeRedeemed" xml:space="preserve">
<value>OIDCH_0042: Authorization Code redeemed: '{0}'</value>
</data>
<data name="OIDCH_0043_AuthorizationCodeRedeemedNotificationHandledResponse" xml:space="preserve">
<value>OIDCH_0043: AuthorizationCodeRedeemedNotification.HandledResponse</value>
</data>
<data name="OIDCH_0044_AuthorizationCodeRedeemedNotificationSkipped" xml:space="preserve">
<value>OIDCH_0044: AuthorizationCodeRedeemedNotification.Skipped</value>
</data>
<data name="OIDCH_0045_Id_Token_Code_Missing" xml:space="preserve">
<value>OIDCH_0045: Cannot process the message. Both id_token and code are missing.</value>
</data>
<data name="OIDCH_0046_UserInfo_Endpoint_Not_Set" xml:space="preserve">
<value>OIDCH_0046: UserInfo endpoint is not set. Request to retrieve claims from userinfo endpoint cannot be completed.</value>
</data>
</root>

View File

@ -43,6 +43,12 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{ "OIDCH_0020:", LogLevel.Debug },
{ "OIDCH_0021:", LogLevel.Verbose },
{ "OIDCH_0026:", LogLevel.Error },
{ "OIDCH_0038:", LogLevel.Debug },
{ "OIDCH_0040:", LogLevel.Debug },
{ "OIDCH_0042:", LogLevel.Debug },
{ "OIDCH_0043:", LogLevel.Verbose },
{ "OIDCH_0044:", LogLevel.Verbose },
{ "OIDCH_0045:", LogLevel.Debug }
};
BuildLogEntryList();

View File

@ -2,9 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.OpenIdConnect;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.IdentityModel.Protocols;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
@ -14,7 +18,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
public class OpenIdConnectAuthenticationHandlerForTestingAuthenticate : OpenIdConnectAuthenticationHandler
{
public OpenIdConnectAuthenticationHandlerForTestingAuthenticate()
: base()
: base(null)
{
}
@ -32,6 +36,23 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
return Task.FromResult(0);
}
protected override async Task<OpenIdConnectTokenEndpointResponse> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri)
{
var jsonResponse = new JObject();
jsonResponse.Add(OpenIdConnectParameterNames.IdToken, "test token");
return new OpenIdConnectTokenEndpointResponse(jsonResponse);
}
protected override async Task<AuthenticationTicket> GetUserInformationAsync(AuthenticationProperties properties, OpenIdConnectMessage message, AuthenticationTicket ticket)
{
var claimsIdentity = (ClaimsIdentity)ticket.Principal.Identity;
if (claimsIdentity == null)
{
claimsIdentity = new ClaimsIdentity();
}
claimsIdentity.AddClaim(new Claim("test claim", "test value"));
return new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), ticket.Properties, ticket.AuthenticationScheme);
}
//public override bool ShouldHandleScheme(string authenticationScheme)
//{

View File

@ -104,8 +104,9 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
options.Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = notification =>
AuthorizationCodeRedeemed = notification =>
{
notification.HandleResponse();
if (notification.ProtocolMessage.State == null && !notification.ProtocolMessage.Parameters.ContainsKey(ExpectedStateParameter))
return Task.FromResult<object>(null);
@ -138,7 +139,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
get
{
var formater = new AuthenticationPropertiesFormaterKeyValue();
var dataset = new TheoryData<LogLevel, int[], Action< OpenIdConnectAuthenticationOptions >, OpenIdConnectMessage>();
var dataset = new TheoryData<LogLevel, int[], Action<OpenIdConnectAuthenticationOptions>, OpenIdConnectMessage>();
var properties = new AuthenticationProperties();
var message = new OpenIdConnectMessage();
var validState = UrlEncoder.Default.UrlEncode(formater.Protect(properties));
@ -155,13 +156,13 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
// State - null, empty string, invalid
message = new OpenIdConnectMessage();
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7 }, StateNullOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 28 }, StateNullOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7 }, StateNullOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, StateNullOptions, message);
message = new OpenIdConnectMessage();
message.State = string.Empty;
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7 }, StateEmptyOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 28 }, StateEmptyOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7 }, StateEmptyOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, StateEmptyOptions, message);
@ -225,6 +226,34 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
dataset.Add(LogLevel.Verbose, new int[] { 7, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedSkippedOptions, message);
message = new OpenIdConnectMessage();
message.Code = Guid.NewGuid().ToString();
message.State = validState;
message.IdToken = "test token";
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 14, 15 }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 15 }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 14, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedSkippedOptions, message);
// CodeReceivedAndRedeemed and GetUserInformationFromUIEndpoint
message = new OpenIdConnectMessage();
message.IdToken = null;
message.Code = Guid.NewGuid().ToString();
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 14, 23, 25, 26 }, CodeReceivedAndRedeemedHandledOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7, 26 }, CodeReceivedAndRedeemedHandledOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, CodeReceivedAndRedeemedHandledOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 14, 23, 25, 27 }, CodeReceivedAndRedeemedSkippedOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7, 27 }, CodeReceivedAndRedeemedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, CodeReceivedAndRedeemedSkippedOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 14, 23, 25, 24, 12 }, GetUserInfoFromUIEndpoint, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7, 12 }, GetUserInfoFromUIEndpoint, message);
dataset.Add(LogLevel.Error, new int[] { }, GetUserInfoFromUIEndpoint, message);
return dataset;
}
}
@ -243,6 +272,8 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
private static void AuthorizationCodeReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { MockSecurityTokenValidator() };
options.ProtocolValidator = MockProtocolValidator();
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
@ -257,6 +288,8 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
private static void AuthorizationCodeReceivedSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { MockSecurityTokenValidator() };
options.ProtocolValidator = MockProtocolValidator();
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
@ -271,6 +304,8 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
private static void AuthenticationErrorHandledOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { MockSecurityTokenValidator() };
options.ProtocolValidator = MockProtocolValidator();
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
@ -285,6 +320,8 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
private static void AuthenticationErrorSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { MockSecurityTokenValidator() };
options.ProtocolValidator = MockProtocolValidator();
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
@ -310,6 +347,57 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
};
}
private static void CodeReceivedAndRedeemedHandledOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.ResponseType = OpenIdConnectResponseTypes.Code;
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeRedeemed = (notification) =>
{
notification.HandleResponse();
return Task.FromResult<object>(null);
}
};
}
private static void CodeReceivedAndRedeemedSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.ResponseType = OpenIdConnectResponseTypes.Code;
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeRedeemed = (notification) =>
{
notification.SkipToNextMiddleware();
return Task.FromResult<object>(null);
}
};
}
private static void GetUserInfoFromUIEndpoint(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.ResponseType = OpenIdConnectResponseTypes.Code;
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
options.GetClaimsFromUserInfoEndpoint = true;
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { MockSecurityTokenValidator() };
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = (notification) =>
{
var claimValue = notification.AuthenticationTicket.Principal.FindFirst("test claim");
Assert.Equal(claimValue.Value, "test value");
notification.HandleResponse();
return Task.FromResult<object>(null);
}
};
}
private static void MessageReceivedSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
@ -357,6 +445,21 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
};
}
private static ISecurityTokenValidator MockSecurityTokenValidator()
{
var mockValidator = new Mock<ISecurityTokenValidator>();
mockValidator.Setup(v => v.ValidateToken(It.IsAny<string>(), It.IsAny<TokenValidationParameters>(), out specCompliantJwt)).Returns(new ClaimsPrincipal());
mockValidator.Setup(v => v.CanReadToken(It.IsAny<string>())).Returns(true);
return mockValidator.Object;
}
private static OpenIdConnectProtocolValidator MockProtocolValidator()
{
var mockProtocolValidator = new Mock<OpenIdConnectProtocolValidator>();
mockProtocolValidator.Setup(v => v.Validate(It.IsAny<JwtSecurityToken>(), It.IsAny<OpenIdConnectProtocolValidationContext>()));
return mockProtocolValidator.Object;
}
private static void SecurityTokenValidatorCannotReadToken(OpenIdConnectAuthenticationOptions options)
{
AuthenticationErrorHandledOptions(options);
@ -380,10 +483,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
private static void SecurityTokenValidatorValidatesAllTokens(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
var mockValidator = new Mock<ISecurityTokenValidator>();
mockValidator.Setup(v => v.ValidateToken(It.IsAny<string>(), It.IsAny<TokenValidationParameters>(), out specCompliantJwt)).Returns(new ClaimsPrincipal());
mockValidator.Setup(v => v.CanReadToken(It.IsAny<string>())).Returns(true);
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { mockValidator.Object };
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { MockSecurityTokenValidator() };
options.ProtocolValidator.RequireTimeStampInNonce = false;
options.ProtocolValidator.RequireNonce = false;
}

View File

@ -142,10 +142,6 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
properties.Items.Add("item1", Guid.NewGuid().ToString());
}
else
{
properties.Items.Add(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey, queryValues.RedirectUri);
}
var server = CreateServer(options =>
{
@ -164,6 +160,16 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
var transaction = await SendAsync(server, DefaultHost + challenge);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
if (challenge != ChallengeWithProperties)
{
if (userState != null)
{
properties.Items.Add(OpenIdConnectAuthenticationDefaults.UserstatePropertiesKey, userState);
}
properties.Items.Add(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey, queryValues.RedirectUri);
}
queryValues.State = stateDataFormat.Protect(properties);
queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters(new string[] { OpenIdConnectParameterNames.State }));
}