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

Commit 73ae996

Browse files
committed
#969 Make social auth claims mapping more configurable
1 parent cbceba6 commit 73ae996

File tree

12 files changed

+228
-121
lines changed

12 files changed

+228
-121
lines changed

samples/SocialSample/Startup.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
112112

113113
// You must first create an app with GitHub and add its ID and Secret to your user-secrets.
114114
// https://console.developers.google.com/project
115-
app.UseGoogleAuthentication(new GoogleOptions
115+
var googleOptions = new GoogleOptions
116116
{
117117
ClientId = Configuration["google:clientid"],
118118
ClientSecret = Configuration["google:clientsecret"],
@@ -126,7 +126,10 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
126126
return Task.FromResult(0);
127127
}
128128
}
129-
});
129+
};
130+
googleOptions.ClaimResolvers.AddNested("urn:google:image", "image", "url");
131+
googleOptions.ClaimResolvers.Remove(ClaimTypes.GivenName);
132+
app.UseGoogleAuthentication(googleOptions);
130133

131134
// You must first create an app with Twitter and add its key and Secret to your user-secrets.
132135
// https://apps.twitter.com/
@@ -357,7 +360,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
357360
}
358361

359362
await context.Response.WriteAsync("Tokens:<br>");
360-
363+
361364
await context.Response.WriteAsync("Access Token: " + await context.Authentication.GetTokenAsync("access_token") + "<br>");
362365
await context.Response.WriteAsync("Refresh Token: " + await context.Authentication.GetTokenAsync("refresh_token") + "<br>");
363366
await context.Response.WriteAsync("Token Type: " + await context.Authentication.GetTokenAsync("token_type") + "<br>");

src/Microsoft.AspNetCore.Authentication.Google/GoogleHandler.cs

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -43,40 +43,9 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
4343
var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);
4444
var context = new OAuthCreatingTicketContext(ticket, Context, Options, Backchannel, tokens, payload);
4545

46-
var identifier = GoogleHelper.GetId(payload);
47-
if (!string.IsNullOrEmpty(identifier))
46+
foreach (var map in Options.ClaimResolvers)
4847
{
49-
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, Options.ClaimsIssuer));
50-
}
51-
52-
var givenName = GoogleHelper.GetGivenName(payload);
53-
if (!string.IsNullOrEmpty(givenName))
54-
{
55-
identity.AddClaim(new Claim(ClaimTypes.GivenName, givenName, ClaimValueTypes.String, Options.ClaimsIssuer));
56-
}
57-
58-
var familyName = GoogleHelper.GetFamilyName(payload);
59-
if (!string.IsNullOrEmpty(familyName))
60-
{
61-
identity.AddClaim(new Claim(ClaimTypes.Surname, familyName, ClaimValueTypes.String, Options.ClaimsIssuer));
62-
}
63-
64-
var name = GoogleHelper.GetName(payload);
65-
if (!string.IsNullOrEmpty(name))
66-
{
67-
identity.AddClaim(new Claim(ClaimTypes.Name, name, ClaimValueTypes.String, Options.ClaimsIssuer));
68-
}
69-
70-
var email = GoogleHelper.GetEmail(payload);
71-
if (!string.IsNullOrEmpty(email))
72-
{
73-
identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer));
74-
}
75-
76-
var profile = GoogleHelper.GetProfile(payload);
77-
if (!string.IsNullOrEmpty(profile))
78-
{
79-
identity.AddClaim(new Claim("urn:google:profile", profile, ClaimValueTypes.String, Options.ClaimsIssuer));
48+
map.Apply(payload, identity, Options.ClaimsIssuer);
8049
}
8150

8251
await Options.Events.CreatingTicket(context);

src/Microsoft.AspNetCore.Authentication.Google/GoogleHelper.cs

