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

Commit 144ee21

Browse files
committed
#1188 Add AuthenticationProperties to HandleRequestResult and RemoteFailureContext
1 parent 5abcfe7 commit 144ee21

File tree

11 files changed

+344
-176
lines changed

11 files changed

+344
-176
lines changed

samples/SocialSample/Startup.cs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public void ConfigureServices(IServiceCollection services)
6767
o.Fields.Add("name");
6868
o.Fields.Add("email");
6969
o.SaveTokens = true;
70+
o.Events = new OAuthEvents()
71+
{
72+
OnRemoteFailure = HandleOnRemoteFailure
73+
};
7074
})
7175
// You must first create an app with Google and add its ID and Secret to your user-secrets.
7276
// https://console.developers.google.com/project
@@ -81,6 +85,10 @@ public void ConfigureServices(IServiceCollection services)
8185
o.Scope.Add("profile");
8286
o.Scope.Add("email");
8387
o.SaveTokens = true;
88+
o.Events = new OAuthEvents()
89+
{
90+
OnRemoteFailure = HandleOnRemoteFailure
91+
};
8492
})
8593
// You must first create an app with Google and add its ID and Secret to your user-secrets.
8694
// https://console.developers.google.com/project
@@ -93,12 +101,7 @@ public void ConfigureServices(IServiceCollection services)
93101
o.SaveTokens = true;
94102
o.Events = new OAuthEvents()
95103
{
96-
OnRemoteFailure = ctx =>
97-
{
98-
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
99-
ctx.HandleResponse();
100-
return Task.FromResult(0);
101-
}
104+
OnRemoteFailure = HandleOnRemoteFailure
102105
};
103106
o.ClaimActions.MapJsonSubKey("urn:google:image", "image", "url");
104107
o.ClaimActions.Remove(ClaimTypes.GivenName);
@@ -116,12 +119,7 @@ public void ConfigureServices(IServiceCollection services)
116119
o.ClaimActions.MapJsonKey("urn:twitter:profilepicture", "profile_image_url", ClaimTypes.Uri);
117120
o.Events = new TwitterEvents()
118121
{
119-
OnRemoteFailure = ctx =>
120-
{
121-
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
122-
ctx.HandleResponse();
123-
return Task.FromResult(0);
124-
}
122+
OnRemoteFailure = HandleOnRemoteFailure
125123
};
126124
})
127125
/* Azure AD app model v2 has restrictions that prevent the use of plain HTTP for redirect URLs.
@@ -139,6 +137,10 @@ public void ConfigureServices(IServiceCollection services)
139137
o.TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint;
140138
o.Scope.Add("https://graph.microsoft.com/user.read");
141139
o.SaveTokens = true;
140+
o.Events = new OAuthEvents()
141+
{
142+
OnRemoteFailure = HandleOnRemoteFailure
143+
};
142144
})
143145
// You must first create an app with Microsoft Account and add its ID and Secret to your user-secrets.
144146
// https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-app-registration/
@@ -148,6 +150,10 @@ public void ConfigureServices(IServiceCollection services)
148150
o.ClientSecret = Configuration["microsoftaccount:clientsecret"];
149151
o.SaveTokens = true;
150152
o.Scope.Add("offline_access");
153+
o.Events = new OAuthEvents()
154+
{
155+
OnRemoteFailure = HandleOnRemoteFailure
156+
};
151157
})
152158
// You must first create an app with GitHub and add its ID and Secret to your user-secrets.
153159
// https://github.com/settings/applications/
@@ -159,6 +165,10 @@ public void ConfigureServices(IServiceCollection services)
159165
o.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
160166
o.TokenEndpoint = "https://github.com/login/oauth/access_token";
161167
o.SaveTokens = true;
168+
o.Events = new OAuthEvents()
169+
{
170+
OnRemoteFailure = HandleOnRemoteFailure
171+
};
162172
})
163173
// You must first create an app with GitHub and add its ID and Secret to your user-secrets.
164174
// https://github.com/settings/applications/
@@ -180,6 +190,7 @@ public void ConfigureServices(IServiceCollection services)
180190
o.ClaimActions.MapJsonKey("urn:github:url", "url");
181191
o.Events = new OAuthEvents
182192
{
193+
OnRemoteFailure = HandleOnRemoteFailure,
183194
OnCreatingTicket = async context =>
184195
{
185196
// Get the GitHub user
@@ -198,6 +209,30 @@ public void ConfigureServices(IServiceCollection services)
198209
});
199210
}
200211

212+
private async Task HandleOnRemoteFailure(RemoteFailureContext context)
213+
{
214+
context.Response.StatusCode = 500;
215+
context.Response.ContentType = "text/html";
216+
await context.Response.WriteAsync("<html><body>");
217+
await context.Response.WriteAsync("A remote failure has occurred: " + UrlEncoder.Default.Encode(context.Failure.Message) + "<br>");
218+
219+
if (context.Properties != null)
220+
{
221+
await context.Response.WriteAsync("Properties:<br>");
222+
foreach (var pair in context.Properties.Items)
223+
{
224+
await context.Response.WriteAsync($"-{ UrlEncoder.Default.Encode(pair.Key)}={ UrlEncoder.Default.Encode(pair.Value)}<br>");
225+
}
226+
}
227+
228+
await context.Response.WriteAsync("<a href=\"/\">Home</a>");
229+
await context.Response.WriteAsync("</body></html>");
230+
231+
// context.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(context.Failure.Message));
232+
233+
context.HandleResponse();
234+
}
235+
201236
public void Configure(IApplicationBuilder app)
202237
{
203238
app.UseDeveloperExceptionPage();

src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,22 @@ public OAuthHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, Ur
4444

4545
protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync()
4646
{
47-
AuthenticationProperties properties = null;
4847
var query = Request.Query;
4948

49+
var state = query["state"];
50+
var properties = Options.StateDataFormat.Unprotect(state);
51+
52+
if (properties == null)
53+
{
54+
return HandleRequestResult.Fail("The oauth state was missing or invalid.");
55+
}
56+
57+
// OAuth2 10.12 CSRF
58+
if (!ValidateCorrelationId(properties))
59+
{
60+
return HandleRequestResult.Fail("Correlation failed.", properties);
61+
}
62+
5063
var error = query["error"];
5164
if (!StringValues.IsNullOrEmpty(error))
5265
{
@@ -63,39 +76,26 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
6376
failureMessage.Append(";Uri=").Append(errorUri);
6477
}
6578

66-
return HandleRequestResult.Fail(failureMessage.ToString());
79+
return HandleRequestResult.Fail(failureMessage.ToString(), properties);
6780
}
6881

6982
var code = query["code"];
70-
var state = query["state"];
71-
72-
properties = Options.StateDataFormat.Unprotect(state);
73-
if (properties == null)
74-
{
75-
return HandleRequestResult.Fail("The oauth state was missing or invalid.");
76-
}
77-
78-
// OAuth2 10.12 CSRF
79-
if (!ValidateCorrelationId(properties))
80-
{
81-
return HandleRequestResult.Fail("Correlation failed.");
82-
}
8383

8484
if (StringValues.IsNullOrEmpty(code))
8585
{
86-
return HandleRequestResult.Fail("Code was not found.");
86+
return HandleRequestResult.Fail("Code was not found.", properties);
8787
}
8888

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

9191
if (tokens.Error != null)
9292
{
93-
return HandleRequestResult.Fail(tokens.Error);
93+
return HandleRequestResult.Fail(tokens.Error, properties);
9494
}
9595

9696
if (string.IsNullOrEmpty(tokens.AccessToken))
9797
{
98-
return HandleRequestResult.Fail("Failed to retrieve access token.");
98+
return HandleRequestResult.Fail("Failed to retrieve access token.", properties);
9999
}
100100

101101
var identity = new ClaimsIdentity(ClaimsIssuer);
@@ -141,7 +141,7 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
141141
}
142142
else
143143
{
144-
return HandleRequestResult.Fail("Failed to retrieve user information from remote server.");
144+
return HandleRequestResult.Fail("Failed to retrieve user information from remote server.", properties);
145145
}
146146
}
147147

src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -491,13 +491,10 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
491491
return HandleRequestResult.Fail("No message.");
492492
}
493493

494+
AuthenticationProperties properties = null;
494495
try
495496
{
496-
AuthenticationProperties properties = null;
497-
if (!string.IsNullOrEmpty(authorizationResponse.State))
498-
{
499-
properties = Options.StateDataFormat.Unprotect(authorizationResponse.State);
500-
}
497+
properties = ReadPropertiesAndClearState(authorizationResponse);
501498

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

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

528524
if (properties == null)
@@ -533,21 +529,20 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
533529
// Not for us?
534530
return HandleRequestResult.SkipHandler();
535531
}
532+
533+
// if state exists and we failed to 'unprotect' this is not a message we should process.
536534
return HandleRequestResult.Fail(Resources.MessageStateIsInvalid);
537535
}
538536

539-
properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out string userstate);
540-
authorizationResponse.State = userstate;
541-
542537
if (!ValidateCorrelationId(properties))
543538
{
544-
return HandleRequestResult.Fail("Correlation failed.");
539+
return HandleRequestResult.Fail("Correlation failed.", properties);
545540
}
546541

547542
// if any of the error fields are set, throw error null
548543
if (!string.IsNullOrEmpty(authorizationResponse.Error))
549544
{
550-
return HandleRequestResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null));
545+
return HandleRequestResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null), properties);
551546
}
552547

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

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

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

725-
return HandleRequestResult.Fail(exception);
719+
return HandleRequestResult.Fail(exception, properties);
720+
}
721+
}
722+
723+
private AuthenticationProperties ReadPropertiesAndClearState(OpenIdConnectMessage message)
724+
{
725+
AuthenticationProperties properties = null;
726+
if (!string.IsNullOrEmpty(message.State))
727+
{
728+
properties = Options.StateDataFormat.Unprotect(message.State);
729+
730+
if (properties != null)
731+
{
732+
// If properties can be decoded from state, clear the message state.
733+
properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out var userstate);
734+
message.State = userstate;
735+
}
726736
}
737+
return properties;
727738
}
728739

729740
private void PopulateSessionProperties(OpenIdConnectMessage message, AuthenticationProperties properties)
@@ -830,7 +841,7 @@ protected virtual async Task<HandleRequestResult> GetUserInformationAsync(
830841
}
831842
else
832843
{
833-
return HandleRequestResult.Fail("Unknown response type: " + contentType.MediaType);
844+
return HandleRequestResult.Fail("Unknown response type: " + contentType.MediaType, properties);
834845
}
835846

836847
var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(principal, properties, message, user);

src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public TwitterHandler(IOptionsMonitor<TwitterOptions> options, ILoggerFactory lo
4646

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

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

60-
properties = requestToken.Properties;
59+
var properties = requestToken.Properties;
6160

6261
// REVIEW: see which of these are really errors
6362

6463
var returnedToken = query["oauth_token"];
6564
if (StringValues.IsNullOrEmpty(returnedToken))
6665
{
67-
return HandleRequestResult.Fail("Missing oauth_token");
66+
return HandleRequestResult.Fail("Missing oauth_token", properties);
6867
}
6968

7069
if (!string.Equals(returnedToken, requestToken.Token, StringComparison.Ordinal))
7170
{
72-
return HandleRequestResult.Fail("Unmatched token");
71+
return HandleRequestResult.Fail("Unmatched token", properties);
7372
}
7473

7574
var oauthVerifier = query["oauth_verifier"];
7675
if (StringValues.IsNullOrEmpty(oauthVerifier))
7776
{
78-
return HandleRequestResult.Fail("Missing or blank oauth_verifier");
77+
return HandleRequestResult.Fail("Missing or blank oauth_verifier", properties);
7978
}
8079

8180
var cookieOptions = Options.StateCookie.Build(Context, Clock.UtcNow);

src/Microsoft.AspNetCore.Authentication/Events/RemoteFailureContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,10 @@ public RemoteFailureContext(
2525
/// User friendly error message for the error.
2626
/// </summary>
2727
public Exception Failure { get; set; }
28+
29+
/// <summary>
30+
/// Additional state values for the authentication session.
31+
/// </summary>
32+
public AuthenticationProperties Properties { get; set; }
2833
}
2934
}

src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationResult.cs renamed to src/Microsoft.AspNetCore.Authentication/HandleRequestResult.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,31 @@ public class HandleRequestResult : AuthenticateResult
4949
/// <summary>
5050
/// Indicates that there was a failure during authentication.
5151
/// </summary>
52-
/// <param name="failureMessage">The failure message.</param>
52+
/// <param name="failure">The failure exception.</param>
53+
/// <param name="properties">Additional state values for the authentication session.</param>
5354
/// <returns>The result.</returns>
54-
public static new HandleRequestResult Fail(string failureMessage)
55+
public static new HandleRequestResult Fail(Exception failure, AuthenticationProperties properties)
5556
{
56-
return new HandleRequestResult() { Failure = new Exception(failureMessage) };
57+
return new HandleRequestResult() { Failure = failure, Properties = properties };
5758
}
5859

60+
/// <summary>
61+
/// Indicates that there was a failure during authentication.
62+
/// </summary>
63+
/// <param name="failureMessage">The failure message.</param>
64+
/// <returns>The result.</returns>
65+
public static new HandleRequestResult Fail(string failureMessage)
66+
=> Fail(new Exception(failureMessage));
67+
68+
/// <summary>
69+
/// Indicates that there was a failure during authentication.
70+
/// </summary>
71+
/// <param name="failureMessage">The failure message.</param>
72+
/// <param name="properties">Additional state values for the authentication session.</param>
73+
/// <returns>The result.</returns>
74+
public static new HandleRequestResult Fail(string failureMessage, AuthenticationProperties properties)
75+
=> Fail(new Exception(failureMessage), properties);
76+
5977
/// <summary>
6078
/// Discontinue all processing for this request and return to the client.
6179
/// The caller is responsible for generating the full response.

0 commit comments

Comments
 (0)