Replace JObject with JsonDocument in Authentication (#7105)

This commit is contained in:
Chris Ross 2019-02-05 13:37:20 -08:00 committed by GitHub
parent d630a20d1a
commit 67037a0039
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 521 additions and 340 deletions

View File

@ -21,7 +21,7 @@ namespace MusicStore.Mocks.Facebook
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "Id", "");
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst("urn:facebook:link")?.Value == "https://www.facebook.com/myLink", "");
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "");
Helpers.ThrowIfConditionFailed(() => context.User.SelectToken("id").ToString() == context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value, "");
Helpers.ThrowIfConditionFailed(() => context.User.GetString("id") == context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value, "");
Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(100), "");
Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", "");
context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false"));

View File

@ -23,7 +23,6 @@ namespace MusicStore.Mocks.Google
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Surname)?.Value == "AspnetvnextTest", "FamilyName is not valid");
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "Name is not valid");
Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(1200), "ExpiresIn is not valid");
Helpers.ThrowIfConditionFailed(() => context.User != null, "User object is not valid");
context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false"));
}

View File

@ -23,8 +23,7 @@ namespace MusicStore.Mocks.MicrosoftAccount
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "fccf9a24999f4f4f", "Id is not valid");
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "Name is not valid");
Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(3600), "ExpiresIn is not valid");
Helpers.ThrowIfConditionFailed(() => context.User != null, "User object is not valid");
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == context.User.SelectToken("id").ToString(), "User id is not valid");
Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == context.User.GetString("id"), "User id is not valid");
context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false"));
}

View File

@ -0,0 +1,2 @@
#launchSettings.json files are required for the auth samples. The ports must not change.
!launchSettings.json

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:1780/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"CookieSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:1782/"
}
}
}

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:1771/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"CookieSessionSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:1776/"
}
}
}

View File

@ -0,0 +1,14 @@
// 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 System.Text.Json;
namespace Microsoft.AspNetCore.Authentication
{
public static class JsonDocumentAuthExtensions
{
public static string GetString(this JsonElement element, string key) =>
element.TryGetProperty(key, out var property)
? property.ToString() : null;
}
}

View File

@ -8,12 +8,12 @@ using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.Facebook
{
@ -41,15 +41,13 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
throw new HttpRequestException($"An error occurred when retrieving Facebook user information ({response.StatusCode}). Please check if the authentication information is correct and the corresponding Facebook Graph API is enabled.");
}
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload);
context.RunClaimActions();
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()))
{
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);
context.RunClaimActions();
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
}
}
private string GenerateAppSecretProof(string accessToken)

View File

@ -7,12 +7,12 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.Google
{
@ -37,13 +37,13 @@ namespace Microsoft.AspNetCore.Authentication.Google
throw new HttpRequestException($"An error occurred when retrieving Google user information ({response.StatusCode}). Please check if the authentication information is correct and the corresponding Google+ API is enabled.");
}
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload);
context.RunClaimActions();
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()))
{
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);
context.RunClaimActions();
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
}
}
// TODO: Abstract this properties override pattern into the base class?

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:44318/",
"sslPort": 44318
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SocialSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:44318/"
}
}
}

View File

@ -1,7 +1,11 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Runtime.ExceptionServices;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
@ -10,7 +14,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
namespace JwtBearerSample
{
@ -93,19 +96,37 @@ namespace JwtBearerSample
{
var reader = new StreamReader(context.Request.Body);
var body = await reader.ReadToEndAsync();
var obj = JObject.Parse(body);
var todo = new Todo() { Description = obj["Description"].Value<string>(), Owner = context.User.Identity.Name };
Todos.Add(todo);
using (var json = JsonDocument.Parse(body))
{
var obj = json.RootElement;
var todo = new Todo() { Description = obj.GetProperty("Description").GetString(), Owner = context.User.Identity.Name };
Todos.Add(todo);
}
}
else
{
response.ContentType = "application/json";
response.Headers[HeaderNames.CacheControl] = "no-cache";
var json = JToken.FromObject(Todos);
await response.WriteAsync(json.ToString());
Serialize(Todos, response.BodyPipe);
await response.BodyPipe.FlushAsync();
}
});
});
}
private void Serialize(IList<Todo> todos, IBufferWriter<byte> output)
{
var writer = new Utf8JsonWriter(output);
writer.WriteStartArray();
foreach (var todo in todos)
{
writer.WriteStartObject();
writer.WriteString("Description", todo.Description);
writer.WriteString("Owner", todo.Owner);
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.Flush();
}
}
}
}