Lines changed: 0 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -12,71 +12,6 @@ namespace Microsoft.AspNetCore.Authentication.Google
1212
/// </summary>
1313
public static class GoogleHelper
1414
{
15-
/// <summary>
16-
/// Gets the Google user ID.
17-
/// </summary>
18-
public static string GetId(JObject user)
19-
{
20-
if (user == null)
21-
{
22-
throw new ArgumentNullException(nameof(user));
23-
}
24-
25-
return user.Value<string>("id");
26-
}
27-
28-
/// <summary>
29-
/// Gets the user's name.
30-
/// </summary>
31-
public static string GetName(JObject user)
32-
{
33-
if (user == null)
34-
{
35-
throw new ArgumentNullException(nameof(user));
36-
}
37-
38-
return user.Value<string>("displayName");
39-
}
40-
41-
/// <summary>
42-
/// Gets the user's given name.
43-
/// </summary>
44-
public static string GetGivenName(JObject user)
45-
{
46-
if (user == null)
47-
{
48-
throw new ArgumentNullException(nameof(user));
49-
}
50-
51-
return TryGetValue(user, "name", "givenName");
52-
}
53-
54-
/// <summary>
55-
/// Gets the user's family name.
56-
/// </summary>
57-
public static string GetFamilyName(JObject user)
58-
{
59-
if (user == null)
60-
{
61-
throw new ArgumentNullException(nameof(user));
62-
}
63-
64-
return TryGetValue(user, "name", "familyName");
65-
}
66-
67-
/// <summary>
68-
/// Gets the user's profile link.
69-
/// </summary>
70-
public static string GetProfile(JObject user)
71-
{
72-
if (user == null)
73-
{
74-
throw new ArgumentNullException(nameof(user));
75-
}
76-
77-
return user.Value<string>("url");
78-
}
79-
8015
/// <summary>
8116
/// Gets the user's email.
8217
/// </summary>
@@ -90,21 +25,6 @@ public static string GetEmail(JObject user)
9025
return TryGetFirstValue(user, "emails", "value");
9126
}
9227

93-
// Get the given subProperty from a property.
94-
private static string TryGetValue(JObject user, string propertyName, string subProperty)
95-
{
96-
JToken value;
97-
if (user.TryGetValue(propertyName, out value))
98-
{
99-
var subObject = JObject.Parse(value.ToString());
100-
if (subObject != null && subObject.TryGetValue(subProperty, out value))
101-
{
102-
return value.ToString();
103-
}
104-
}
105-
return null;
106-
}
107-
10828
// Get the given subProperty from a list property.
10929
private static string TryGetFirstValue(JObject user, string propertyName, string subProperty)
11030
{

src/Microsoft.AspNetCore.Authentication.Google/GoogleOptions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System.Security.Claims;
5+
using Microsoft.AspNetCore.Authentication;
46
using Microsoft.AspNetCore.Authentication.Google;
57
using Microsoft.AspNetCore.Http;
68

@@ -25,6 +27,13 @@ public GoogleOptions()
2527
Scope.Add("openid");
2628
Scope.Add("profile");
2729
Scope.Add("email");
30+
31+
ClaimResolvers.Add(ClaimTypes.NameIdentifier, "id");
32+
ClaimResolvers.Add(ClaimTypes.Name, "displayName");
33+
ClaimResolvers.AddNested(ClaimTypes.GivenName, "name", "givenName");
34+
ClaimResolvers.AddNested(ClaimTypes.Surname, "name", "familyName");
35+
ClaimResolvers.Add("urn:google:profile", "url");
36+
ClaimResolvers.AddCustom(ClaimTypes.Email, GoogleHelper.GetEmail);
2837
}
2938

