#456 Unify OIDC Code/IdToken/Hybride flows.

This commit is contained in:
Chris R 2016-02-03 16:12:53 -08:00
parent bbcabc0212
commit 34bc9c52e1
2 changed files with 232 additions and 243 deletions

View File

@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
@ -34,6 +35,28 @@ namespace OpenIdConnectSample
{ {
loggerfactory.AddConsole(LogLevel.Information); loggerfactory.AddConsole(LogLevel.Information);
// Simple error page
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
if (!context.Response.HasStarted)
{
context.Response.Clear();
context.Response.StatusCode = 500;
await context.Response.WriteAsync(ex.ToString());
}
else
{
throw;
}
}
});
app.UseIISPlatformHandler(); app.UseIISPlatformHandler();
app.UseCookieAuthentication(new CookieAuthenticationOptions app.UseCookieAuthentication(new CookieAuthenticationOptions
@ -52,17 +75,30 @@ namespace OpenIdConnectSample
app.Run(async context => app.Run(async context =>
{ {
if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) if (context.Request.Path.Equals("/signout"))
{ {
await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" }); await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
context.Response.ContentType = "text/html";
context.Response.ContentType = "text/plain"; await context.Response.WriteAsync($"<html><body>Signing out {context.User.Identity.Name}<br>{Environment.NewLine}");
await context.Response.WriteAsync("Hello First timer"); await context.Response.WriteAsync("<a href=\"/\">Sign In</a>");
await context.Response.WriteAsync($"</body></html>");
return; return;
} }
context.Response.ContentType = "text/plain"; if (!context.User.Identities.Any(identity => identity.IsAuthenticated))
await context.Response.WriteAsync("Hello Authenticated User"); {
await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
return;
}
context.Response.ContentType = "text/html";
await context.Response.WriteAsync($"<html><body>Hello Authenticated User {context.User.Identity.Name}<br>{Environment.NewLine}");
foreach (var claim in context.User.Claims)
{
await context.Response.WriteAsync($"{claim.Type}: {claim.Value}<br>{Environment.NewLine}");
}
await context.Response.WriteAsync("<a href=\"/signout\">Sign Out</a>");
await context.Response.WriteAsync($"</body></html>");
}); });
} }

View File