View File

@ -5,11 +5,11 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
{
@ -30,13 +30,13 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
throw new HttpRequestException($"An error occurred when retrieving Microsoft user information ({response.StatusCode}). Please check if the authentication information is correct and the corresponding Microsoft Account API is enabled.");
}
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload);
context.RunClaimActions();
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()))
{
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);
context.RunClaimActions();
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
}
}
}
}

View File

@ -2,10 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
{
@ -29,7 +27,7 @@ namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount
ClaimActions.MapJsonKey(ClaimTypes.Name, "displayName");
ClaimActions.MapJsonKey(ClaimTypes.GivenName, "givenName");
ClaimActions.MapJsonKey(ClaimTypes.Surname, "surname");
ClaimActions.MapCustomJson(ClaimTypes.Email, user => user.Value<string>("mail") ?? user.Value<string>("userPrincipalName"));
ClaimActions.MapCustomJson(ClaimTypes.Email, user => user.GetString("mail") ?? user.GetString("userPrincipalName"));
}
}
}

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 System.Security.Claims;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
{
@ -37,6 +37,6 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
/// <param name="userData">The source data to examine. This value may be null.</param>
/// <param name="identity">The identity to add Claims to.</param>
/// <param name="issuer">The value to use for Claim.Issuer when creating a Claim.</param>
public abstract void Run(JObject userData, ClaimsIdentity identity, string issuer);
public abstract void Run(JsonElement userData, ClaimsIdentity identity, string issuer);
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 System;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication
{
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Authentication
/// <param name="collection"></param>
/// <param name="claimType">The value to use for Claim.Type when creating a Claim.</param>
/// <param name="resolver">The Func that will be called to select value from the given json user data.</param>
public static void MapCustomJson(this ClaimActionCollection collection, string claimType, Func<JObject, string> resolver)
public static void MapCustomJson(this ClaimActionCollection collection, string claimType, Func<JsonElement, string> resolver)
{
collection.MapCustomJson(claimType, ClaimValueTypes.String, resolver);
}
@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Authentication
/// <param name="claimType">The value to use for Claim.Type when creating a Claim.</param>
/// <param name="valueType">The value to use for Claim.ValueType when creating a Claim.</param>
/// <param name="resolver">The Func that will be called to select value from the given json user data.</param>
public static void MapCustomJson(this ClaimActionCollection collection, string claimType, string valueType, Func<JObject, string> resolver)
public static void MapCustomJson(this ClaimActionCollection collection, string claimType, string valueType, Func<JsonElement, string> resolver)
{
collection.Add(new CustomJsonClaimAction(claimType, valueType, resolver));
}

View File

@ -1,9 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 System;
using System.Security.Claims;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
{
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
/// <param name="claimType">The value to use for Claim.Type when creating a Claim.</param>
/// <param name="valueType">The value to use for Claim.ValueType when creating a Claim.</param>
/// <param name="resolver">The Func that will be called to select value from the given json user data.</param>
public CustomJsonClaimAction(string claimType, string valueType, Func<JObject, string> resolver)
public CustomJsonClaimAction(string claimType, string valueType, Func<JsonElement, string> resolver)
: base(claimType, valueType)
{
Resolver = resolver;
@ -27,15 +27,11 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
/// <summary>
/// The Func that will be called to select value from the given json user data.
/// </summary>
public Func<JObject, string> Resolver { get; }
public Func<JsonElement, string> Resolver { get; }
/// <inheritdoc />
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
if (userData == null)
{
return;
}
var value = Resolver(userData);
if (!string.IsNullOrEmpty(value))
{

View File

@ -1,9 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 System.Linq;
using System.Security.Claims;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
{
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
}
/// <inheritdoc />
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
foreach (var claim in identity.FindAll(ClaimType).ToList())
{

View File

@ -5,8 +5,8 @@ using System;
using System.Globalization;
using System.Net.Http;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.OAuth
{
@ -15,27 +15,6 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
/// </summary>
public class OAuthCreatingTicketContext : ResultContext<OAuthOptions>
{
/// <summary>
/// Initializes a new <see cref="OAuthCreatingTicketContext"/>.
/// </summary>
/// <param name="principal">The <see cref="ClaimsPrincipal"/>.</param>
/// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
/// <param name="context">The HTTP environment.</param>
/// <param name="scheme">The authentication scheme.</param>
/// <param name="options">The options used by the authentication middleware.</param>
/// <param name="backchannel">The HTTP client used by the authentication middleware</param>
/// <param name="tokens">The tokens returned from the token endpoint.</param>
public OAuthCreatingTicketContext(
ClaimsPrincipal principal,
AuthenticationProperties properties,
HttpContext context,
AuthenticationScheme scheme,
OAuthOptions options,
HttpClient backchannel,
OAuthTokenResponse tokens)
: this(principal, properties, context, scheme, options, backchannel, tokens, user: new JObject())
{ }
/// <summary>
/// Initializes a new <see cref="OAuthCreatingTicketContext"/>.
/// </summary>
@ -55,7 +34,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
OAuthOptions options,
HttpClient backchannel,
OAuthTokenResponse tokens,
JObject user)
JsonElement user)
: base(context, scheme, options)
{
if (backchannel == null)
@ -68,11 +47,6 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
throw new ArgumentNullException(nameof(tokens));
}
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
TokenResponse = tokens;
Backchannel = backchannel;
User = user;
@ -82,9 +56,9 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
/// <summary>
/// Gets the JSON-serialized user or an empty
/// <see cref="JObject"/> if it is not available.
/// <see cref="JsonElement"/> if it is not available.
/// </summary>
public JObject User { get; }
public JsonElement User { get; }
/// <summary>
/// Gets the token response returned by the authentication service.
@ -136,17 +110,12 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
public void RunClaimActions() => RunClaimActions(User);
public void RunClaimActions(JObject userData)
public void RunClaimActions(JsonElement userData)
{
if (userData == null)
{
throw new ArgumentNullException(nameof(userData));
}
foreach (var action in Options.ClaimActions)
{
action.Run(userData, Identity, Options.ClaimsIssuer ?? Scheme.Name);
}
}
}
}
}

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 System.Security.Claims;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
{
@ -30,20 +30,27 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
public string JsonKey { get; }
/// <inheritdoc />
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
var value = userData?[JsonKey];
if (value is JValue)
if (!userData.TryGetProperty(JsonKey, out var value))
{
AddClaim(value?.ToString(), identity, issuer);
return;
}
else if (value is JArray)
if (value.Type == JsonValueType.Array)
{
foreach (var v in value)
foreach (var v in value.EnumerateArray())
{
AddClaim(v?.ToString(), identity, issuer);
AddClaim(v.ToString(), identity, issuer);
}
}
else if (value.Type == JsonValueType.Object || value.Type == JsonValueType.Undefined)
{
// Skip, because they were previously skipped
}
else
{
AddClaim(value.ToString(), identity, issuer);
}
}
private void AddClaim(string value, ClaimsIdentity identity, string issuer)

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 Newtonsoft.Json.Linq;
using System.Security.Claims;
using System.Text.Json;
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
{
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
public string SubKey { get; }
/// <inheritdoc />
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
var value = GetValue(userData, JsonKey, SubKey);
if (!string.IsNullOrEmpty(value))
@ -42,15 +42,12 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
}
// Get the given subProperty from a property.
private static string GetValue(JObject userData, string propertyName, string subProperty)
private static string GetValue(JsonElement userData, string propertyName, string subProperty)
{
if (userData != null && userData.TryGetValue(propertyName, out var value))
if (userData.TryGetProperty(propertyName, out var value)
&& value.Type == JsonValueType.Object && value.TryGetProperty(subProperty, out value))
{
var subObject = JObject.Parse(value.ToString());
if (subObject != null && subObject.TryGetValue(subProperty, out value))
{
return value.ToString();
}
return value.ToString();
}
return null;
}

View File

@ -1,9 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 System;
using System.Security.Claims;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
{
@ -17,24 +17,20 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
{
}
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
if (userData == null)
foreach (var pair in userData.EnumerateObject())
{
return;
}
foreach (var pair in userData)
{
var claimValue = userData.TryGetValue(pair.Key, out var value) ? value.ToString() : null;
var claimValue = pair.Value.ToString();
// Avoid adding a claim if there's a duplicate name and value. This often happens in OIDC when claims are
// retrieved both from the id_token and from the user-info endpoint.
var duplicate = identity.FindFirst(c => string.Equals(c.Type, pair.Key, StringComparison.OrdinalIgnoreCase)
var duplicate = identity.FindFirst(c => string.Equals(c.Type, pair.Name, StringComparison.OrdinalIgnoreCase)
&& string.Equals(c.Value, claimValue, StringComparison.Ordinal)) != null;
if (!duplicate)
{
identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, issuer));
identity.AddClaim(new Claim(pair.Name, claimValue, ClaimValueTypes.String, issuer));
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>ASP.NET Core middleware that enables an application to support any standard OAuth 2.0 authentication workflow.</Description>
@ -10,7 +10,6 @@
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Authentication" />
<Reference Include="Newtonsoft.Json" />
</ItemGroup>
</Project>

View File

@ -9,13 +9,13 @@ using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.OAuth
{
@ -99,62 +99,63 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
return HandleRequestResult.Fail("Code was not found.", properties);
}
var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));
if (tokens.Error != null)
using (var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath)))
{
return HandleRequestResult.Fail(tokens.Error, properties);
}
if (string.IsNullOrEmpty(tokens.AccessToken))
{
return HandleRequestResult.Fail("Failed to retrieve access token.", properties);
}
var identity = new ClaimsIdentity(ClaimsIssuer);
if (Options.SaveTokens)
{
var authTokens = new List<AuthenticationToken>();
authTokens.Add(new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken });
if (!string.IsNullOrEmpty(tokens.RefreshToken))
if (tokens.Error != null)
{
authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = tokens.RefreshToken });
return HandleRequestResult.Fail(tokens.Error, properties);
}
if (!string.IsNullOrEmpty(tokens.TokenType))
if (string.IsNullOrEmpty(tokens.AccessToken))
{
authTokens.Add(new AuthenticationToken { Name = "token_type", Value = tokens.TokenType });
return HandleRequestResult.Fail("Failed to retrieve access token.", properties);
}
if (!string.IsNullOrEmpty(tokens.ExpiresIn))
var identity = new ClaimsIdentity(ClaimsIssuer);
if (Options.SaveTokens)
{
int value;
if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
var authTokens = new List<AuthenticationToken>();
authTokens.Add(new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken });
if (!string.IsNullOrEmpty(tokens.RefreshToken))
{
// https://www.w3.org/TR/xmlschema-2/#dateTime
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
var expiresAt = Clock.UtcNow + TimeSpan.FromSeconds(value);
authTokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = tokens.RefreshToken });
}
if (!string.IsNullOrEmpty(tokens.TokenType))
{
authTokens.Add(new AuthenticationToken { Name = "token_type", Value = tokens.TokenType });
}
if (!string.IsNullOrEmpty(tokens.ExpiresIn))
{
int value;
if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
{
// https://www.w3.org/TR/xmlschema-2/#dateTime
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
var expiresAt = Clock.UtcNow + TimeSpan.FromSeconds(value);
authTokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
}
}
properties.StoreTokens(authTokens);
}
properties.StoreTokens(authTokens);
}
var ticket = await CreateTicketAsync(identity, properties, tokens);
if (ticket != null)
{
return HandleRequestResult.Success(ticket);
}
else
{
return HandleRequestResult.Fail("Failed to retrieve user information from remote server.", properties);
var ticket = await CreateTicketAsync(identity, properties, tokens);
if (ticket != null)
{
return HandleRequestResult.Success(ticket);
}
else
{
return HandleRequestResult.Fail("Failed to retrieve user information from remote server.", properties);
}
}
}
@ -177,7 +178,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
if (response.IsSuccessStatusCode)
{
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
return OAuthTokenResponse.Success(payload);
}
else
@ -198,9 +199,12 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
{
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens);
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
using (var user = JsonDocument.Parse("{}"))
{
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, user.RootElement);
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
}
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)

