Update MicrosoftAccount to use converged auth

-Updated MicrosoftAccountDefaults to use app model v2 & graph API
-Updated MicrosoftAccountHelper to extract user info from the user object
returned by the graph API
-Updated MicrosoftAccountMiddlewareTests accordingly
-Added the app model v2 client credentials to SocialSample/config.json
-Configured SocialSample to use SSL

Startup.cs
- Sorted namespaces
- Dropped openid scope from Microsoft-AccessToken
project.json
- Sorted dependencies
MicrosoftAccountHelper.cs
- Removed name claim transofmation
- renamed GetName, GetFirstName & GetLastName
This commit is contained in:
Osman M Elsayed 2016-01-30 02:38:55 +03:00 committed by Chris R
parent 909c342711
commit 8645ca0dc5
10 changed files with 114 additions and 71 deletions

View File

@ -4,13 +4,14 @@
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54540",
"sslPort": 0
"sslPort": 44318
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "https://localhost:44318/",
"environmentVariables": {
"ASPNET_ENV": "Development"
}
@ -18,10 +19,9 @@
"web": {
"commandName": "web",
"launchBrowser": true,
"launchUrl": "http://localhost:54540/",
"launchUrl": "https://localhost:54541/",
"environmentVariables": {
"Hosting:Environment": "Development",
"ASPNET_server.urls": "http://localhost:54540/"
"Hosting:Environment": "Development"
}
}
}

View File

@ -1,8 +1,11 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
@ -15,12 +18,15 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Server.Kestrel.Filter;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace CookieSample
namespace SocialSample
{
/* Note all servers must use the same address and port because these are pre-registered with the various providers. */
public class Startup
@ -45,6 +51,10 @@ namespace CookieSample
{
loggerfactory.AddConsole(LogLevel.Information);
//Configure SSL
var serverCertificate = LoadCertificate();
app.UseKestrelHttps(serverCertificate);
// Simple error page to avoid a repo dependency.
app.Use(async (context, next) =>
{
@ -63,6 +73,12 @@ namespace CookieSample
}
});
// Forward the scheme from IISPlatformHandler
app.UseForwardedHeaders(new ForwardedHeadersOptions()
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto,
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
@ -105,7 +121,6 @@ namespace CookieSample
Events = new OAuthEvents()
{
OnRemoteFailure = ctx =>
{
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
ctx.HandleResponse();
@ -132,45 +147,34 @@ namespace CookieSample
}
});
// You must first create an app with live.com and add it's ID and Secret to your config.json or user-secrets.
/* https://account.live.com/developers/applications
The MicrosoftAccount service has restrictions that prevent the use of http://localhost:54540/ for test applications.
As such, here is how to change this sample to uses http://mssecsample.localhost.this:54540/ instead.
Edit the hosting.json file and add "server.urls": "http://mssecsample.localhost.this:54540/".
From an admin command console first enter:
notepad C:\Windows\System32\drivers\etc\hosts
and add this to the file, save, and exit (and reboot?):
127.0.0.1 MsSecSample.localhost.this
[WebListener] Then you can choose to run the app as admin (see below) or add the following ACL as admin:
netsh http add urlacl url=http://mssecsample.localhost.this:54540/ user=[domain\user]
The sample app can then be run via:
dnx web
/* Azure AD app model v2 has restrictions that prevent the use of plain HTTP for redirect URLs.
Therefore, to authenticate through microsoft accounts, tryout the sample using the following URL:
https://localhost:54541/
*/
//app.UseOAuthAuthentication(new OAuthOptions
//{
// AuthenticationScheme = "Microsoft-AccessToken",
// DisplayName = "MicrosoftAccount-AccessToken - Requires project changes",
// ClientId = Configuration["msa:clientid"],
// ClientSecret = Configuration["msa:clientsecret"],
// CallbackPath = new PathString("/signin-microsoft-token"),
// AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
// TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
// Scope = { "wl.basic" },
// SaveTokens = true
//});
// See config.json
// https://apps.dev.microsoft.com/
app.UseOAuthAuthentication(new OAuthOptions
{
AuthenticationScheme = "Microsoft-AccessToken",
DisplayName = "MicrosoftAccount-AccessToken",
ClientId = Configuration["msa:clientid"],
ClientSecret = Configuration["msa:clientsecret"],
CallbackPath = new PathString("/signin-microsoft-token"),
AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
Scope = { "https://graph.microsoft.com/user.read" },
SaveTokens = true
});
////// You must first create an app with live.com and add it's ID and Secret to your config.json or user-secrets.
//app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions
//{
// DisplayName = "MicrosoftAccount - Requires project changes",
// ClientId = Configuration["msa:clientid"],
// ClientSecret = Configuration["msa:clientsecret"],
// Scope = { "wl.emails" }
//});
// See config.json
// https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-app-registration/
app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions
{
DisplayName = "MicrosoftAccount",
ClientId = Configuration["msa:clientid"],
ClientSecret = Configuration["msa:clientsecret"],
SaveTokens = true
});
// See config.json
// https://github.com/settings/applications/
@ -346,5 +350,23 @@ namespace CookieSample
host.Run();
}
private X509Certificate2 LoadCertificate()
{
var socialSampleAssembly = GetType().GetTypeInfo().Assembly;
var embeddedFileProvider = new EmbeddedFileProvider(socialSampleAssembly, "SocialSample");
var certificateFileInfo = embeddedFileProvider.GetFileInfo("compiler/resources/cert.pfx");
using (var certificateStream = certificateFileInfo.CreateReadStream())
{
byte[] certificatePayload;
using (var memoryStream = new MemoryStream())
{
certificateStream.CopyTo(memoryStream);
certificatePayload = memoryStream.ToArray();
}
return new X509Certificate2(certificatePayload, "testPassword");
}
}
}
}

