diff --git a/samples/OpenIdConnectSample/Startup.cs b/samples/OpenIdConnectSample/Startup.cs
index c05bc8b522..82bfdf54f1 100644
--- a/samples/OpenIdConnectSample/Startup.cs
+++ b/samples/OpenIdConnectSample/Startup.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Text.Encodings.Web;
@@ -44,6 +45,8 @@ namespace OpenIdConnectSample
public void ConfigureServices(IServiceCollection services)
{
+ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
+
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
@@ -56,9 +59,13 @@ namespace OpenIdConnectSample
o.ClientId = Configuration["oidc:clientid"];
o.ClientSecret = Configuration["oidc:clientsecret"]; // for code flow
o.Authority = Configuration["oidc:authority"];
+
o.ResponseType = OpenIdConnectResponseType.CodeIdToken;
o.SaveTokens = true;
o.GetClaimsFromUserInfoEndpoint = true;
+
+ o.ClaimActions.MapAllExcept("aud", "iss", "iat", "nbf", "exp", "aio", "c_hash", "uti", "nonce");
+
o.Events = new OpenIdConnectEvents()
{
OnAuthenticationFailed = c =>
diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/Claims/ClaimActionCollectionMapExtensions.cs b/src/Microsoft.AspNetCore.Authentication.OAuth/Claims/ClaimActionCollectionMapExtensions.cs
index f3fee6a229..5a178957a0 100644
--- a/src/Microsoft.AspNetCore.Authentication.OAuth/Claims/ClaimActionCollectionMapExtensions.cs
+++ b/src/Microsoft.AspNetCore.Authentication.OAuth/Claims/ClaimActionCollectionMapExtensions.cs
@@ -87,6 +87,27 @@ namespace Microsoft.AspNetCore.Authentication
collection.Add(new CustomJsonClaimAction(claimType, valueType, resolver));
}
+ ///
+ /// Clears any current ClaimsActions and maps all values from the json user data as claims, excluding duplicates.
+ ///
+ ///
+ public static void MapAll(this ClaimActionCollection collection)
+ {
+ collection.Clear();
+ collection.Add(new MapAllClaimsAction());
+ }
+
+ ///
+ /// Clears any current ClaimsActions and maps all values from the json user data as claims, excluding the specified types.
+ ///
+ ///
+ ///
+ public static void MapAllExcept(this ClaimActionCollection collection, params string[] exclusions)
+ {
+ collection.MapAll();
+ collection.DeleteClaims(exclusions);
+ }
+
///
/// Delete all claims from the given ClaimsIdentity with the given ClaimType.
///
@@ -96,5 +117,23 @@ namespace Microsoft.AspNetCore.Authentication
{
collection.Add(new DeleteClaimAction(claimType));
}
+
+ ///
+ /// Delete all claims from the ClaimsIdentity with the given claimTypes.
+ ///
+ ///
+ ///
+ public static void DeleteClaims(this ClaimActionCollection collection, params string[] claimTypes)
+ {
+ if (claimTypes == null)
+ {
+ throw new ArgumentNullException(nameof(claimTypes));
+ }
+
+ foreach (var claimType in claimTypes)
+ {
+ collection.Add(new DeleteClaimAction(claimType));
+ }
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Authentication.OAuth/Claims/MapAllClaimsAction.cs b/src/Microsoft.AspNetCore.Authentication.OAuth/Claims/MapAllClaimsAction.cs
new file mode 100644
index 0000000000..b3bf5d99f1
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.OAuth/Claims/MapAllClaimsAction.cs
@@ -0,0 +1,42 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
+{
+ ///
+ /// A ClaimAction that selects all top level values from the json user data and adds them as Claims.
+ /// This excludes duplicate sets of names and values.
+ ///
+ public class MapAllClaimsAction : ClaimAction
+ {
+ public MapAllClaimsAction() : base("All", ClaimValueTypes.String)
+ {
+ }
+
+ public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
+ {
+ if (userData == null)
+ {
+ return;
+ }
+ foreach (var pair in userData)
+ {
+ var claimValue = userData.TryGetValue(pair.Key, out var value) ? value.ToString() : null;
+
+ // 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)
+ && string.Equals(c.Value, claimValue, StringComparison.Ordinal)) != null;
+
+ if (!duplicate)
+ {
+ identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, issuer));
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Authentication.Test/ClaimActionTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/ClaimActionTests.cs
index 541e1edf28..b083e9d76d 100644
--- a/test/Microsoft.AspNetCore.Authentication.Test/ClaimActionTests.cs
+++ b/test/Microsoft.AspNetCore.Authentication.Test/ClaimActionTests.cs
@@ -51,5 +51,62 @@ namespace Microsoft.AspNetCore.Authentication
Assert.Equal("role", roleClaims[1].Type);
Assert.Equal("role2", roleClaims[1].Value);
}
+
+ [Fact]
+ public void MapAllSucceeds()
+ {
+ var userData = new JObject
+ {
+ ["name0"] = "value0",
+ ["name1"] = "value1",
+ };
+
+ var identity = new ClaimsIdentity();
+ var action = new MapAllClaimsAction();
+ action.Run(userData, identity, "iss");
+
+ Assert.Equal("name0", identity.FindFirst("name0").Type);
+ Assert.Equal("value0", identity.FindFirst("name0").Value);
+ Assert.Equal("name1", identity.FindFirst("name1").Type);
+ Assert.Equal("value1", identity.FindFirst("name1").Value);
+ }
+
+ [Fact]
+ public void MapAllAllowesDulicateKeysWithUniqueValues()
+ {
+ var userData = new JObject
+ {
+ ["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");
+
+ Assert.Equal(2, identity.FindAll("name0").Count());
+ Assert.Equal(2, identity.FindAll("name1").Count());
+ }
+
+ [Fact]
+ public void MapAllSkipsDuplicateValues()
+ {
+ var userData = new JObject
+ {
+ ["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");
+
+ Assert.Single(identity.FindAll("name0"));
+ Assert.Single(identity.FindAll("name1"));
+ }
}
}