View File

@ -2,19 +2,20 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace Microsoft.AspNetCore.Authentication.OAuth
{
public class OAuthTokenResponse
public class OAuthTokenResponse : IDisposable
{
private OAuthTokenResponse(JObject response)
private OAuthTokenResponse(JsonDocument response)
{
Response = response;
AccessToken = response.Value<string>("access_token");
TokenType = response.Value<string>("token_type");
RefreshToken = response.Value<string>("refresh_token");
ExpiresIn = response.Value<string>("expires_in");
var root = response.RootElement;
AccessToken = root.GetString("access_token");
TokenType = root.GetString("token_type");
RefreshToken = root.GetString("refresh_token");
ExpiresIn = root.GetString("expires_in");
}
private OAuthTokenResponse(Exception error)
@ -22,7 +23,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
Error = error;
}
public static OAuthTokenResponse Success(JObject response)
public static OAuthTokenResponse Success(JsonDocument response)
{
return new OAuthTokenResponse(response);
}
@ -32,11 +33,16 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
return new OAuthTokenResponse(error);
}
public JObject Response { get; set; }
public void Dispose()
{
Response?.Dispose();
}
public JsonDocument Response { get; set; }
public string AccessToken { get; set; }
public string TokenType { get; set; }
public string RefreshToken { get; set; }
public string ExpiresIn { get; set; }
public Exception Error { get; set; }
}
}
}

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:44318/",
"sslPort": 44318
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SocialSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:44318/"
}
}
}

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:44318/",
"sslPort": 44318
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SocialSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:44318/"
}
}
}

