From 8645ca0dc505d559cf4ef7649e6c548fc0db06b4 Mon Sep 17 00:00:00 2001 From: Osman M Elsayed Date: Sat, 30 Jan 2016 02:38:55 +0300 Subject: [PATCH] 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 --- .../Properties/launchSettings.json | 8 +- samples/SocialSample/Startup.cs | 100 +++++++++++------- .../SocialSample/compiler/resources/cert.pfx | Bin 0 -> 2483 bytes samples/SocialSample/config.json | 4 +- samples/SocialSample/project.json | 11 +- .../MicrosoftAccountDefaults.cs | 6 +- .../MicrosoftAccountHandler.cs | 16 ++- .../MicrosoftAccountHelper.cs | 21 ++-- .../MicrosoftAccountOptions.cs | 2 +- .../MicrosoftAccountMiddlewareTests.cs | 17 ++- 10 files changed, 114 insertions(+), 71 deletions(-) create mode 100644 samples/SocialSample/compiler/resources/cert.pfx diff --git a/samples/SocialSample/Properties/launchSettings.json b/samples/SocialSample/Properties/launchSettings.json index 15c91efdcb..e9d26ad03e 100644 --- a/samples/SocialSample/Properties/launchSettings.json +++ b/samples/SocialSample/Properties/launchSettings.json @@ -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" } } } diff --git a/samples/SocialSample/Startup.cs b/samples/SocialSample/Startup.cs index 108ad6ff86..358a031cd3 100644 --- a/samples/SocialSample/Startup.cs +++ b/samples/SocialSample/Startup.cs @@ -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"); + } + } } } diff --git a/samples/SocialSample/compiler/resources/cert.pfx b/samples/SocialSample/compiler/resources/cert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..7118908c2d730670c16e9f8b2c532a262c951989 GIT binary patch literal 2483 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 literal 0 HcmV?d00001 diff --git a/samples/SocialSample/config.json b/samples/SocialSample/config.json index 13d3ff94a0..5c1453e39f 100644 --- a/samples/SocialSample/config.json +++ b/samples/SocialSample/config.json @@ -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" } diff --git a/samples/SocialSample/project.json b/samples/SocialSample/project.json index a4c71e0b70..1d02230294 100644 --- a/samples/SocialSample/project.json +++ b/samples/SocialSample/project.json @@ -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" -} \ No newline at end of file + "userSecretsId": "aspnet5-SocialSample-20151210111056", + "content": [ + "config.json", + "project.json" + ] +} diff --git a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountDefaults.cs b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountDefaults.cs index d853794779..0d272f9792 100644 --- a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountDefaults.cs +++ b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountDefaults.cs @@ -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"; } } diff --git a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHandler.cs b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHandler.cs index 245fbed87d..8b9177625c 100644 --- a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHandler.cs @@ -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)) { diff --git a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHelper.cs b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHelper.cs index 8b88cbc44c..cce8dcc73b 100644 --- a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHelper.cs +++ b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountHelper.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount { /// /// Contains static methods that allow to extract user's information from a - /// 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 /// public static class MicrosoftAccountHelper { @@ -28,40 +29,40 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount /// /// Gets the user's name. /// - public static string GetName(JObject user) + public static string GetDisplayName(JObject user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - return user.Value("name"); + return user.Value("displayName"); } /// - /// Gets the user's first name. + /// Gets the user's given name. /// - public static string GetFirstName(JObject user) + public static string GetGivenName(JObject user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - return user.Value("first_name"); + return user.Value("givenName"); } /// - /// Gets the user's last name. + /// Gets the user's surname. /// - public static string GetLastName(JObject user) + public static string GetSurname(JObject user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - return user.Value("last_name"); + return user.Value("surname"); } /// @@ -74,7 +75,7 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount throw new ArgumentNullException(nameof(user)); } - return user.Value("emails")?.Value("preferred"); + return user.Value("mail") ?? user.Value("userPrincipalName"); } } } diff --git a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountOptions.cs b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountOptions.cs index 2783bcef1f..625d4baf9c 100644 --- a/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountOptions.cs @@ -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"); } } } diff --git a/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs index f5a43b8ed2..0ddfb5a3c7 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs @@ -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" }); }