Skip to content

Commit 87c3383

Browse files
authored
Ensure Apple email claim is set for .NET Core 2 (#412)
* Ensure Apple email claim is set for .NET Core 2 * Removed C# 8 code.
1 parent 07dcbac commit 87c3383

File tree

4 files changed

+97
-1
lines changed

4 files changed

+97
-1
lines changed

src/AspNet.Security.OAuth.Apple/AppleAuthenticationOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public AppleAuthenticationOptions()
3030

3131
Scope.Add("name");
3232
Scope.Add("email");
33+
34+
// Add a custom claim action that maps the email claim from the ID token if
35+
// it was not otherwise provided in the user endpoint response.
36+
// See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues/407
37+
ClaimActions.Add(new AppleEmailClaimAction(this));
3338
}
3439

3540
/// <summary>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using System;
8+
using System.Security.Claims;
9+
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
10+
using Newtonsoft.Json.Linq;
11+
12+
namespace AspNet.Security.OAuth.Apple
13+
{
14+
internal sealed class AppleEmailClaimAction : ClaimAction
15+
{
16+
private readonly AppleAuthenticationOptions _options;
17+
18+
internal AppleEmailClaimAction(AppleAuthenticationOptions options)
19+
: base(ClaimTypes.Email, ClaimValueTypes.String)
20+
{
21+
_options = options;
22+
}
23+
24+
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
25+
{
26+
if (!identity.HasClaim((p) => string.Equals(p.Type, ClaimType, StringComparison.OrdinalIgnoreCase)))
27+
{
28+
var emailClaim = identity.FindFirst("email");
29+
30+
if (!string.IsNullOrEmpty(emailClaim?.Value))
31+
{
32+
identity.AddClaim(new Claim(ClaimType, emailClaim.Value, ValueType, _options.ClaimsIssuer));
33+
}
34+
}
35+
}
36+
}
37+
}

test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleTests.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public AppleTests(ITestOutputHelper outputHelper)
3131

3232
protected override HttpMethod RedirectMethod => HttpMethod.Post;
3333

34-
protected override IDictionary<string, string> RedirectParameters => new Dictionary<string, string>()
34+
protected override IDictionary<string, string> RedirectParameters { get; } = new Dictionary<string, string>()
3535
{
3636
["user"] = @"{""name"":{""firstName"":""Johnny"",""lastName"":""Appleseed""},""email"":""[email protected]""}",
3737
};
@@ -112,6 +112,47 @@ void ConfigureServices(IServiceCollection services)
112112
}
113113
}
114114

115+
[Theory]
116+
[InlineData("at_hash", "eOy0y7XVexdkzc7uuDZiCQ")]
117+
[InlineData("aud", "com.martincostello.signinwithapple.test.client")]
118+
[InlineData("auth_time", "1587211556")]
119+
[InlineData("email", "[email protected]")]
120+
[InlineData("email_verified", "true")]
121+
[InlineData("exp", "1587212159")]
122+
[InlineData("iat", "1587211559")]
123+
[InlineData("iss", "https://appleid.apple.com")]
124+
[InlineData("is_private_email", "true")]
125+
[InlineData("nonce_supported", "true")]
126+
[InlineData("sub", "001883.fcc77ba97500402389df96821ad9c790.1517")]
127+
[InlineData(ClaimTypes.Email, "[email protected]")]
128+
[InlineData(ClaimTypes.NameIdentifier, "001883.fcc77ba97500402389df96821ad9c790.1517")]
129+
public async Task Can_Sign_In_Using_Apple_And_Receive_Claims_From_Id_Token(string claimType, string claimValue)
130+
{
131+
// Arrange
132+
void ConfigureServices(IServiceCollection services)
133+
{
134+
services.AddSingleton<JwtSecurityTokenHandler, FrozenJwtSecurityTokenHandler>();
135+
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
136+
{
137+
options.ClientSecret = "my-client-secret";
138+
options.GenerateClientSecret = false;
139+
options.TokenEndpoint = "https://appleid.apple.local/auth/token/email";
140+
options.ValidateTokens = false;
141+
});
142+
}
143+
144+
RedirectParameters.Clear(); // Simulate second sign in where user data is not returned
145+
146+
using (var server = CreateTestServer(ConfigureServices))
147+
{
148+
// Act
149+
var claims = await AuthenticateUserAsync(server);
150+
151+
// Assert
152+
AssertClaim(claims, claimType, claimValue);
153+
}
154+
}
155+
115156
[Theory]
116157
[InlineData(ClaimTypes.Email, "[email protected]")]
117158
[InlineData(ClaimTypes.GivenName, "Johnny")]

test/AspNet.Security.OAuth.Providers.Tests/Apple/bundle.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@
3232
"token_type": "bearer"
3333
}
3434
},
35+
{
36+
"comment": "https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens",
37+
"uri": "https://appleid.apple.local/auth/token/email",
38+
"method": "POST",
39+
"contentFormat": "json",
40+
"contentJson": {
41+
"access_token": "secret-access-token",
42+
"expires_in": "300",
43+
"id_token": "eyJraWQiOiI4NkQ4OEtmIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLm1hcnRpbmNvc3RlbGxvLnNpZ25pbndpdGhhcHBsZS50ZXN0LmNsaWVudCIsImV4cCI6MTU4NzIxMjE1OSwiaWF0IjoxNTg3MjExNTU5LCJzdWIiOiIwMDE4ODMuZmNjNzdiYTk3NTAwNDAyMzg5ZGY5NjgyMWFkOWM3OTAuMTUxNyIsImF0X2hhc2giOiJlT3kweTdYVmV4ZGt6Yzd1dURaaUNRIiwiZW1haWwiOiJ1c3Nja2VmdXo2QHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImlzX3ByaXZhdGVfZW1haWwiOiJ0cnVlIiwiYXV0aF90aW1lIjoxNTg3MjExNTU2LCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.ZPUgcJlCneXLNZiFDraKpWVtFPSyoxkWgrMlTZ8tM3IBBXOmQFbb75OBQC-JbZHciry96y-sy33O_fF8gaudmInH1EorDIsfryafNd0POD-8pJWY9PiGrGx50c_1DLIIIsYEm0p-JEIfQpzJ-lIWpz9ujv4ChmZx-t3PzPzzZOVlC0q1pATqJaxhY_ntL_u98BZnfAKxzqEhb5q-1TmhtHFaEtAtsd2gGm6PTaM5N-2HXQ8Bh_BlJMH3u_KakFNJRhaezlVIlLtmgxM4VjrxUeIqba-fwBlfGXPonA_xZIHg71ZujJSlYJp3yWW3Kjsb4rUUUff7yEQF5A1LVnghwA",
44+
"refresh_token": "secret-refresh-token",
45+
"token_type": "bearer"
46+
}
47+
},
3548
{
3649
"uri": "https://appleid.apple.local/auth/keys/none",
3750
"method": "GET",

0 commit comments

Comments
 (0)