View File

@ -5,6 +5,7 @@ using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
@ -16,7 +17,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Newtonsoft.Json.Linq;
namespace OpenIdConnectSample
{
@ -208,30 +208,31 @@ namespace OpenIdConnectSample
var tokenResponse = await options.Backchannel.PostAsync(metadata.TokenEndpoint, content, context.RequestAborted);
tokenResponse.EnsureSuccessStatusCode();
var payload = JObject.Parse(await tokenResponse.Content.ReadAsStringAsync());
// Persist the new acess token
props.UpdateTokenValue("access_token", payload.Value<string>("access_token"));
props.UpdateTokenValue("refresh_token", payload.Value<string>("refresh_token"));
if (int.TryParse(payload.Value<string>("expires_in"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds))
using (var payload = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync()))
{
var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds);
props.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
// Persist the new acess token
props.UpdateTokenValue("access_token", payload.RootElement.GetString("access_token"));
props.UpdateTokenValue("refresh_token", payload.RootElement.GetString("refresh_token"));
if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds))
{
var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds);
props.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
}
await context.SignInAsync(user, props);
await WriteHtmlAsync(response, async res =>
{
await res.WriteAsync($"<h1>Refreshed.</h1>");
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/refresh\">Refresh tokens</a>");
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/\">Home</a>");
await res.WriteAsync("<h2>Tokens:</h2>");
await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value }));
await res.WriteAsync("<h2>Payload:</h2>");
await res.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",<br>") + "<br>");
});
}
await context.SignInAsync(user, props);
await WriteHtmlAsync(response, async res =>
{
await res.WriteAsync($"<h1>Refreshed.</h1>");
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/refresh\">Refresh tokens</a>");
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/\">Home</a>");
await res.WriteAsync("<h2>Tokens:</h2>");
await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value }));
await res.WriteAsync("<h2>Payload:</h2>");
await res.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",<br>") + "<br>");
});
return;
}