Binary file not shown.

View File

@ -6,5 +6,7 @@
"github:clientid": "49e302895d8b09ea5656",
"github:clientsecret": "98f1bf028608901e9df91d64ee61536fe562064b",
"github-token:clientid": "8c0c5a572abe8fe89588",
"github-token:clientsecret": "e1d95eaf03461d27acd6f49d4fc7bf19d6ac8cda"
"github-token:clientsecret": "e1d95eaf03461d27acd6f49d4fc7bf19d6ac8cda",
"msa:clientid": "e2105565-1f56-434a-ae61-9849ebaf606c",
"msa:clientsecret": "pjqtt3RXrFwcfSJyQ0BeUez"
}

View File

@ -6,9 +6,12 @@
"Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.0.0-*",
"Microsoft.AspNetCore.Authentication.Twitter": "1.0.0-*",
"Microsoft.AspNetCore.DataProtection": "1.0.0-*",
"Microsoft.AspNetCore.HttpOverrides": "1.0.0-*",
"Microsoft.AspNetCore.IISPlatformHandler": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel.Https": "1.0.0-*",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0-*",
"Microsoft.Extensions.FileProviders.Embedded": "1.0.0-*",
"Microsoft.Extensions.Logging.Console": "1.0.0-*",
"Microsoft.NETCore.Platforms": "1.0.1-*"
},
@ -26,5 +29,9 @@
]
}
},
"userSecretsId": "aspnet5-SocialSample-20151210111056"
}
"userSecretsId": "aspnet5-SocialSample-20151210111056",
"content": [
"config.json",
"project.json"
]
}

View File

@ -7,10 +7,10 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
{
public const string AuthenticationScheme = "Microsoft";
public static readonly string AuthorizationEndpoint = "https://login.live.com/oauth20_authorize.srf";
public static readonly string AuthorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
public static readonly string TokenEndpoint = "https://login.live.com/oauth20_token.srf";
public static readonly string TokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
public static readonly string UserInformationEndpoint = "https://apis.live.net/v5.0/me";
public static readonly string UserInformationEndpoint = "https://graph.microsoft.com/v1.0/me";
}
}

View File

