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

Update MicrosoftAccount to use graph API & converged authentication #691

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions samples/SocialSample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
"web": {
"commandName": "web",
"launchBrowser": true,
"launchUrl": "http://localhost:54540/",
"launchUrl": "https://localhost:54541/",
"environmentVariables": {
"Hosting:Environment": "Development",
"ASPNET_server.urls": "http://localhost:54540/"
"ASPNET_server.urls": "https://localhost:54541/;http://localhost:54540/"
}
}
}
Expand Down
63 changes: 39 additions & 24 deletions samples/SocialSample/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
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.AspNet.FileProviders;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
Expand All @@ -14,12 +18,13 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Server.Kestrel.Filter;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
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
Expand All @@ -44,6 +49,10 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
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) =>
{
Expand Down Expand Up @@ -128,44 +137,32 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
}
});

// 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/
*/

// See config.json
// https://apps.dev.microsoft.com/
app.UseOAuthAuthentication(new OAuthOptions
{
AuthenticationScheme = "Microsoft-AccessToken",
DisplayName = "MicrosoftAccount-AccessToken - Requires project changes",
DisplayName = "MicrosoftAccount-AccessToken",
ClientId = Configuration["msa:clientid"],
ClientSecret = Configuration["msa:clientsecret"],
CallbackPath = new PathString("/signin-microsoft-token"),
AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
Scope = { "wl.basic" },
Scope = { "https://graph.microsoft.com/user.read" },
SaveTokensAsClaims = true
});

//// You must first create an app with live.com and add it's ID and Secret to your config.json or user-secrets.
// See config.json
app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions
{
DisplayName = "MicrosoftAccount - Requires project changes",
DisplayName = "MicrosoftAccount",
ClientId = Configuration["msa:clientid"],
ClientSecret = Configuration["msa:clientsecret"],
Scope = { "wl.emails" }
ClientSecret = Configuration["msa:clientsecret"]
});

// See config.json
Expand Down Expand Up @@ -334,5 +331,23 @@ public static void Main(string[] args)

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 added samples/SocialSample/compiler/resources/cert.pfx
Binary file not shown.
4 changes: 3 additions & 1 deletion samples/SocialSample/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
"github:clientid": "49e302895d8b09ea5656",
"github:clientsecret": "98f1bf028608901e9df91d64ee61536fe562064b",
"github-token:clientid": "8c0c5a572abe8fe89588",
"github-token:clientsecret": "e1d95eaf03461d27acd6f49d4fc7bf19d6ac8cda"
"github-token:clientsecret": "e1d95eaf03461d27acd6f49d4fc7bf19d6ac8cda",
"msa:clientid": "a7bfff26-c032-47d9-883f-aaa9cda2a9b5",
"msa:clientsecret": "TWFzu11P6BthrmEebBPnaVz"
}
2 changes: 2 additions & 0 deletions samples/SocialSample/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"dependencies": {
"Microsoft.AspNet.FileProviders.Embedded": "1.0.0-*",
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0-*",
"Microsoft.AspNetCore.Authentication.Facebook": "1.0.0-*",
"Microsoft.AspNetCore.Authentication.Google": "1.0.0-*",
Expand All @@ -8,6 +9,7 @@
"Microsoft.AspNetCore.DataProtection": "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.Logging.Console": "1.0.0-*",
"Microsoft.NETCore.Platforms": "1.0.1-*"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ public static class MicrosoftAccountDefaults
{
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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,27 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIden
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))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -28,40 +29,40 @@ public static string GetId(JObject user)
/// <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>
Expand All @@ -74,7 +75,7 @@ public static string GetEmail(JObject user)
throw new ArgumentNullException(nameof(user));
}

return user.Value<JObject>("emails")?.Value<string>("preferred");
return user.Value<string>("mail") ?? user.Value<string>("userPrincipalName");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public MicrosoftAccountOptions()
AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint;
TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint;
UserInformationEndpoint = MicrosoftAccountDefaults.UserInformationEndpoint;
Scope.Add("wl.basic");
Scope.Add("https://graph.microsoft.com/user.read");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public async Task ChallengeWillTriggerRedirection()
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);
Expand All @@ -113,7 +113,7 @@ public async Task AuthenticatedEventCanGetRefreshToken()
{
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
{
Expand All @@ -123,18 +123,15 @@ public async Task AuthenticatedEventCanGetRefreshToken()
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"
});
}

Expand Down