View File

@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
{
@ -16,6 +16,6 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
public OpenIdConnectMessage ProtocolMessage { get; set; }
public JObject User { get; set; }
public JsonDocument User { get; set; }
}
}

View File

@ -12,6 +12,7 @@ using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@ -20,7 +21,6 @@ using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
{
@ -712,10 +712,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
}
else
{
var identity = (ClaimsIdentity)user.Identity;
foreach (var action in Options.ClaimActions)
using (var payload = JsonDocument.Parse("{}"))
{
action.Run(null, identity, ClaimsIssuer);
var identity = (ClaimsIdentity)user.Identity;
foreach (var action in Options.ClaimActions)
{
action.Run(payload.RootElement, identity, ClaimsIssuer);
}
}
}
@ -853,42 +856,46 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
responseMessage.EnsureSuccessStatusCode();
var userInfoResponse = await responseMessage.Content.ReadAsStringAsync();
JObject user;
JsonDocument user;
var contentType = responseMessage.Content.Headers.ContentType;
if (contentType.MediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase))
{
user = JObject.Parse(userInfoResponse);
user = JsonDocument.Parse(userInfoResponse);
}
else if (contentType.MediaType.Equals("application/jwt", StringComparison.OrdinalIgnoreCase))
{
var userInfoEndpointJwt = new JwtSecurityToken(userInfoResponse);
user = JObject.FromObject(userInfoEndpointJwt.Payload);
user = JsonDocument.Parse(userInfoEndpointJwt.Payload.SerializeToJson());
}
else
{
return HandleRequestResult.Fail("Unknown response type: " + contentType.MediaType, properties);
}
var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(principal, properties, message, user);
if (userInformationReceivedContext.Result != null)
using (user)
{
return userInformationReceivedContext.Result;
}
principal = userInformationReceivedContext.Principal;
properties = userInformationReceivedContext.Properties;
user = userInformationReceivedContext.User;
var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(principal, properties, message, user);
if (userInformationReceivedContext.Result != null)
{
return userInformationReceivedContext.Result;
}
principal = userInformationReceivedContext.Principal;
properties = userInformationReceivedContext.Properties;
using (var updatedUser = userInformationReceivedContext.User)
{
Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext()
{
UserInfoEndpointResponse = userInfoResponse,
ValidatedIdToken = jwt,
});
Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext()
{
UserInfoEndpointResponse = userInfoResponse,
ValidatedIdToken = jwt,
});
var identity = (ClaimsIdentity)principal.Identity;
var identity = (ClaimsIdentity)principal.Identity;
foreach (var action in Options.ClaimActions)
{
action.Run(user, identity, ClaimsIssuer);
foreach (var action in Options.ClaimActions)
{
action.Run(user.RootElement, identity, ClaimsIssuer);
}
}
}
return HandleRequestResult.Success(new AuthenticationTicket(principal, properties, Scheme.Name));
@ -1144,7 +1151,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
return context;
}
private async Task<UserInformationReceivedContext> RunUserInformationReceivedEventAsync(ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage message, JObject user)
private async Task<UserInformationReceivedContext> RunUserInformationReceivedEventAsync(ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage message, JsonDocument user)
{
Logger.UserInformationReceived(user.ToString());

View File

@ -1,10 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.OpenIdConnect.Claims
{
@ -27,9 +27,9 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect.Claims
}
/// <inheritdoc />
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
var value = userData?.Value<string>(JsonKey);
var value = userData.GetString(JsonKey);
if (string.IsNullOrEmpty(value))
{
// Not found

View File

@ -1,10 +1,9 @@
// 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 System;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.Twitter
{
@ -36,14 +35,14 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
string screenName,
string accessToken,
string accessTokenSecret,
JObject user)
JsonElement user)
: base(context, scheme, options)
{
UserId = userId;
ScreenName = screenName;
AccessToken = accessToken;
AccessTokenSecret = accessTokenSecret;
User = user ?? new JObject();
User = user;
Principal = principal;
Properties = properties;
}
@ -70,8 +69,8 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
/// <summary>
/// Gets the JSON-serialized user or an empty
/// <see cref="JObject"/> if it is not available.
/// <see cref="JsonElement"/> if it is not available.
/// </summary>
public JObject User { get; }
public JsonElement User { get; }
}
}

