Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Add AuthenticationProperties to HandleRequestResult and RemoteFailureContext #1299

Merged
merged 1 commit into from
Sep 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 47 additions & 12 deletions samples/SocialSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public void ConfigureServices(IServiceCollection services)
o.Fields.Add("name");
o.Fields.Add("email");
o.SaveTokens = true;
o.Events = new OAuthEvents()
{
OnRemoteFailure = HandleOnRemoteFailure
};
})
// You must first create an app with Google and add its ID and Secret to your user-secrets.
// https://console.developers.google.com/project
Expand All @@ -81,6 +85,10 @@ public void ConfigureServices(IServiceCollection services)
o.Scope.Add("profile");
o.Scope.Add("email");
o.SaveTokens = true;
o.Events = new OAuthEvents()
{
OnRemoteFailure = HandleOnRemoteFailure
};
})
// You must first create an app with Google and add its ID and Secret to your user-secrets.
// https://console.developers.google.com/project
Expand All @@ -93,12 +101,7 @@ public void ConfigureServices(IServiceCollection services)
o.SaveTokens = true;
o.Events = new OAuthEvents()
{
OnRemoteFailure = ctx =>
{
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
ctx.HandleResponse();
return Task.FromResult(0);
}
OnRemoteFailure = HandleOnRemoteFailure
};
o.ClaimActions.MapJsonSubKey("urn:google:image", "image", "url");
o.ClaimActions.Remove(ClaimTypes.GivenName);
Expand All @@ -116,12 +119,7 @@ public void ConfigureServices(IServiceCollection services)
o.ClaimActions.MapJsonKey("urn:twitter:profilepicture", "profile_image_url", ClaimTypes.Uri);
o.Events = new TwitterEvents()
{
OnRemoteFailure = ctx =>
{
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
ctx.HandleResponse();
return Task.FromResult(0);
}
OnRemoteFailure = HandleOnRemoteFailure
};
})
/* Azure AD app model v2 has restrictions that prevent the use of plain HTTP for redirect URLs.
Expand All @@ -139,6 +137,10 @@ public void ConfigureServices(IServiceCollection services)
o.TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint;
o.Scope.Add("https://graph.microsoft.com/user.read");
o.SaveTokens = true;
o.Events = new OAuthEvents()
{
OnRemoteFailure = HandleOnRemoteFailure
};
})
// You must first create an app with Microsoft Account and add its ID and Secret to your user-secrets.
// https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-app-registration/
Expand All @@ -148,6 +150,10 @@ public void ConfigureServices(IServiceCollection services)
o.ClientSecret = Configuration["microsoftaccount:clientsecret"];
o.SaveTokens = true;
o.Scope.Add("offline_access");
o.Events = new OAuthEvents()
{
OnRemoteFailure = HandleOnRemoteFailure
};
})
// You must first create an app with GitHub and add its ID and Secret to your user-secrets.
// https://github.com/settings/applications/
Expand All @@ -159,6 +165,10 @@ public void ConfigureServices(IServiceCollection services)
o.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
o.TokenEndpoint = "https://github.com/login/oauth/access_token";
o.SaveTokens = true;
o.Events = new OAuthEvents()
{
OnRemoteFailure = HandleOnRemoteFailure
};
})
// You must first create an app with GitHub and add its ID and Secret to your user-secrets.
// https://github.com/settings/applications/
Expand All @@ -180,6 +190,7 @@ public void ConfigureServices(IServiceCollection services)
o.ClaimActions.MapJsonKey("urn:github:url", "url");
o.Events = new OAuthEvents
{
OnRemoteFailure = HandleOnRemoteFailure,
OnCreatingTicket = async context =>
{
// Get the GitHub user
Expand All @@ -198,6 +209,30 @@ public void ConfigureServices(IServiceCollection services)
});
}

private async Task HandleOnRemoteFailure(RemoteFailureContext context)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html><body>");
await context.Response.WriteAsync("A remote failure has occurred: " + UrlEncoder.Default.Encode(context.Failure.Message) + "<br>");

if (context.Properties != null)
{
await context.Response.WriteAsync("Properties:<br>");
foreach (var pair in context.Properties.Items)
{
await context.Response.WriteAsync($"-{ UrlEncoder.Default.Encode(pair.Key)}={ UrlEncoder.Default.Encode(pair.Value)}<br>");
}
}

await context.Response.WriteAsync("<a href=\"/\">Home</a>");
await context.Response.WriteAsync("</body></html>");

// context.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(context.Failure.Message));

context.HandleResponse();
}

public void Configure(IApplicationBuilder app)
{
app.UseDeveloperExceptionPage();
Expand Down
38 changes: 19 additions & 19 deletions src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,22 @@ public OAuthHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, Ur

protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync()
{
AuthenticationProperties properties = null;
var query = Request.Query;

var state = query["state"];
var properties = Options.StateDataFormat.Unprotect(state);

if (properties == null)
{
return HandleRequestResult.Fail("The oauth state was missing or invalid.");
}

// OAuth2 10.12 CSRF
if (!ValidateCorrelationId(properties))
{
return HandleRequestResult.Fail("Correlation failed.", properties);
}

var error = query["error"];
if (!StringValues.IsNullOrEmpty(error))
{
Expand All @@ -63,39 +76,26 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
failureMessage.Append(";Uri=").Append(errorUri);
}

return HandleRequestResult.Fail(failureMessage.ToString());
return HandleRequestResult.Fail(failureMessage.ToString(), properties);
}

var code = query["code"];
var state = query["state"];

properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
return HandleRequestResult.Fail("The oauth state was missing or invalid.");
}

// OAuth2 10.12 CSRF
if (!ValidateCorrelationId(properties))
{
return HandleRequestResult.Fail("Correlation failed.");
}

if (StringValues.IsNullOrEmpty(code))
{
return HandleRequestResult.Fail("Code was not found.");
return HandleRequestResult.Fail("Code was not found.", properties);
}

var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));

if (tokens.Error != null)
{
return HandleRequestResult.Fail(tokens.Error);
return HandleRequestResult.Fail(tokens.Error, properties);
}

if (string.IsNullOrEmpty(tokens.AccessToken))
{
return HandleRequestResult.Fail("Failed to retrieve access token.");
return HandleRequestResult.Fail("Failed to retrieve access token.", properties);
}

var identity = new ClaimsIdentity(ClaimsIssuer);
Expand Down Expand Up @@ -141,7 +141,7 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
}
else
{
return HandleRequestResult.Fail("Failed to retrieve user information from remote server.");
return HandleRequestResult.Fail("Failed to retrieve user information from remote server.", properties);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,13 +491,10 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
return HandleRequestResult.Fail("No message.");
}

AuthenticationProperties properties = null;
try
{
AuthenticationProperties properties = null;
if (!string.IsNullOrEmpty(authorizationResponse.State))
{
properties = Options.StateDataFormat.Unprotect(authorizationResponse.State);
}
properties = ReadPropertiesAndClearState(authorizationResponse);

var messageReceivedContext = await RunMessageReceivedEventAsync(authorizationResponse, properties);
if (messageReceivedContext.Result != null)
Expand All @@ -521,8 +518,7 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
return HandleRequestResult.Fail(Resources.MessageStateIsNullOrEmpty);
}

// if state exists and we failed to 'unprotect' this is not a message we should process.
properties = Options.StateDataFormat.Unprotect(authorizationResponse.State);
properties = ReadPropertiesAndClearState(authorizationResponse);
}

if (properties == null)
Expand All @@ -533,21 +529,20 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
// Not for us?
return HandleRequestResult.SkipHandler();
}

// if state exists and we failed to 'unprotect' this is not a message we should process.
return HandleRequestResult.Fail(Resources.MessageStateIsInvalid);
}

properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out string userstate);
authorizationResponse.State = userstate;

if (!ValidateCorrelationId(properties))
{
return HandleRequestResult.Fail("Correlation failed.");
return HandleRequestResult.Fail("Correlation failed.", properties);
}

// if any of the error fields are set, throw error null
if (!string.IsNullOrEmpty(authorizationResponse.Error))
{
return HandleRequestResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null));
return HandleRequestResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null), properties);
}

if (_configuration == null && Options.ConfigurationManager != null)
Expand Down Expand Up @@ -635,8 +630,7 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync

// At least a cursory validation is required on the new IdToken, even if we've already validated the one from the authorization response.
// And we'll want to validate the new JWT in ValidateTokenResponse.
JwtSecurityToken tokenEndpointJwt;
var tokenEndpointUser = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out tokenEndpointJwt);
var tokenEndpointUser = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out var tokenEndpointJwt);

// Avoid reading & deleting the nonce cookie, running the event, etc, if it was already done as part of the authorization response validation.
if (user == null)
Expand Down Expand Up @@ -722,8 +716,25 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
return authenticationFailedContext.Result;
}

return HandleRequestResult.Fail(exception);
return HandleRequestResult.Fail(exception, properties);
}
}

private AuthenticationProperties ReadPropertiesAndClearState(OpenIdConnectMessage message)
{
AuthenticationProperties properties = null;
if (!string.IsNullOrEmpty(message.State))
{
properties = Options.StateDataFormat.Unprotect(message.State);

if (properties != null)
{
// If properties can be decoded from state, clear the message state.
properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out var userstate);
message.State = userstate;
}
}
return properties;
}

private void PopulateSessionProperties(OpenIdConnectMessage message, AuthenticationProperties properties)
Expand Down Expand Up @@ -830,7 +841,7 @@ protected virtual async Task<HandleRequestResult> GetUserInformationAsync(
}
else
{
return HandleRequestResult.Fail("Unknown response type: " + contentType.MediaType);
return HandleRequestResult.Fail("Unknown response type: " + contentType.MediaType, properties);
}

var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(principal, properties, message, user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public TwitterHandler(IOptionsMonitor<TwitterOptions> options, ILoggerFactory lo

protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync()
{
AuthenticationProperties properties = null;
var query = Request.Query;
var protectedRequestToken = Request.Cookies[Options.StateCookie.Name];

Expand All @@ -57,25 +56,25 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
return HandleRequestResult.Fail("Invalid state cookie.");
}

properties = requestToken.Properties;
var properties = requestToken.Properties;

// REVIEW: see which of these are really errors

var returnedToken = query["oauth_token"];
if (StringValues.IsNullOrEmpty(returnedToken))
{
return HandleRequestResult.Fail("Missing oauth_token");
return HandleRequestResult.Fail("Missing oauth_token", properties);
}

if (!string.Equals(returnedToken, requestToken.Token, StringComparison.Ordinal))
{
return HandleRequestResult.Fail("Unmatched token");
return HandleRequestResult.Fail("Unmatched token", properties);
}

var oauthVerifier = query["oauth_verifier"];
if (StringValues.IsNullOrEmpty(oauthVerifier))
{
return HandleRequestResult.Fail("Missing or blank oauth_verifier");
return HandleRequestResult.Fail("Missing or blank oauth_verifier", properties);
}

var cookieOptions = Options.StateCookie.Build(Context, Clock.UtcNow);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,10 @@ public RemoteFailureContext(
/// User friendly error message for the error.
/// </summary>
public Exception Failure { get; set; }

/// <summary>
/// Additional state values for the authentication session.
/// </summary>
public AuthenticationProperties Properties { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,31 @@ public class HandleRequestResult : AuthenticateResult
/// <summary>
/// Indicates that there was a failure during authentication.
/// </summary>
/// <param name="failureMessage">The failure message.</param>
/// <param name="failure">The failure exception.</param>
/// <param name="properties">Additional state values for the authentication session.</param>
/// <returns>The result.</returns>
public static new HandleRequestResult Fail(string failureMessage)
public static new HandleRequestResult Fail(Exception failure, AuthenticationProperties properties)
{
return new HandleRequestResult() { Failure = new Exception(failureMessage) };
return new HandleRequestResult() { Failure = failure, Properties = properties };
}

/// <summary>
/// Indicates that there was a failure during authentication.
/// </summary>
/// <param name="failureMessage">The failure message.</param>
/// <returns>The result.</returns>
public static new HandleRequestResult Fail(string failureMessage)
=> Fail(new Exception(failureMessage));

/// <summary>
/// Indicates that there was a failure during authentication.
/// </summary>
/// <param name="failureMessage">The failure message.</param>
/// <param name="properties">Additional state values for the authentication session.</param>
/// <returns>The result.</returns>
public static new HandleRequestResult Fail(string failureMessage, AuthenticationProperties properties)
=> Fail(new Exception(failureMessage), properties);

/// <summary>
/// Discontinue all processing for this request and return to the client.
/// The caller is responsible for generating the full response.
Expand Down
Loading