-
Notifications
You must be signed in to change notification settings - Fork 593
Revisit OAuthAuthenticationHandler and add a new SaveTokensAsClaims option #257
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Security.Claims; | ||
|
@@ -110,6 +111,7 @@ dnx . web | |
options.Caption = "MicrosoftAccount - Requires project changes"; | ||
options.ClientId = "00000000480FF62E"; | ||
options.ClientSecret = "bLw2JIvf8Y1TaToipPEqxTVlOeJwCUsr"; | ||
options.Scope.Add("wl.emails"); | ||
}); | ||
|
||
// https://github.com/settings/applications/ | ||
|
@@ -131,48 +133,53 @@ dnx . web | |
options.TokenEndpoint = "https://github.com/login/oauth/access_token"; | ||
options.UserInformationEndpoint = "https://api.github.com/user"; | ||
options.ClaimsIssuer = "OAuth2-Github"; | ||
options.SaveTokensAsClaims = false; | ||
// Retrieving user information is unique to each provider. | ||
options.Notifications = new OAuthAuthenticationNotifications() | ||
options.Notifications = new OAuthAuthenticationNotifications | ||
{ | ||
OnGetUserInformationAsync = async (context) => | ||
OnAuthenticated = async notification => | ||
{ | ||
// Get the GitHub user | ||
var userRequest = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint); | ||
userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken); | ||
userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); | ||
var userResponse = await context.Backchannel.SendAsync(userRequest, context.HttpContext.RequestAborted); | ||
userResponse.EnsureSuccessStatusCode(); | ||
var text = await userResponse.Content.ReadAsStringAsync(); | ||
var user = JObject.Parse(text); | ||
|
||
var identity = new ClaimsIdentity( | ||
context.Options.AuthenticationScheme, | ||
ClaimsIdentity.DefaultNameClaimType, | ||
ClaimsIdentity.DefaultRoleClaimType); | ||
|
||
JToken value; | ||
var id = user.TryGetValue("id", out value) ? value.ToString() : null; | ||
if (!string.IsNullOrEmpty(id)) | ||
var request = new HttpRequestMessage(HttpMethod.Get, notification.Options.UserInformationEndpoint); | ||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", notification.AccessToken); | ||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); | ||
|
||
var response = await notification.Backchannel.SendAsync(request, notification.HttpContext.RequestAborted); | ||
response.EnsureSuccessStatusCode(); | ||
|
||
var user = JObject.Parse(await response.Content.ReadAsStringAsync()); | ||
|
||
var identifier = user.Value<string>("id"); | ||
if (!string.IsNullOrEmpty(identifier)) | ||
{ | ||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id, ClaimValueTypes.String, context.Options.ClaimsIssuer)); | ||
notification.Identity.AddClaim(new Claim( | ||
ClaimTypes.NameIdentifier, identifier, | ||
ClaimValueTypes.String, notification.Options.ClaimsIssuer)); | ||
} | ||
var userName = user.TryGetValue("login", out value) ? value.ToString() : null; | ||
|
||
var userName = user.Value<string>("login"); | ||
if (!string.IsNullOrEmpty(userName)) | ||
{ | ||
identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, userName, ClaimValueTypes.String, context.Options.ClaimsIssuer)); | ||
notification.Identity.AddClaim(new Claim( | ||
ClaimsIdentity.DefaultNameClaimType, userName, | ||
ClaimValueTypes.String, notification.Options.ClaimsIssuer)); | ||
} | ||
var name = user.TryGetValue("name", out value) ? value.ToString() : null; | ||
|
||
var name = user.Value<string>("name"); | ||
if (!string.IsNullOrEmpty(name)) | ||
{ | ||
identity.AddClaim(new Claim("urn:github:name", name, ClaimValueTypes.String, context.Options.ClaimsIssuer)); | ||
notification.Identity.AddClaim(new Claim( | ||
"urn:github:name", name, | ||
ClaimValueTypes.String, notification.Options.ClaimsIssuer)); | ||
} | ||
var link = user.TryGetValue("url", out value) ? value.ToString() : null; | ||
|
||
var link = user.Value<string>("url"); | ||
if (!string.IsNullOrEmpty(link)) | ||
{ | ||
identity.AddClaim(new Claim("urn:github:url", link, ClaimValueTypes.String, context.Options.ClaimsIssuer)); | ||
notification.Identity.AddClaim(new Claim( | ||
"urn:github:url", link, | ||
ClaimValueTypes.String, notification.Options.ClaimsIssuer)); | ||
} | ||
|
||
context.Principal = new ClaimsPrincipal(identity); | ||
}, | ||
}; | ||
}); | ||
|
@@ -207,8 +214,8 @@ dnx . web | |
{ | ||
signoutApp.Run(async context => | ||
{ | ||
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); | ||
context.Response.ContentType = "text/html"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @HaoK now that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah well, as you can tell, the samples code for security is basically a little test app (that I never ever run clearly) I mostly rely on MVC/Identity/MusicStore for functional verification, but I should use this as a canary as well (until we get real functional tests...in the repo sigh) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I started porting For instance, returning a simple There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of fragile, I prefer the term powerful and immediate :) But yeah the behavior is very different, I don't think we entirely know all the ramifications yet (i.e. map path issue) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would argue calling these methods which immediately do something is easier to grok, than the old behavior of stuff happens at some undetermined future time (whenever the response is sent). Perhaps you are just used to the old behavior, and the new will grow on you. I didn't play with the old behavior much (so I'm missing any fond memories of it) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not saying that the new behavior is necessarily bad, it's just surprising and... obviously different 😄 What I'm saying is that we can't ship these new methods without changing the way we use them: altering the headers after invoking For instance, this snippet will throw an exception: await Context.Authentication.SignInAsync(OpenIdConnectDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
return new HttpStatusCodeResult(200); ... while this one, that uses await Context.Authentication.SignInAsync(OpenIdConnectDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
return new EmptyResult(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep totally agree, this is something that definitely needs to go on the Announcements as a heads up/breaking change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved |
||
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); | ||
await context.Response.WriteAsync("<html><body>"); | ||
await context.Response.WriteAsync("You have been logged out. Goodbye " + context.User.Identity.Name + "<br>"); | ||
await context.Response.WriteAsync("<a href=\"/\">Home</a>"); | ||
|
@@ -219,7 +226,7 @@ dnx . web | |
// Deny anonymous request beyond this point. | ||
app.Use(async (context, next) => | ||
{ | ||
if (string.IsNullOrEmpty(context.User.Identity.Name)) | ||
if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) | ||
{ | ||
// The cookie middleware will intercept this 401 and redirect to /login | ||
await context.Authentication.ChallengeAsync(); | ||
|
@@ -233,7 +240,7 @@ dnx . web | |
{ | ||
context.Response.ContentType = "text/html"; | ||
await context.Response.WriteAsync("<html><body>"); | ||
await context.Response.WriteAsync("Hello " + context.User.Identity.Name + "<br>"); | ||
await context.Response.WriteAsync("Hello " + (context.User.Identity.Name ?? "anonymous") + "<br>"); | ||
foreach (var claim in context.User.Claims) | ||
{ | ||
await context.Response.WriteAsync(claim.Type + ": " + claim.Value + "<br>"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using Microsoft.Framework.Internal; | ||
using Newtonsoft.Json.Linq; | ||
|
||
namespace Microsoft.AspNet.Authentication.Facebook | ||
{ | ||
/// <summary> | ||
/// Contains static methods that allow to extract user's information from a <see cref="JObject"/> | ||
/// instance retrieved from Facebook after a successful authentication process. | ||
/// </summary> | ||
public static class FacebookAuthenticationHelper | ||
{ | ||
/// <summary> | ||
/// Gets the Facebook user ID. | ||
/// </summary> | ||
public static string GetId([NotNull] JObject user) => user.Value<string>("id"); | ||
|
||
/// <summary> | ||
/// Gets the user's name. | ||
/// </summary> | ||
public static string GetName([NotNull] JObject user) => user.Value<string>("name"); | ||
|
||
/// <summary> | ||
/// Gets the user's link. | ||
/// </summary> | ||
public static string GetLink([NotNull] JObject user) => user.Value<string>("link"); | ||
|
||
/// <summary> | ||
/// Gets the Facebook username. | ||
/// </summary> | ||
public static string GetUserName([NotNull] JObject user) => user.Value<string>("username"); | ||
|
||
/// <summary> | ||
/// Gets the Facebook email. | ||
/// </summary> | ||
public static string GetEmail([NotNull] JObject user) => user.Value<string>("email"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely not something I wanted to change, but it sounds like @HaoK's recent changes broke something here:
It seems that the
redirect_uri
is now computed using thePathBase
of the handler/middleware that triggers theChallenge
call, which of course, produces an incorrectredirect_uri
(http://localhost:54540/login/signin-google
vshttp://localhost:54540/signin-google
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, the middleware may need to snapshot the base address when the request first arrives.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the correct url that is registered for these apps? I'm seeing errors saying: "http://localhost:54540/login/signin-google-token" did not match a registered redirect uri
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nm, I should scroll up :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to port this fix manually to a standalone so social samples actually work again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks to your fix, I can now revert this change 😄