View File

@ -9,13 +9,13 @@ using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.Twitter
{
@ -99,25 +99,33 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
},
ClaimsIssuer);
JObject user = null;
JsonDocument user;
if (Options.RetrieveUserDetails)
{
user = await RetrieveUserDetailsAsync(accessToken, identity);
}
if (Options.SaveTokens)
else
{
properties.StoreTokens(new [] {
new AuthenticationToken { Name = "access_token", Value = accessToken.Token },
new AuthenticationToken { Name = "access_token_secret", Value = accessToken.TokenSecret }
});
user = JsonDocument.Parse("{}");
}
return HandleRequestResult.Success(await CreateTicketAsync(identity, properties, accessToken, user));
using (user)
{
if (Options.SaveTokens)
{
properties.StoreTokens(new[] {
new AuthenticationToken { Name = "access_token", Value = accessToken.Token },
new AuthenticationToken { Name = "access_token_secret", Value = accessToken.TokenSecret }
});
}
var ticket = await CreateTicketAsync(identity, properties, accessToken, user.RootElement);
return HandleRequestResult.Success(ticket);
}
}
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(
ClaimsIdentity identity, AuthenticationProperties properties, AccessToken token, JObject user)
ClaimsIdentity identity, AuthenticationProperties properties, AccessToken token, JsonElement user)
{
foreach (var action in Options.ClaimActions)
{
@ -275,7 +283,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
}
// https://dev.twitter.com/rest/reference/get/account/verify_credentials
private async Task<JObject> RetrieveUserDetailsAsync(AccessToken accessToken, ClaimsIdentity identity)
private async Task<JsonDocument> RetrieveUserDetailsAsync(AccessToken accessToken, ClaimsIdentity identity)
{
Logger.RetrieveUserDetails();
@ -288,7 +296,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
}
var responseText = await response.Content.ReadAsStringAsync();
var result = JObject.Parse(responseText);
var result = JsonDocument.Parse(responseText);
return result;
}

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:44318/",
"sslPort": 44318
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SocialSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:44318/"
}
}
}

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:44318/",
"sslPort": 44318
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SocialSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:44318/"
}
}
}

View File

