diff --git a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs index cf8bb10d3796..98c6a3500ef7 100644 --- a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs +++ b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs @@ -21,7 +21,7 @@ internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) 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")); diff --git a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs index 4dec753c30de..f5af6cf9ae00 100644 --- a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs +++ b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs @@ -23,7 +23,6 @@ internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) 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")); } diff --git a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs index 3639a7bb278b..45bea42b0457 100644 --- a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs +++ b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs @@ -23,8 +23,7 @@ internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) 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")); } diff --git a/src/Security/Authentication/.gitignore b/src/Security/Authentication/.gitignore new file mode 100644 index 000000000000..c6dd745c38ef --- /dev/null +++ b/src/Security/Authentication/.gitignore @@ -0,0 +1,2 @@ +#launchSettings.json files are required for the auth samples. The ports must not change. +!launchSettings.json \ No newline at end of file diff --git a/src/Security/Authentication/Cookies/samples/CookieSample/Properties/launchSettings.json b/src/Security/Authentication/Cookies/samples/CookieSample/Properties/launchSettings.json new file mode 100644 index 000000000000..0c2b575f19bd --- /dev/null +++ b/src/Security/Authentication/Cookies/samples/CookieSample/Properties/launchSettings.json @@ -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/" + } + } +} \ No newline at end of file diff --git a/src/Security/Authentication/Cookies/samples/CookieSessionSample/Properties/launchSettings.json b/src/Security/Authentication/Cookies/samples/CookieSessionSample/Properties/launchSettings.json new file mode 100644 index 000000000000..290ebb2efe7a --- /dev/null +++ b/src/Security/Authentication/Cookies/samples/CookieSessionSample/Properties/launchSettings.json @@ -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/" + } + } +} \ No newline at end of file diff --git a/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs b/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs new file mode 100644 index 000000000000..54b8f8e42fef --- /dev/null +++ b/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs @@ -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; + } +} diff --git a/src/Security/Authentication/Facebook/src/FacebookHandler.cs b/src/Security/Authentication/Facebook/src/FacebookHandler.cs index eb42511431d6..7fe350385e80 100644 --- a/src/Security/Authentication/Facebook/src/FacebookHandler.cs +++ b/src/Security/Authentication/Facebook/src/FacebookHandler.cs @@ -8,12 +8,12 @@ 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 @@ protected override async Task CreateTicketAsync(ClaimsIden 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) diff --git a/src/Security/Authentication/Google/src/GoogleHandler.cs b/src/Security/Authentication/Google/src/GoogleHandler.cs index 88d48d446765..7d1df746f038 100644 --- a/src/Security/Authentication/Google/src/GoogleHandler.cs +++ b/src/Security/Authentication/Google/src/GoogleHandler.cs @@ -7,12 +7,12 @@ 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 @@ protected override async Task CreateTicketAsync( 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? diff --git a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Properties/launchSettings.json b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Properties/launchSettings.json @@ -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/" + } + } +} diff --git a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs index 8c4a63cad67b..f0faeefc6894 100644 --- a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs +++ b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs @@ -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.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; -using Newtonsoft.Json.Linq; namespace JwtBearerSample { @@ -93,19 +96,37 @@ public void Configure(IApplicationBuilder app) { 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(), 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 todos, IBufferWriter 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(); + } } -} \ No newline at end of file +} diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs index bba547277406..910da8d9146e 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs @@ -5,11 +5,11 @@ 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 @@ protected override async Task CreateTicketAsync(ClaimsIden 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); + } } } } diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountOptions.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountOptions.cs index dbca3507e914..8462913a3d93 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountOptions.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountOptions.cs @@ -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 @@ public MicrosoftAccountOptions() ClaimActions.MapJsonKey(ClaimTypes.Name, "displayName"); ClaimActions.MapJsonKey(ClaimTypes.GivenName, "givenName"); ClaimActions.MapJsonKey(ClaimTypes.Surname, "surname"); - ClaimActions.MapCustomJson(ClaimTypes.Email, user => user.Value("mail") ?? user.Value("userPrincipalName")); + ClaimActions.MapCustomJson(ClaimTypes.Email, user => user.GetString("mail") ?? user.GetString("userPrincipalName")); } } } diff --git a/src/Security/Authentication/OAuth/src/ClaimAction.cs b/src/Security/Authentication/OAuth/src/ClaimAction.cs index 78b63bb38e29..8d324bc8413b 100644 --- a/src/Security/Authentication/OAuth/src/ClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/ClaimAction.cs @@ -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 @@ public ClaimAction(string claimType, string valueType) /// The source data to examine. This value may be null. /// The identity to add Claims to. /// The value to use for Claim.Issuer when creating a Claim. - public abstract void Run(JObject userData, ClaimsIdentity identity, string issuer); + public abstract void Run(JsonElement userData, ClaimsIdentity identity, string issuer); } } diff --git a/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs b/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs index 5a178957a09a..f123785691f1 100644 --- a/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs +++ b/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs @@ -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 @@ public static void MapJsonSubKey(this ClaimActionCollection collection, string c /// /// The value to use for Claim.Type when creating a Claim. /// The Func that will be called to select value from the given json user data. - public static void MapCustomJson(this ClaimActionCollection collection, string claimType, Func resolver) + public static void MapCustomJson(this ClaimActionCollection collection, string claimType, Func resolver) { collection.MapCustomJson(claimType, ClaimValueTypes.String, resolver); } @@ -82,7 +82,7 @@ public static void MapCustomJson(this ClaimActionCollection collection, string c /// The value to use for Claim.Type when creating a Claim. /// The value to use for Claim.ValueType when creating a Claim. /// The Func that will be called to select value from the given json user data. - public static void MapCustomJson(this ClaimActionCollection collection, string claimType, string valueType, Func resolver) + public static void MapCustomJson(this ClaimActionCollection collection, string claimType, string valueType, Func resolver) { collection.Add(new CustomJsonClaimAction(claimType, valueType, resolver)); } diff --git a/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs b/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs index 21a4f70e12ec..ee307a97ae89 100644 --- a/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs @@ -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 @@ public class CustomJsonClaimAction : ClaimAction /// The value to use for Claim.Type when creating a Claim. /// The value to use for Claim.ValueType when creating a Claim. /// The Func that will be called to select value from the given json user data. - public CustomJsonClaimAction(string claimType, string valueType, Func resolver) + public CustomJsonClaimAction(string claimType, string valueType, Func resolver) : base(claimType, valueType) { Resolver = resolver; @@ -27,15 +27,11 @@ public CustomJsonClaimAction(string claimType, string valueType, Func /// The Func that will be called to select value from the given json user data. /// - public Func Resolver { get; } + public Func Resolver { get; } /// - 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)) { diff --git a/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs b/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs index 75167cabcb7f..e12454bffad6 100644 --- a/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs @@ -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 @@ public DeleteClaimAction(string claimType) } /// - 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()) { diff --git a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs index f660dd22476d..f534fac2f18c 100644 --- a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs +++ b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs @@ -5,8 +5,8 @@ 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 /// public class OAuthCreatingTicketContext : ResultContext { - /// - /// Initializes a new . - /// - /// The . - /// The . - /// The HTTP environment. - /// The authentication scheme. - /// The options used by the authentication middleware. - /// The HTTP client used by the authentication middleware - /// The tokens returned from the token endpoint. - 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()) - { } - /// /// Initializes a new . /// @@ -55,7 +34,7 @@ public OAuthCreatingTicketContext( OAuthOptions options, HttpClient backchannel, OAuthTokenResponse tokens, - JObject user) + JsonElement user) : base(context, scheme, options) { if (backchannel == null) @@ -68,11 +47,6 @@ public OAuthCreatingTicketContext( throw new ArgumentNullException(nameof(tokens)); } - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - TokenResponse = tokens; Backchannel = backchannel; User = user; @@ -82,9 +56,9 @@ public OAuthCreatingTicketContext( /// /// Gets the JSON-serialized user or an empty - /// if it is not available. + /// if it is not available. /// - public JObject User { get; } + public JsonElement User { get; } /// /// Gets the token response returned by the authentication service. @@ -136,17 +110,12 @@ public TimeSpan? ExpiresIn 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); } } } -} \ No newline at end of file +} diff --git a/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs b/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs index ccd1a965dc87..464bcb6e225e 100644 --- a/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs @@ -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 @@ public JsonKeyClaimAction(string claimType, string valueType, string jsonKey) public string JsonKey { get; } /// - 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) diff --git a/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs b/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs index bc29672d0fad..4d3b134d44f0 100644 --- a/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs @@ -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 @@ public JsonSubKeyClaimAction(string claimType, string valueType, string jsonKey, public string SubKey { get; } /// - 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 @@ public override void Run(JObject userData, ClaimsIdentity identity, string issue } // 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; } diff --git a/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs b/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs index b3bf5d99f15c..46c6f0cb7668 100644 --- a/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs +++ b/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs @@ -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 @@ public MapAllClaimsAction() : base("All", ClaimValueTypes.String) { } - 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)); } } } diff --git a/src/Security/Authentication/OAuth/src/Microsoft.AspNetCore.Authentication.OAuth.csproj b/src/Security/Authentication/OAuth/src/Microsoft.AspNetCore.Authentication.OAuth.csproj index 575f78c15cbb..85623aebce29 100644 --- a/src/Security/Authentication/OAuth/src/Microsoft.AspNetCore.Authentication.OAuth.csproj +++ b/src/Security/Authentication/OAuth/src/Microsoft.AspNetCore.Authentication.OAuth.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core middleware that enables an application to support any standard OAuth 2.0 authentication workflow. @@ -10,7 +10,6 @@ - diff --git a/src/Security/Authentication/OAuth/src/OAuthHandler.cs b/src/Security/Authentication/OAuth/src/OAuthHandler.cs index a84f8a485ad6..0d2cf140663a 100644 --- a/src/Security/Authentication/OAuth/src/OAuthHandler.cs +++ b/src/Security/Authentication/OAuth/src/OAuthHandler.cs @@ -9,13 +9,13 @@ 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 @@ protected override async Task HandleRemoteAuthenticateAsync return HandleRequestResult.Fail("Code was not found.", properties); } - var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath)); - - if (tokens.Error != null) - { - 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) + using (var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath))) { - var authTokens = new List(); - - 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(); + + authTokens.Add(new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken }); + if (!string.IsNullOrEmpty(tokens.RefreshToken)) + { + 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)) { - // 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 + int value; + if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) { - Name = "expires_at", - Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) - }); + // 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 @@ protected virtual async Task ExchangeCodeAsync(string code, 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 @@ private static async Task Display(HttpResponseMessage response) protected virtual async Task 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) diff --git a/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs b/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs index aa4026b009eb..15eaf71eb14b 100644 --- a/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs +++ b/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs @@ -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("access_token"); - TokenType = response.Value("token_type"); - RefreshToken = response.Value("refresh_token"); - ExpiresIn = response.Value("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 @@ private OAuthTokenResponse(Exception error) Error = error; } - public static OAuthTokenResponse Success(JObject response) + public static OAuthTokenResponse Success(JsonDocument response) { return new OAuthTokenResponse(response); } @@ -32,11 +33,16 @@ public static OAuthTokenResponse Failed(Exception error) 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; } } -} \ No newline at end of file +} diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json @@ -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/" + } + } +} diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Properties/launchSettings.json b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Properties/launchSettings.json @@ -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/" + } + } +} diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs index a2b5a9a5cad9..e47a1c86293a 100644 --- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs @@ -5,6 +5,7 @@ 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.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Newtonsoft.Json.Linq; namespace OpenIdConnectSample { @@ -208,30 +208,31 @@ await WriteHtmlAsync(response, async res => 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("access_token")); - props.UpdateTokenValue("refresh_token", payload.Value("refresh_token")); - if (int.TryParse(payload.Value("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)); - } - await context.SignInAsync(user, props); + // 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($"

