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 0000000000..7118908c2d Binary files /dev/null and b/samples/SocialSample/compiler/resources/cert.pfx differ 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" }); }