@ -38,13 +38,27 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
identity.AddClaim(new Claim("urn:microsoftaccount:id", identifier, ClaimValueTypes.String, Options.ClaimsIssuer));
}
var name = MicrosoftAccountHelper.GetName(payload);
var name = MicrosoftAccountHelper.GetDisplayName(payload);
if (!string.IsNullOrEmpty(name))
{
identity.AddClaim(new Claim(ClaimTypes.Name, name, ClaimValueTypes.String, Options.ClaimsIssuer));
identity.AddClaim(new Claim("urn:microsoftaccount:name", name, ClaimValueTypes.String, Options.ClaimsIssuer));
}
var givenName = MicrosoftAccountHelper.GetGivenName(payload);
if (!string.IsNullOrEmpty(givenName))
{
identity.AddClaim(new Claim(ClaimTypes.GivenName, givenName, ClaimValueTypes.String, Options.ClaimsIssuer));
identity.AddClaim(new Claim("urn:microsoftaccount:givenname", givenName, ClaimValueTypes.String, Options.ClaimsIssuer));
}
var surname = MicrosoftAccountHelper.GetSurname(payload);
if (!string.IsNullOrEmpty(surname))
{
identity.AddClaim(new Claim(ClaimTypes.Surname, surname, ClaimValueTypes.String, Options.ClaimsIssuer));
identity.AddClaim(new Claim("urn:microsoftaccount:surname", surname, ClaimValueTypes.String, Options.ClaimsIssuer));
}
var email = MicrosoftAccountHelper.GetEmail(payload);
if (!string.IsNullOrEmpty(email))
{

View File

@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
{
/// <summary>
/// Contains static methods that allow to extract user's information from a <see cref="JObject"/>
/// instance retrieved from Google after a successful authentication process.
/// instance retrieved from Microsoft after a successful authentication process.
/// http://graph.microsoft.io/en-us/docs/api-reference/v1.0/resources/user
/// </summary>
public static class MicrosoftAccountHelper
{
@ -28,40 +29,40 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
/// <summary>
/// Gets the user's name.
/// </summary>
public static string GetName(JObject user)
public static string GetDisplayName(JObject user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return user.Value<string>("name");
return user.Value<string>("displayName");
}
/// <summary>
/// Gets the user's first name.
/// Gets the user's given name.
/// </summary>
public static string GetFirstName(JObject user)
public static string GetGivenName(JObject user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return user.Value<string>("first_name");
return user.Value<string>("givenName");
}
/// <summary>
/// Gets the user's last name.
/// Gets the user's surname.
/// </summary>
public static string GetLastName(JObject user)
public static string GetSurname(JObject user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return user.Value<string>("last_name");
return user.Value<string>("surname");
}
/// <summary>
@ -74,7 +75,7 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
throw new ArgumentNullException(nameof(user));
}
return user.Value<JObject>("emails")?.Value<string>("preferred");
return user.Value<string>("mail") ?? user.Value<string>("userPrincipalName");
}
}
}

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Builder
AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint;
TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint;
UserInformationEndpoint = MicrosoftAccountDefaults.UserInformationEndpoint;
Scope.Add("wl.basic");
Scope.Add("https://graph.microsoft.com/user.read");
}
}
}

View File

@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
var transaction = await server.SendAsync("http://example.com/challenge");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
var location = transaction.Response.Headers.Location.AbsoluteUri;
Assert.Contains("https://login.live.com/oauth20_authorize.srf", location);
Assert.Contains("https://login.microsoftonline.com/common/oauth2/v2.0/authorize", location);
Assert.Contains("response_type=code", location);
Assert.Contains("client_id=", location);
Assert.Contains("redirect_uri=", location);
@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
{
Sender = req =>
{
if (req.RequestUri.AbsoluteUri == "https://login.live.com/oauth20_token.srf")
if (req.RequestUri.AbsoluteUri == "https://login.microsoftonline.com/common/oauth2/v2.0/token")
{
return ReturnJsonResponse(new
{
@ -123,18 +123,15 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
refresh_token = "Test Refresh Token"
});
}
else if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == "https://apis.live.net/v5.0/me")
else if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == "https://graph.microsoft.com/v1.0/me")
{
return ReturnJsonResponse(new
{
id = "Test User ID",
name = "Test Name",
first_name = "Test Given Name",
last_name = "Test Family Name",
emails = new
{
preferred = "Test email"
}
displayName = "Test Name",
givenName = "Test Given Name",
surname = "Test Family Name",
mail = "Test email"
});
}