Refreshed.

"); - await res.WriteAsync("Refresh tokens"); - await res.WriteAsync("Home"); + await WriteHtmlAsync(response, async res => + { + await res.WriteAsync($"

Refreshed.

"); + await res.WriteAsync("Refresh tokens"); + await res.WriteAsync("Home"); - await res.WriteAsync("

Tokens:

"); - await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value })); + await res.WriteAsync("

Tokens:

"); + await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value })); - await res.WriteAsync("

Payload:

"); - await res.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",
") + "
"); - }); + await res.WriteAsync("

Payload:

"); + await res.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",
") + "
"); + }); + } return; } diff --git a/src/Security/Authentication/OpenIdConnect/src/Events/UserInformationReceivedContext.cs b/src/Security/Authentication/OpenIdConnect/src/Events/UserInformationReceivedContext.cs index 0b855eaf3930..29deb9acbe94 100644 --- a/src/Security/Authentication/OpenIdConnect/src/Events/UserInformationReceivedContext.cs +++ b/src/Security/Authentication/OpenIdConnect/src/Events/UserInformationReceivedContext.cs @@ -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 @@ public UserInformationReceivedContext(HttpContext context, AuthenticationScheme public OpenIdConnectMessage ProtocolMessage { get; set; } - public JObject User { get; set; } + public JsonDocument User { get; set; } } } diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs index 982376387307..8348b9832a1a 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs @@ -12,6 +12,7 @@ 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.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 @@ protected override async Task HandleRemoteAuthenticateAsync } 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 @@ protected virtual async Task GetUserInformationAsync( 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) - { - return userInformationReceivedContext.Result; - } - principal = userInformationReceivedContext.Principal; - properties = userInformationReceivedContext.Properties; - user = userInformationReceivedContext.User; - - Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext() + using (user) { - UserInfoEndpointResponse = userInfoResponse, - ValidatedIdToken = jwt, - }); + 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, + }); - 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 @@ private async Task RunTokenResponseReceivedEventAs return context; } - private async Task RunUserInformationReceivedEventAsync(ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage message, JObject user) + private async Task RunUserInformationReceivedEventAsync(ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage message, JsonDocument user) { Logger.UserInformationReceived(user.ToString()); diff --git a/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs b/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs index 132885b3caf3..762146b72ca3 100644 --- a/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs +++ b/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs @@ -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 @@ public UniqueJsonKeyClaimAction(string claimType, string valueType, string jsonK } /// - public override void Run(JObject userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) { - var value = userData?.Value(JsonKey); + var value = userData.GetString(JsonKey); if (string.IsNullOrEmpty(value)) { // Not found diff --git a/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs b/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs index 67f28d5297af..b95abef0b098 100644 --- a/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs +++ b/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs @@ -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 @@ public TwitterCreatingTicketContext( 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 @@ public TwitterCreatingTicketContext( /// /// Gets the JSON-serialized user or an empty - /// if it is not available. + /// if it is not available. /// - public JObject User { get; } + public JsonElement User { get; } } } diff --git a/src/Security/Authentication/Twitter/src/TwitterHandler.cs b/src/Security/Authentication/Twitter/src/TwitterHandler.cs index 01f80dc72f9f..e963d7a27e88 100644 --- a/src/Security/Authentication/Twitter/src/TwitterHandler.cs +++ b/src/Security/Authentication/Twitter/src/TwitterHandler.cs @@ -9,13 +9,13 @@ 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 @@ protected override async Task HandleRemoteAuthenticateAsync }, ClaimsIssuer); - JObject user = null; + JsonDocument user; if (Options.RetrieveUserDetails) { user = await RetrieveUserDetailsAsync(accessToken, identity); } + else + { + user = JsonDocument.Parse("{}"); + } - if (Options.SaveTokens) + using (user) { - properties.StoreTokens(new [] { + if (Options.SaveTokens) + { + properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = accessToken.Token }, new AuthenticationToken { Name = "access_token_secret", Value = accessToken.TokenSecret } - }); - } + }); + } - return HandleRequestResult.Success(await CreateTicketAsync(identity, properties, accessToken, user)); + var ticket = await CreateTicketAsync(identity, properties, accessToken, user.RootElement); + return HandleRequestResult.Success(ticket); + } } protected virtual async Task 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 @@ private async Task ObtainAccessTokenAsync(RequestToken token, strin } // https://dev.twitter.com/rest/reference/get/account/verify_credentials - private async Task RetrieveUserDetailsAsync(AccessToken accessToken, ClaimsIdentity identity) + private async Task RetrieveUserDetailsAsync(AccessToken accessToken, ClaimsIdentity identity) { Logger.RetrieveUserDetails(); @@ -288,7 +296,7 @@ private async Task RetrieveUserDetailsAsync(AccessToken accessToken, Cl } var responseText = await response.Content.ReadAsStringAsync(); - var result = JObject.Parse(responseText); + var result = JsonDocument.Parse(responseText); return result; } diff --git a/src/Security/Authentication/WsFederation/samples/WsFedSample/Properties/launchSettings.json b/src/Security/Authentication/WsFederation/samples/WsFedSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/WsFederation/samples/WsFedSample/Properties/launchSettings.json @@ -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/" + } + } +} diff --git a/src/Security/Authentication/samples/SocialSample/Properties/launchSettings.json b/src/Security/Authentication/samples/SocialSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/samples/SocialSample/Properties/launchSettings.json @@ -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/" + } + } +} diff --git a/src/Security/Authentication/samples/SocialSample/Startup.cs b/src/Security/Authentication/samples/SocialSample/Startup.cs index b5032072bd6b..3921ab5f36a8 100644 --- a/src/Security/Authentication/samples/SocialSample/Startup.cs +++ b/src/Security/Authentication/samples/SocialSample/Startup.cs @@ -6,6 +6,7 @@ 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.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Newtonsoft.Json.Linq; namespace SocialSample { @@ -207,9 +207,10 @@ public void ConfigureServices(IServiceCollection services) 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 @@ public void Configure(IApplicationBuilder app) 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("access_token")); - refreshToken = payload.Value("refresh_token"); - if (!string.IsNullOrEmpty(refreshToken)) - { - authProperties.UpdateTokenValue("refresh_token", refreshToken); - } - if (int.TryParse(payload.Value("expires_in"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds)) + using (var payload = JsonDocument.Parse(await refreshResponse.Content.ReadAsStringAsync())) { - 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 @@ public void Configure(IApplicationBuilder app) }.ToQueryString(); var refreshResponse = await options.Backchannel.GetStringAsync(options.TokenEndpoint + query); - var payload = JObject.Parse(refreshResponse); - - authProperties.UpdateTokenValue("access_token", payload.Value("access_token")); - if (int.TryParse(payload.Value("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 @@ private Task GetOAuthOptionsAsync(HttpContext context, string curr 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(""); await response.WriteAsync("Refreshed.
"); - await response.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",
") + "
"); + await response.WriteAsync(HtmlEncoder.Default.Encode(payload.RootElement.ToString()).Replace(",", ",
") + "
"); await response.WriteAsync("
Tokens:
"); diff --git a/src/Security/Authentication/samples/SocialSample/web.config b/src/Security/Authentication/samples/SocialSample/web.config index f7ac679334bc..e149af83da85 100644 --- a/src/Security/Authentication/samples/SocialSample/web.config +++ b/src/Security/Authentication/samples/SocialSample/web.config @@ -4,6 +4,8 @@ - + + + \ No newline at end of file diff --git a/src/Security/Authentication/test/ClaimActionTests.cs b/src/Security/Authentication/test/ClaimActionTests.cs index b083e9d76dbe..1ebe08dd7bd3 100644 --- a/src/Security/Authentication/test/ClaimActionTests.cs +++ b/src/Security/Authentication/test/ClaimActionTests.cs @@ -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 @@ public class ClaimActionTests [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 @@ public void CanMapSingleValueUserDataToClaim() [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 @@ public void CanMapArrayValueUserDataToClaims() [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 @@ public void MapAllSucceeds() [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 @@ public void MapAllAllowesDulicateKeysWithUniqueValues() [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")); diff --git a/src/Security/Authentication/test/FacebookTests.cs b/src/Security/Authentication/test/FacebookTests.cs index 964af2ce7285..bd50a86de683 100644 --- a/src/Security/Authentication/test/FacebookTests.cs +++ b/src/Security/Authentication/test/FacebookTests.cs @@ -10,7 +10,6 @@ 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 @@ public async Task CustomUserInfoEndpointHasValidGraphQuery() 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 @@ public async Task CustomUserInfoEndpointHasValidGraphQuery() { 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; } diff --git a/src/Security/Authentication/test/GoogleTests.cs b/src/Security/Authentication/test/GoogleTests.cs index 6559c1aacc0e..8d5e635fd085 100644 --- a/src/Security/Authentication/test/GoogleTests.cs +++ b/src/Security/Authentication/test/GoogleTests.cs @@ -9,7 +9,6 @@ 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 @@ public async Task ValidateAuthenticatedContext() { 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 @@ private HttpMessageHandler CreateBackchannel() 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; } diff --git a/src/Security/Authentication/test/MicrosoftAccountTests.cs b/src/Security/Authentication/test/MicrosoftAccountTests.cs index 4005b50efeca..c13f4bf51b01 100644 --- a/src/Security/Authentication/test/MicrosoftAccountTests.cs +++ b/src/Security/Authentication/test/MicrosoftAccountTests.cs @@ -9,7 +9,6 @@ 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 @@ private static TestServer CreateServer(Action configure 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; }