@ -6,6 +6,7 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
@ -21,7 +22,6 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
namespace SocialSample
{
@ -207,9 +207,10 @@ namespace SocialSample
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
using (var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync()))
{
context.RunClaimActions(user.RootElement);
}
}
};
});
@ -332,24 +333,25 @@ namespace SocialSample
var refreshResponse = await options.Backchannel.PostAsync(options.TokenEndpoint, content, context.RequestAborted);
refreshResponse.EnsureSuccessStatusCode();
var payload = JObject.Parse(await refreshResponse.Content.ReadAsStringAsync());
// Persist the new acess token
authProperties.UpdateTokenValue("access_token", payload.Value<string>("access_token"));
refreshToken = payload.Value<string>("refresh_token");
if (!string.IsNullOrEmpty(refreshToken))
using (var payload = JsonDocument.Parse(await refreshResponse.Content.ReadAsStringAsync()))
{
authProperties.UpdateTokenValue("refresh_token", refreshToken);
}
if (int.TryParse(payload.Value<string>("expires_in"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds))
{
var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds);
authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
}
await context.SignInAsync(user, authProperties);
await PrintRefreshedTokensAsync(response, payload, authProperties);
// Persist the new acess token
authProperties.UpdateTokenValue("access_token", payload.RootElement.GetString("access_token"));
refreshToken = payload.RootElement.GetString("refresh_token");
if (!string.IsNullOrEmpty(refreshToken))
{
authProperties.UpdateTokenValue("refresh_token", refreshToken);
}
if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds))
{
var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds);
authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
}
await context.SignInAsync(user, authProperties);
await PrintRefreshedTokensAsync(response, payload, authProperties);
}
return;
}
// https://developers.facebook.com/docs/facebook-login/access-tokens/expiration-and-extension
@ -368,18 +370,18 @@ namespace SocialSample
}.ToQueryString();
var refreshResponse = await options.Backchannel.GetStringAsync(options.TokenEndpoint + query);
var payload = JObject.Parse(refreshResponse);
authProperties.UpdateTokenValue("access_token", payload.Value<string>("access_token"));
if (int.TryParse(payload.Value<string>("expires_in"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds))
using (var payload = JsonDocument.Parse(refreshResponse))
{
var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds);
authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
authProperties.UpdateTokenValue("access_token", payload.RootElement.GetString("access_token"));
if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds))
{
var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds);
authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
}
await context.SignInAsync(user, authProperties);
await PrintRefreshedTokensAsync(response, payload, authProperties);
}
await context.SignInAsync(user, authProperties);
await PrintRefreshedTokensAsync(response, payload, authProperties);
return;
}
@ -485,12 +487,12 @@ namespace SocialSample
throw new NotImplementedException(currentAuthType);
}
private async Task PrintRefreshedTokensAsync(HttpResponse response, JObject payload, AuthenticationProperties authProperties)
private async Task PrintRefreshedTokensAsync(HttpResponse response, JsonDocument payload, AuthenticationProperties authProperties)
{
response.ContentType = "text/html";
await response.WriteAsync("<html><body>");
await response.WriteAsync("Refreshed.<br>");
await response.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",<br>") + "<br>");
await response.WriteAsync(HtmlEncoder.Default.Encode(payload.RootElement.ToString()).Replace(",", ",<br>") + "<br>");
await response.WriteAsync("<br>Tokens:<br>");

View File

@ -4,6 +4,8 @@
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false">
<environmentVariables />
</aspNetCore>
</system.webServer>
</configuration>

View File