@ -308,16 +308,16 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
{ {
Logger.LogTrace(10, "Entering: {0}." + nameof(HandleRemoteAuthenticateAsync), GetType()); Logger.LogTrace(10, "Entering: {0}." + nameof(HandleRemoteAuthenticateAsync), GetType());
OpenIdConnectMessage message = null; OpenIdConnectMessage authorizationResponse = null;
if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
{ {
message = new OpenIdConnectMessage(Request.Query.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value))); authorizationResponse = new OpenIdConnectMessage(Request.Query.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));
// response_mode=query (explicit or not) and a response_type containing id_token // response_mode=query (explicit or not) and a response_type containing id_token
// or token are not considered as a safe combination and MUST be rejected. // or token are not considered as a safe combination and MUST be rejected.
// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.AccessToken)) if (!string.IsNullOrEmpty(authorizationResponse.IdToken) || !string.IsNullOrEmpty(authorizationResponse.AccessToken))
{ {
if (Options.SkipUnrecognizedRequests) if (Options.SkipUnrecognizedRequests)
{ {
@ -336,10 +336,10 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
&& Request.Body.CanRead) && Request.Body.CanRead)
{ {
var form = await Request.ReadFormAsync(); var form = await Request.ReadFormAsync();
message = new OpenIdConnectMessage(form.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value))); authorizationResponse = new OpenIdConnectMessage(form.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));
} }
if (message == null) if (authorizationResponse == null)
{ {
if (Options.SkipUnrecognizedRequests) if (Options.SkipUnrecognizedRequests)
{ {
@ -349,54 +349,52 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
return AuthenticateResult.Fail("No message."); return AuthenticateResult.Fail("No message.");
} }
AuthenticateResult result;
try try
{ {
var messageReceivedContext = await RunMessageReceivedEventAsync(message); var messageReceivedContext = await RunMessageReceivedEventAsync(authorizationResponse);
if (messageReceivedContext.HandledResponse) if (CheckEventResult(messageReceivedContext, out result))
{ {
return AuthenticateResult.Success(messageReceivedContext.Ticket); return result;
} }
else if (messageReceivedContext.Skipped) authorizationResponse = messageReceivedContext.ProtocolMessage;
{
return AuthenticateResult.Skip();
}
message = messageReceivedContext.ProtocolMessage;
// Fail if state is missing, it's required for the correlation id. // Fail if state is missing, it's required for the correlation id.
if (string.IsNullOrEmpty(message.State)) if (string.IsNullOrEmpty(authorizationResponse.State))
{ {
// This wasn't a valid OIDC message, it may not have been intended for us. // This wasn't a valid OIDC message, it may not have been intended for us.
Logger.LogDebug(11, "message.State is null or empty.");
if (Options.SkipUnrecognizedRequests) if (Options.SkipUnrecognizedRequests)
{ {
return AuthenticateResult.Skip(); return AuthenticateResult.Skip();
} }
Logger.LogDebug(11, "message.State is null or empty.");
return AuthenticateResult.Fail(Resources.MessageStateIsNullOrEmpty); return AuthenticateResult.Fail(Resources.MessageStateIsNullOrEmpty);
} }
// if state exists and we failed to 'unprotect' this is not a message we should process. // if state exists and we failed to 'unprotect' this is not a message we should process.
var properties = Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(message.State)); var properties = Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(authorizationResponse.State));
if (properties == null) if (properties == null)
{ {
Logger.LogDebug(12, "Unable to read the message.State.");
if (Options.SkipUnrecognizedRequests) if (Options.SkipUnrecognizedRequests)
{ {
// Not for us? // Not for us?
return AuthenticateResult.Skip(); return AuthenticateResult.Skip();
} }
Logger.LogError(12, "Unable to read the message.State.");
return AuthenticateResult.Fail(Resources.MessageStateIsInvalid); return AuthenticateResult.Fail(Resources.MessageStateIsInvalid);
} }
// if any of the error fields are set, throw error null // if any of the error fields are set, throw error null
if (!string.IsNullOrEmpty(message.Error)) if (!string.IsNullOrEmpty(authorizationResponse.Error))
{ {
Logger.LogError(13, "Message contains error: '{0}', error_description: '{1}', error_uri: '{2}'.", message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null"); Logger.LogError(13, "Message contains error: '{0}', error_description: '{1}', error_uri: '{2}'.", authorizationResponse.Error, authorizationResponse.ErrorDescription ?? "ErrorDecription null", authorizationResponse.ErrorUri ?? "ErrorUri null");
return AuthenticateResult.Fail(new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null"))); return AuthenticateResult.Fail(new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.MessageContainsError, authorizationResponse.Error, authorizationResponse.ErrorDescription ?? "ErrorDecription null", authorizationResponse.ErrorUri ?? "ErrorUri null")));
} }
string userstate = null; string userstate = null;
properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate); properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate);
message.State = userstate; authorizationResponse.State = userstate;
if (!ValidateCorrelationId(properties)) if (!ValidateCorrelationId(properties))
{ {
@ -409,38 +407,113 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
} }
Logger.LogTrace(15, "Authorization response received."); var authorizationResponseReceivedContext = await RunAuthorizationResponseReceivedEventAsync(authorizationResponse, properties);
var authorizationResponseReceivedContext = new AuthorizationResponseReceivedContext(Context, Options, properties) if (CheckEventResult(authorizationResponseReceivedContext, out result))
{ {
ProtocolMessage = message return result;
};
await Options.Events.AuthorizationResponseReceived(authorizationResponseReceivedContext);
if (authorizationResponseReceivedContext.HandledResponse)
{
Logger.LogDebug(16, "AuthorizationResponseReceived.HandledResponse");
return AuthenticateResult.Success(authorizationResponseReceivedContext.Ticket);
} }
else if (authorizationResponseReceivedContext.Skipped) authorizationResponse = authorizationResponseReceivedContext.ProtocolMessage;
{
Logger.LogDebug(17, "AuthorizationResponseReceived.Skipped");
return AuthenticateResult.Skip();
}
message = authorizationResponseReceivedContext.ProtocolMessage;
properties = authorizationResponseReceivedContext.Properties; properties = authorizationResponseReceivedContext.Properties;
if (string.IsNullOrEmpty(message.IdToken) && !string.IsNullOrEmpty(message.Code)) PopulateSessionProperties(authorizationResponse, properties);
AuthenticationTicket ticket = null;
JwtSecurityToken jwt = null;
string nonce = null;
var validationParameters = Options.TokenValidationParameters.Clone();
// Hybrid or Implicit flow
if (!string.IsNullOrEmpty(authorizationResponse.IdToken))
{ {
return await HandleCodeOnlyFlow(message, properties); Logger.LogDebug(23, "'id_token' received.");
ticket = ValidateToken(authorizationResponse.IdToken, properties, validationParameters, out jwt);
nonce = jwt?.Payload.Nonce;
if (!string.IsNullOrEmpty(nonce))
{
nonce = ReadNonceCookie(nonce);
}
} }
else if (!string.IsNullOrEmpty(message.IdToken))
Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext()
{ {
return await HandleIdTokenFlows(message, properties); ClientId = Options.ClientId,
} ProtocolMessage = authorizationResponse,
else ValidatedIdToken = jwt,
Nonce = nonce
});
// TODO: AuthorizationResponseValidated event?
OpenIdConnectMessage tokenEndpointResponse = null;
// Authorization Code or Hybrid flow
if (!string.IsNullOrEmpty(authorizationResponse.Code))
{ {
Logger.LogTrace(18, "Cannot process the message. Both id_token and code are missing."); // TODO: Does this event provide any value over AuthorizationResponseReceived or AuthorizationResponseValidated?
return AuthenticateResult.Fail(Resources.IdTokenCodeMissing); var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(authorizationResponse, properties, ticket, jwt);
if (CheckEventResult(authorizationCodeReceivedContext, out result))
{
return result;
}
authorizationResponse = authorizationCodeReceivedContext.ProtocolMessage;
var code = authorizationCodeReceivedContext.Code;
tokenEndpointResponse = await RedeemAuthorizationCodeAsync(code, authorizationCodeReceivedContext.RedirectUri);
var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, properties);
if (CheckEventResult(authorizationCodeRedeemedContext, out result))
{
return result;
}
authorizationResponse = authorizationCodeRedeemedContext.ProtocolMessage;
tokenEndpointResponse = authorizationCodeRedeemedContext.TokenEndpointResponse;
// We only have to process the IdToken if we didn't already get one in the AuthorizationResponse
if (ticket == 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].
validationParameters.RequireSignedTokens = false;
ticket = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out jwt);
nonce = jwt?.Payload.Nonce;
if (!string.IsNullOrEmpty(nonce))
{
nonce = ReadNonceCookie(nonce);
}
}
Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext()
{
ClientId = Options.ClientId,
ProtocolMessage = tokenEndpointResponse,
ValidatedIdToken = jwt,
Nonce = nonce
});
} }
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(authorizationResponse, ticket, properties, tokenEndpointResponse);
if (CheckEventResult(authenticationValidatedContext, out result))
{
return result;
}
authorizationResponse = authenticationValidatedContext.ProtocolMessage;
tokenEndpointResponse = authenticationValidatedContext.TokenEndpointResponse;
ticket = authenticationValidatedContext.Ticket;
if (Options.SaveTokensAsClaims)
{
SaveTokens(ticket.Principal, tokenEndpointResponse ?? authorizationResponse, jwt.Issuer);
}
if (Options.GetClaimsFromUserInfoEndpoint)
{
return await GetUserInformationAsync(tokenEndpointResponse ?? authorizationResponse, jwt, ticket);
}
return AuthenticateResult.Success(ticket);
} }
catch (Exception exception) catch (Exception exception)
{ {
@ -456,179 +529,43 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
} }
} }
var authenticationFailedContext = await RunAuthenticationFailedEventAsync(message, exception); var authenticationFailedContext = await RunAuthenticationFailedEventAsync(authorizationResponse, exception);
if (authenticationFailedContext.HandledResponse) if (CheckEventResult(authenticationFailedContext, out result))
{ {
return AuthenticateResult.Success(authenticationFailedContext.Ticket); return result;
}
else if (authenticationFailedContext.Skipped)
{
return AuthenticateResult.Skip();
} }
throw; return AuthenticateResult.Fail(exception);
} }
} }
// Authorization Code Flow private bool CheckEventResult(BaseControlContext context, out AuthenticateResult result)
private async Task<AuthenticateResult> HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties)
{ {
AuthenticationTicket ticket = null; if (context.HandledResponse)
JwtSecurityToken jwt = null;
Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext()
{ {
ClientId = Options.ClientId, result = AuthenticateResult.Success(context.Ticket);
ProtocolMessage = message, return true;
});
var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt);
if (authorizationCodeReceivedContext.HandledResponse)
{
return AuthenticateResult.Success(authorizationCodeReceivedContext.Ticket);
} }
else if (authorizationCodeReceivedContext.Skipped) else if (context.Skipped)
{ {
return AuthenticateResult.Skip(); result = AuthenticateResult.Skip();
return true;
} }
message = authorizationCodeReceivedContext.ProtocolMessage; result = null;
var code = authorizationCodeReceivedContext.Code; return false;
// Redeeming authorization code for tokens
Logger.LogTrace(21, "Id Token is null. Redeeming code '{0}' for tokens.", code);
var tokenEndpointResponse = await RedeemAuthorizationCodeAsync(code, authorizationCodeReceivedContext.RedirectUri);
var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse, properties);
if (authorizationCodeRedeemedContext.HandledResponse)
{
return AuthenticateResult.Success(authorizationCodeRedeemedContext.Ticket);
}
else if (authorizationCodeRedeemedContext.Skipped)
{
return AuthenticateResult.Skip();
}
message = authorizationCodeRedeemedContext.ProtocolMessage;
tokenEndpointResponse = authorizationCodeRedeemedContext.TokenEndpointResponse;
// 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.RequireSignedTokens = false;
ticket = ValidateToken(tokenEndpointResponse.IdToken, message, properties, validationParameters, out jwt);
var nonce = jwt?.Payload.Nonce;
if (!string.IsNullOrEmpty(nonce))
{
nonce = ReadNonceCookie(nonce);
}
Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext()
{
ClientId = Options.ClientId,
ProtocolMessage = tokenEndpointResponse,
ValidatedIdToken = jwt,
Nonce = nonce
});
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, properties, tokenEndpointResponse);
if (authenticationValidatedContext.HandledResponse)
{
return AuthenticateResult.Success(authenticationValidatedContext.Ticket);
}
else if (authenticationValidatedContext.Skipped)
{
return AuthenticateResult.Skip();
}
ticket = authenticationValidatedContext.Ticket;
if (Options.SaveTokensAsClaims)
{
// Persist the tokens extracted from the token response.
SaveTokens(ticket.Principal, tokenEndpointResponse, jwt.Issuer, saveRefreshToken: true);
}
if (Options.GetClaimsFromUserInfoEndpoint)
{
Logger.LogTrace(22, "Sending request to user info endpoint for retrieving claims.");
ticket = await GetUserInformationAsync(tokenEndpointResponse, jwt, ticket);
}
return AuthenticateResult.Success(ticket);
} }
// Implicit Flow or Hybrid Flow private void PopulateSessionProperties(OpenIdConnectMessage message, AuthenticationProperties properties)
private async Task<AuthenticateResult> HandleIdTokenFlows(OpenIdConnectMessage message, AuthenticationProperties properties)
{ {
Logger.LogTrace(23, "'id_token' received: '{0}'", message.IdToken); if (!string.IsNullOrEmpty(message.SessionState))
JwtSecurityToken jwt = null;
var validationParameters = Options.TokenValidationParameters.Clone();
var ticket = ValidateToken(message.IdToken, message, properties, validationParameters, out jwt);
var nonce = jwt?.Payload.Nonce;
if (!string.IsNullOrEmpty(nonce))
{ {
nonce = ReadNonceCookie(nonce); properties.Items[OpenIdConnectSessionProperties.SessionState] = message.SessionState;
} }
Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext() if (!string.IsNullOrEmpty(_configuration.CheckSessionIframe))
{ {
ClientId = Options.ClientId, properties.Items[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe;
ProtocolMessage = message,
ValidatedIdToken = jwt,
Nonce = nonce
});
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, properties, tokenEndpointResponse: null);
if (authenticationValidatedContext.HandledResponse)
{
return AuthenticateResult.Success(authenticationValidatedContext.Ticket);
} }
else if (authenticationValidatedContext.Skipped)
{
return AuthenticateResult.Skip();
}
message = authenticationValidatedContext.ProtocolMessage;
ticket = authenticationValidatedContext.Ticket;
// Hybrid Flow
if (message.Code != null)
{
var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt);
if (authorizationCodeReceivedContext.HandledResponse)
{
return AuthenticateResult.Success(authorizationCodeReceivedContext.Ticket);
}
else if (authorizationCodeReceivedContext.Skipped)
{
return AuthenticateResult.Skip();
}
message = authorizationCodeReceivedContext.ProtocolMessage;
ticket = authorizationCodeReceivedContext.Ticket;
if (Options.SaveTokensAsClaims)
{
// TODO: call SaveTokens with the token response and set
// saveRefreshToken to true when the hybrid flow is fully implemented.
SaveTokens(ticket.Principal, message, jwt.Issuer, saveRefreshToken: false);
}
}
// Implicit Flow
else
{
if (Options.SaveTokensAsClaims)
{
// Note: don't save the refresh token when it is extracted from the authorization
// response, since it's not a valid parameter when using the implicit flow.
// See http://openid.net/specs/openid-connect-core-1_0.html#Authentication
// and https://tools.ietf.org/html/rfc6749#section-4.2.2.
SaveTokens(ticket.Principal, message, jwt.Issuer, saveRefreshToken: false);
}
}
return AuthenticateResult.Success(ticket);
} }
/// <summary> /// <summary>
@ -639,6 +576,8 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
/// <returns>OpenIdConnect message that has tokens inside it.</returns> /// <returns>OpenIdConnect message that has tokens inside it.</returns>
protected virtual async Task<OpenIdConnectMessage> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri) protected virtual async Task<OpenIdConnectMessage> RedeemAuthorizationCodeAsync(string authorizationCode, string redirectUri)
{ {
Logger.LogDebug(21, "Redeeming code for tokens.");
var openIdMessage = new OpenIdConnectMessage() var openIdMessage = new OpenIdConnectMessage()
{ {
ClientId = Options.ClientId, ClientId = Options.ClientId,
@ -648,6 +587,8 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
RedirectUri = redirectUri RedirectUri = redirectUri
}; };
// TODO: Event that lets you customize the message. E.g. use certificates, specify resources.
var requestMessage = new HttpRequestMessage(HttpMethod.Post, _configuration.TokenEndpoint); var requestMessage = new HttpRequestMessage(HttpMethod.Post, _configuration.TokenEndpoint);
requestMessage.Content = new FormUrlEncodedContent(openIdMessage.Parameters); requestMessage.Content = new FormUrlEncodedContent(openIdMessage.Parameters);
var responseMessage = await Backchannel.SendAsync(requestMessage); var responseMessage = await Backchannel.SendAsync(requestMessage);
@ -663,21 +604,28 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
/// <param name="message">message that is being processed</param> /// <param name="message">message that is being processed</param>
/// <param name="ticket">authentication ticket with claims principal and identities</param> /// <param name="ticket">authentication ticket with claims principal and identities</param>
/// <returns>Authentication ticket with identity with additional claims, if any.</returns> /// <returns>Authentication ticket with identity with additional claims, if any.</returns>
protected virtual async Task<AuthenticationTicket> GetUserInformationAsync(OpenIdConnectMessage message, JwtSecurityToken jwt, AuthenticationTicket ticket) protected virtual async Task<AuthenticateResult> GetUserInformationAsync(OpenIdConnectMessage message, JwtSecurityToken jwt, AuthenticationTicket ticket)
{ {
var userInfoEndpoint = _configuration?.UserInfoEndpoint; var userInfoEndpoint = _configuration?.UserInfoEndpoint;
if (string.IsNullOrEmpty(userInfoEndpoint)) if (string.IsNullOrEmpty(userInfoEndpoint))
{ {
Logger.LogWarning(24, nameof(_configuration.UserInfoEndpoint) + " is not set. Request to retrieve claims cannot be completed."); Logger.LogDebug(24, $"{nameof(_configuration.UserInfoEndpoint)} is not set. Claims cannot be retrieved.");
return ticket; return AuthenticateResult.Success(ticket);
} }
if (string.IsNullOrEmpty(message.AccessToken))
{
Logger.LogDebug(47, "The access_token is not available. Claims cannot be retrieved.");
return AuthenticateResult.Success(ticket);
}
Logger.LogTrace(22, "Retrieving claims from the user info endpoint.");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint); var requestMessage = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", message.AccessToken); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", message.AccessToken);
var responseMessage = await Backchannel.SendAsync(requestMessage); var responseMessage = await Backchannel.SendAsync(requestMessage);
responseMessage.EnsureSuccessStatusCode(); responseMessage.EnsureSuccessStatusCode();
var userInfoResponse = await responseMessage.Content.ReadAsStringAsync(); var userInfoResponse = await responseMessage.Content.ReadAsStringAsync();
JObject user; JObject user;
var contentType = responseMessage.Content.Headers.ContentType; var contentType = responseMessage.Content.Headers.ContentType;
if (contentType.MediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase)) if (contentType.MediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase))
@ -691,17 +639,14 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
} }
else else
{ {
throw new NotSupportedException("Unknown response type: " + contentType.MediaType); return AuthenticateResult.Fail("Unknown response type: " + contentType.MediaType);
} }
var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(ticket, message, user); var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(ticket, message, user);
if (userInformationReceivedContext.HandledResponse) AuthenticateResult result;
if (CheckEventResult(userInformationReceivedContext, out result))
{ {
return userInformationReceivedContext.Ticket; return result;
}
else if (userInformationReceivedContext.Skipped)
{
return ticket;
} }
ticket = userInformationReceivedContext.Ticket; ticket = userInformationReceivedContext.Ticket;
user = userInformationReceivedContext.User; user = userInformationReceivedContext.User;
@ -742,7 +687,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, jwt.Issuer)); identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, jwt.Issuer));
} }
return ticket; return AuthenticateResult.Success(ticket);
} }
/// <summary> /// <summary>
@ -750,8 +695,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
/// </summary> /// </summary>
/// <param name="principal">The principal in which tokens are saved.</param> /// <param name="principal">The principal in which tokens are saved.</param>
/// <param name="message">The OpenID Connect response.</param> /// <param name="message">The OpenID Connect response.</param>
/// <param name="saveRefreshToken">A <see cref="bool"/> indicating whether the refresh token should be stored.</param> private void SaveTokens(ClaimsPrincipal principal, OpenIdConnectMessage message, string issuer)
private void SaveTokens(ClaimsPrincipal principal, OpenIdConnectMessage message, string issuer, bool saveRefreshToken)
{ {
var identity = (ClaimsIdentity)principal.Identity; var identity = (ClaimsIdentity)principal.Identity;
@ -767,7 +711,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
ClaimValueTypes.String, issuer)); ClaimValueTypes.String, issuer));
} }
if (saveRefreshToken && !string.IsNullOrEmpty(message.RefreshToken)) if (!string.IsNullOrEmpty(message.RefreshToken))
{ {
identity.AddClaim(new Claim(OpenIdConnectParameterNames.RefreshToken, message.RefreshToken, identity.AddClaim(new Claim(OpenIdConnectParameterNames.RefreshToken, message.RefreshToken,
ClaimValueTypes.String, issuer)); ClaimValueTypes.String, issuer));
@ -911,6 +855,25 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
return messageReceivedContext; return messageReceivedContext;
} }
private async Task<AuthorizationResponseReceivedContext> RunAuthorizationResponseReceivedEventAsync(OpenIdConnectMessage message, AuthenticationProperties properties)
{
Logger.LogTrace(15, "Authorization response received.");
var authorizationResponseReceivedContext = new AuthorizationResponseReceivedContext(Context, Options, properties)
{
ProtocolMessage = message
};
await Options.Events.AuthorizationResponseReceived(authorizationResponseReceivedContext);
if (authorizationResponseReceivedContext.HandledResponse)
{
Logger.LogDebug(16, "AuthorizationResponseReceived.HandledResponse");
}
else if (authorizationResponseReceivedContext.Skipped)
{
Logger.LogDebug(17, "AuthorizationResponseReceived.Skipped");
}
return authorizationResponseReceivedContext;
}
private async Task<AuthorizationCodeReceivedContext> RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage message, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt) private async Task<AuthorizationCodeReceivedContext> RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage message, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt)
{ {
var redirectUri = properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]; var redirectUri = properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey];
@ -960,13 +923,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
return tokenResponseReceivedContext; return tokenResponseReceivedContext;
} }
private async Task<AuthenticationValidatedContext> RunAuthenticationValidatedEventAsync(OpenIdConnectMessage message, AuthenticationTicket ticket, AuthenticationProperties properties, OpenIdConnectMessage tokenEndpointResponse) private async Task<AuthenticationValidatedContext> RunAuthenticationValidatedEventAsync(OpenIdConnectMessage message, AuthenticationTicket ticket, AuthenticationProperties properties, OpenIdConnectMessage tokenResponse)
{ {
var authenticationValidatedContext = new AuthenticationValidatedContext(Context, Options, properties) var authenticationValidatedContext = new AuthenticationValidatedContext(Context, Options, properties)
{ {
Ticket = ticket, Ticket = ticket,
ProtocolMessage = message, ProtocolMessage = message,
TokenEndpointResponse = tokenEndpointResponse, TokenEndpointResponse = tokenResponse,
}; };
await Options.Events.AuthenticationValidated(authenticationValidatedContext); await Options.Events.AuthenticationValidated(authenticationValidatedContext);
@ -1027,10 +990,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
return authenticationFailedContext; return authenticationFailedContext;
} }
private AuthenticationTicket ValidateToken(string idToken, OpenIdConnectMessage message, AuthenticationProperties properties, TokenValidationParameters validationParameters, out JwtSecurityToken jwt) private AuthenticationTicket ValidateToken(string idToken, AuthenticationProperties properties, TokenValidationParameters validationParameters, out JwtSecurityToken jwt)
{ {
AuthenticationTicket ticket = null; if (!Options.SecurityTokenValidator.CanReadToken(idToken))
jwt = null; {
Logger.LogError(48, "Unable to read the 'id_token', no suitable ISecurityTokenValidator was found for: '{0}'.", idToken);
throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.UnableToValidateToken, idToken));
}
if (_configuration != null) if (_configuration != null)
{ {
@ -1047,16 +1013,12 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
} }
SecurityToken validatedToken = null; SecurityToken validatedToken = null;
ClaimsPrincipal principal = null; var principal = Options.SecurityTokenValidator.ValidateToken(idToken, validationParameters, out validatedToken);
if (Options.SecurityTokenValidator.CanReadToken(idToken)) jwt = validatedToken as JwtSecurityToken;
if (jwt == null)
{ {
principal = Options.SecurityTokenValidator.ValidateToken(idToken, validationParameters, out validatedToken); Logger.LogError(45, "The Validated Security Token must be of type JwtSecurityToken, but instead its type is: '{0}'", validatedToken?.GetType());
jwt = validatedToken as JwtSecurityToken; throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.ValidatedSecurityTokenNotJwt, validatedToken?.GetType()));
if (jwt == null)
{
Logger.LogError(45, "The Validated Security Token must be of type JwtSecurityToken, but instead its type is: '{0}'", validatedToken?.GetType());
throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.ValidatedSecurityTokenNotJwt, validatedToken?.GetType()));
}
} }
if (validatedToken == null) if (validatedToken == null)
@ -1065,16 +1027,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.UnableToValidateToken, idToken)); throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.UnableToValidateToken, idToken));
} }
ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme); var 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;
}
if (Options.UseTokenLifetime) if (Options.UseTokenLifetime)
{ {