3039
/// <summary>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Security.Claims;
6+
using Newtonsoft.Json.Linq;
7+
8+
namespace Microsoft.AspNetCore.Authentication.OAuth
9+
{
10+
public class CustomJsonClaimResolver : ClaimResolver<JObject>
11+
{
12+
public CustomJsonClaimResolver(string claimName, string claimType, Func<JObject, string> resolver)
13+
: base(claimName, claimType)
14+
{
15+
Resolver = resolver;
16+
}
17+
18+
public Func<JObject, string> Resolver { get; set; }
19+
20+
public override void Apply(JObject data, ClaimsIdentity identity, string issuer)
21+
{
22+
var value = Resolver(data);
23+
if (!string.IsNullOrEmpty(value))
24+
{
25+
identity.AddClaim(new Claim(ClaimName, value, ClaimType, issuer));
26+
}
27+
}
28+
}
29+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Security.Claims;
5+
using Newtonsoft.Json.Linq;
6+
7+
namespace Microsoft.AspNetCore.Authentication.OAuth
8+
{
9+
public class JsonClaimResolver : ClaimResolver<JObject>
10+
{
11+
public JsonClaimResolver(string claimName, string claimType, string jsonKey)
12+
: base(claimName, claimType)
13+
{
14+
JsonKey = jsonKey;
15+
}
16+
17+
public string JsonKey { get; }
18+
19+
public override void Apply(JObject data, ClaimsIdentity identity, string issuer)
20+
{
21+
var value = data.Value<string>(JsonKey);
22+
if (!string.IsNullOrEmpty(value))
23+
{
24+
identity.AddClaim(new Claim(ClaimName, value, ClaimType, issuer));
25+
}
26+
}
27+
}
28+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Security.Claims;
6+
using Microsoft.AspNetCore.Authentication.OAuth;
7+
using Newtonsoft.Json.Linq;
8+
9+
namespace Microsoft.AspNetCore.Authentication
10+
{
11+
public static class JsonClaimResolverCollectionExtensions
12+
{
13+
public static void Add(this ClaimResolverCollection<JObject> collection, string claimName, string jsonKey)
14+
{
15+
collection.Add(claimName, jsonKey, ClaimValueTypes.String);
16+
}
17+
18+
public static void Add(this ClaimResolverCollection<JObject> collection, string claimName, string jsonKey, string claimType)
19+
{
20+
collection.Add(new JsonClaimResolver(claimName, claimType, jsonKey));
21+
}
22+
23+
public static void AddNested(this ClaimResolverCollection<JObject> collection, string claimName, string jsonKey, string subKey)
24+
{
25+
collection.AddNested(claimName, jsonKey, subKey, ClaimValueTypes.String);
26+
}
27+
28+
public static void AddNested(this ClaimResolverCollection<JObject> collection, string claimName, string jsonKey, string subKey, string claimType)
29+
{
30+
collection.Add(new NestedJsonClaimResolver(claimName, claimType, jsonKey, subKey));
31+
}
32+
33+
public static void AddCustom(this ClaimResolverCollection<JObject> collection, string claimName, Func<JObject, string> resolver)
34+
{
35+
collection.AddCustom(claimName, ClaimValueTypes.String, resolver);
36+
}
37+
38+
public static void AddCustom(this ClaimResolverCollection<JObject> collection, string claimName, string claimType, Func<JObject, string> resolver)
39+
{
40+
collection.Add(new CustomJsonClaimResolver(claimName, claimType, resolver));
41+
}
42+
}
43+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Newtonsoft.Json.Linq;
5+
using System.Security.Claims;
6+
7+
namespace Microsoft.AspNetCore.Authentication.OAuth
8+
{
9+
public class NestedJsonClaimResolver : JsonClaimResolver
10+
{
11+
public NestedJsonClaimResolver(string claimName, string claimType, string jsonKey, string subKey)
12+
: base(claimName, claimType, jsonKey)
13+
{
14+
SubKey = subKey;
15+
}
16+
17+
public string SubKey { get; }
18+
19+
public override void Apply(JObject data, ClaimsIdentity identity, string issuer)
20+
{
21+
var value = GetValue(data, JsonKey, SubKey);
22+
if (!string.IsNullOrEmpty(value))
23+
{
24+
identity.AddClaim(new Claim(ClaimName, value, ClaimType, issuer));
25+
}
26+
}
27+
28+
// Get the given subProperty from a property.
29+
private static string GetValue(JObject user, string propertyName, string subProperty)
30+
{
31+
JToken value;
32+
if (user.TryGetValue(propertyName, out value))
33+
{
34+
var subObject = JObject.Parse(value.ToString());
35+
if (subObject != null && subObject.TryGetValue(subProperty, out value))
36+
{
37+
return value.ToString();
38+
}
39+
}
40+
return null;
41+
}
42+
}
43+
}

src/Microsoft.AspNetCore.Authentication.OAuth/OAuthOptions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.AspNetCore.Authentication;
77
using Microsoft.AspNetCore.Authentication.OAuth;
88
using Microsoft.AspNetCore.Http.Authentication;
9+
using Newtonsoft.Json.Linq;
910

1011
namespace Microsoft.AspNetCore.Builder
1112
{
@@ -55,6 +56,8 @@ public OAuthOptions()
5556
set { base.Events = value; }
5657
}
5758

59+
public ClaimResolverCollection<JObject> ClaimResolvers { get; } = new ClaimResolverCollection<JObject>();
60+
5861
/// <summary>
5962
/// Gets the list of permissions to request.
6063
/// </summary>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Security.Claims;
5+
6+
namespace Microsoft.AspNetCore.Authentication
7+
{
8+
public abstract class ClaimResolver<T>
9+
{
10+
public ClaimResolver(string claimName, string claimType)
11+
{
12+
ClaimName = claimName;
13+
ClaimType = claimType;
14+
}
15+
16+
public string ClaimName { get; }
17+
18+
public string ClaimType { get; }
19+
20+
public abstract void Apply(T data, ClaimsIdentity identity, string issuer);
21+
}
22+
}

0 commit comments

Comments
 (0)