@ -1,14 +1,11 @@
// 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 System;
using System.IO;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Authentication
{
@ -17,15 +14,12 @@ namespace Microsoft.AspNetCore.Authentication
[Fact]
public void CanMapSingleValueUserDataToClaim()
{
var userData = new JObject
{
["name"] = "test"
};
var userData = JsonDocument.Parse("{ \"name\": \"test\" }");
var identity = new ClaimsIdentity();
var action = new JsonKeyClaimAction("name", "name", "name");
action.Run(userData, identity, "iss");
action.Run(userData.RootElement, identity, "iss");
Assert.Equal("name", identity.FindFirst("name").Type);
Assert.Equal("test", identity.FindFirst("name").Value);
@ -34,15 +28,12 @@ namespace Microsoft.AspNetCore.Authentication
[Fact]
public void CanMapArrayValueUserDataToClaims()
{
var userData = new JObject
{
["role"] = new JArray { "role1", "role2" }
};
var userData = JsonDocument.Parse("{ \"role\": [ \"role1\", null, \"role2\" ] }");
var identity = new ClaimsIdentity();
var action = new JsonKeyClaimAction("role", "role", "role");
action.Run(userData, identity, "iss");
action.Run(userData.RootElement, identity, "iss");
var roleClaims = identity.FindAll("role").ToList();
Assert.Equal(2, roleClaims.Count);
@ -55,15 +46,11 @@ namespace Microsoft.AspNetCore.Authentication
[Fact]
public void MapAllSucceeds()
{
var userData = new JObject
{
["name0"] = "value0",
["name1"] = "value1",
};
var userData = JsonDocument.Parse("{ \"name0\": \"value0\", \"name1\": \"value1\" }");
var identity = new ClaimsIdentity();
var action = new MapAllClaimsAction();
action.Run(userData, identity, "iss");
action.Run(userData.RootElement, identity, "iss");
Assert.Equal("name0", identity.FindFirst("name0").Type);
Assert.Equal("value0", identity.FindFirst("name0").Value);
@ -74,17 +61,13 @@ namespace Microsoft.AspNetCore.Authentication
[Fact]
public void MapAllAllowesDulicateKeysWithUniqueValues()
{
var userData = new JObject
{
["name0"] = "value0",
["name1"] = "value1",
};
var userData = JsonDocument.Parse("{ \"name0\": \"value0\", \"name1\": \"value1\" }");
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim("name0", "value2"));
identity.AddClaim(new Claim("name1", "value3"));
var action = new MapAllClaimsAction();
action.Run(userData, identity, "iss");
action.Run(userData.RootElement, identity, "iss");
Assert.Equal(2, identity.FindAll("name0").Count());
Assert.Equal(2, identity.FindAll("name1").Count());
@ -93,17 +76,13 @@ namespace Microsoft.AspNetCore.Authentication
[Fact]
public void MapAllSkipsDuplicateValues()
{
var userData = new JObject
{
["name0"] = "value0",
["name1"] = "value1",
};
var userData = JsonDocument.Parse("{ \"name0\": \"value0\", \"name1\": \"value1\" }");
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim("name0", "value0"));
identity.AddClaim(new Claim("name1", "value1"));
var action = new MapAllClaimsAction();
action.Run(userData, identity, "iss");
action.Run(userData.RootElement, identity, "iss");
Assert.Single(identity.FindAll("name0"));
Assert.Single(identity.FindAll("name1"));

View File

@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Net;
@ -325,10 +324,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == FacebookDefaults.TokenEndpoint)
{
var res = new HttpResponseMessage(HttpStatusCode.OK);
var graphResponse = JsonConvert.SerializeObject(new
{
access_token = "TestAuthToken"
});
var graphResponse = "{ \"access_token\": \"TestAuthToken\" }";
res.Content = new StringContent(graphResponse, Encoding.UTF8);
return res;
}
@ -337,11 +333,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
{
finalUserInfoEndpoint = req.RequestUri.ToString();
var res = new HttpResponseMessage(HttpStatusCode.OK);
var graphResponse = JsonConvert.SerializeObject(new
{
id = "TestProfileId",
name = "TestName"
});
var graphResponse = "{ \"id\": \"TestProfileId\", \"name\": \"TestName\" }";
res.Content = new StringContent(graphResponse, Encoding.UTF8);
return res;
}

View File

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
@ -704,7 +703,6 @@ namespace Microsoft.AspNetCore.Authentication.Google
{
OnCreatingTicket = context =>
{
Assert.NotNull(context.User);
Assert.Equal("Test Access Token", context.AccessToken);
Assert.Equal("Test Refresh Token", context.RefreshToken);
Assert.Equal(TimeSpan.FromSeconds(3600), context.ExpiresIn);
@ -972,7 +970,7 @@ namespace Microsoft.AspNetCore.Authentication.Google
private static HttpResponseMessage ReturnJsonResponse(object content, HttpStatusCode code = HttpStatusCode.OK)
{
var res = new HttpResponseMessage(code);
var text = JsonConvert.SerializeObject(content);
var text = Newtonsoft.Json.JsonConvert.SerializeObject(content);
res.Content = new StringContent(text, Encoding.UTF8, "application/json");
return res;
}

View File

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Net;
@ -302,7 +301,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
private static HttpResponseMessage ReturnJsonResponse(object content)
{
var res = new HttpResponseMessage(HttpStatusCode.OK);
var text = JsonConvert.SerializeObject(content);
var text = Newtonsoft.Json.JsonConvert.SerializeObject(content);
res.Content = new StringContent(text, Encoding.UTF8, "application/json");
return res;
}