Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cffa395
Basic Apple provider
martincostello Jun 6, 2019
5c3d7cd
Implement Apple provider
martincostello Jun 8, 2019
f78fa43
Enable Sign In with Apple
martincostello Jun 8, 2019
7ba0afb
Update tests
martincostello Jun 8, 2019
7a24782
Enable token lifetime validation
martincostello Jun 8, 2019
c0fc31c
Add null annotations
martincostello Jun 9, 2019
cd8ed33
Improve exception handling
martincostello Jun 9, 2019
9bc3817
Extend integration tests
martincostello Jun 9, 2019
404730e
Move expiry period to options
martincostello Jun 9, 2019
8b11fe7
Add ClientSecretExpiresAfter validation
martincostello Jun 9, 2019
c7b2f74
Add tests for options validation
martincostello Jun 9, 2019
05a8102
Add unit tests for client secret
martincostello Jun 9, 2019
9948d08
Make KeyId required
martincostello Jun 9, 2019
5e72f38
Fix test
martincostello Jun 9, 2019
2d4794a
Fix Linux and macOS secret generation
martincostello Jun 9, 2019
13012dd
Add password option for pfx files
martincostello Jun 9, 2019
b9f329a
Fix flaky test
martincostello Jun 9, 2019
210fbf9
Add UsePrivateKey() method
martincostello Jun 9, 2019
94f1737
Bump System.IdentityModel.Tokens.Jwt
martincostello Jun 9, 2019
67dc9a3
Set response_mode to form_post
martincostello Sep 8, 2019
eae3d43
Use latest C# version
martincostello Sep 8, 2019
4a93eb6
Retrieve user details after sign-in
martincostello Sep 8, 2019
74f607a
Update branding
martincostello Sep 15, 2019
dbbf2db
Access events via options
martincostello Sep 15, 2019
785a01a
Resolve logging TODO
martincostello Sep 15, 2019
eb5ccfd
Comment out Apple option
martincostello Sep 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Extend integration tests
Extend the integration tests for additional scenarios such as no validation, invalid tokens and using a configured client secret.
  • Loading branch information
martincostello committed Sep 8, 2019
commit 9bc3817a627a1a3ca59329391d743f38d227246a
275 changes: 261 additions & 14 deletions test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using Shouldly;
using Xunit;
using Xunit.Abstractions;

Expand All @@ -34,26 +35,82 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu
{
ConfigureDefaults(builder, options);
options.ClientId = "com.martincostello.signinwithapple.test.client";
options.ClientSecret = string.Empty;
options.GenerateClientSecret = true;
options.KeyId = "my-key-id";
options.TeamId = "my-team-id";
options.ValidateTokens = true;
options.PrivateKeyBytes = (keyId) =>
{
Assert.Equal("my-key-id", keyId);
string privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU208KCg/doqiSzsVF5sknVtYSgt8/3oiYGbvryIRrzSgCgYIKoZIzj0DAQehRANCAAQfrvDWizEnWAzB2Hx2r/NyvIBO6KGBDL7wkZoKnz4Sm4+1P1dhD9fVEhbsdoq9RKEf8dvzTOZMaC/iLqZFKSN6";
return Task.FromResult(Convert.FromBase64String(privateKey));
};
});
}

[Theory]
[InlineData(ClaimTypes.NameIdentifier, "001883.fcc77ba97500402389df96821ad9c790.1517")]
public async Task Can_Sign_In_Using_Apple(string claimType, string claimValue)
public async Task Can_Sign_In_Using_Apple_With_Client_Secret(string claimType, string claimValue)
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<JwtSecurityTokenHandler, FrozenJwtSecurityTokenHandler>();
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.GenerateClientSecret = false;
options.ClientSecret = "my-client-secret";
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var claims = await AuthenticateUserAsync(server);

// Assert
AssertClaim(claims, claimType, claimValue);
}
}

[Theory]
[InlineData(ClaimTypes.NameIdentifier, "001883.fcc77ba97500402389df96821ad9c790.1517")]
public async Task Can_Sign_In_Using_Apple_With_Private_Key(string claimType, string claimValue)
{
// Arrange
using (var server = CreateTestServer((services) => services.AddSingleton<JwtSecurityTokenHandler, FrozenJwtSecurityTokenHandler>()))
void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<JwtSecurityTokenHandler, FrozenJwtSecurityTokenHandler>();
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.ClientSecret = string.Empty;
options.GenerateClientSecret = true;
options.KeyId = "my-key-id";
options.TeamId = "my-team-id";
options.ValidateTokens = true;
options.PrivateKeyBytes = (keyId) =>
{
Assert.Equal("my-key-id", keyId);
string privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU208KCg/doqiSzsVF5sknVtYSgt8/3oiYGbvryIRrzSgCgYIKoZIzj0DAQehRANCAAQfrvDWizEnWAzB2Hx2r/NyvIBO6KGBDL7wkZoKnz4Sm4+1P1dhD9fVEhbsdoq9RKEf8dvzTOZMaC/iLqZFKSN6";
return Task.FromResult(Convert.FromBase64String(privateKey));
};
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var claims = await AuthenticateUserAsync(server);

// Assert
AssertClaim(claims, claimType, claimValue);
}
}

[Theory]
[InlineData(ClaimTypes.NameIdentifier, "001883.fcc77ba97500402389df96821ad9c790.1517")]
public async Task Can_Sign_In_Using_Apple_With_No_Token_Validation(string claimType, string claimValue)
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.ValidateTokens = false;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var claims = await AuthenticateUserAsync(server);
Expand All @@ -63,11 +120,201 @@ public async Task Can_Sign_In_Using_Apple(string claimType, string claimValue)
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Apple_With_Expired_Token()
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.ValidateTokens = true;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var exception = await Assert.ThrowsAsync<Exception>(() => AuthenticateUserAsync(server));

// Assert
exception.InnerException.ShouldBeOfType<SecurityTokenExpiredException>();
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Apple_With_Invalid_Token_Audience()
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<JwtSecurityTokenHandler, FrozenJwtSecurityTokenHandler>();
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.ClientId = "my-team";
options.ValidateTokens = true;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var exception = await Assert.ThrowsAsync<Exception>(() => AuthenticateUserAsync(server));

// Assert
exception.InnerException.ShouldBeOfType<SecurityTokenInvalidAudienceException>();
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Apple_With_Invalid_Token_Issuer()
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<JwtSecurityTokenHandler, FrozenJwtSecurityTokenHandler>();
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.TokenAudience = "https://apple.local";
options.ValidateTokens = true;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var exception = await Assert.ThrowsAsync<Exception>(() => AuthenticateUserAsync(server));

// Assert
exception.InnerException.ShouldBeOfType<SecurityTokenInvalidIssuerException>();
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Apple_With_Invalid_Signing_Key()
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<JwtSecurityTokenHandler, FrozenJwtSecurityTokenHandler>();
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.PublicKeyEndpoint = "https://appleid.apple.local/auth/keys/invalid";
options.ValidateTokens = true;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var exception = await Assert.ThrowsAsync<Exception>(() => AuthenticateUserAsync(server));

// Assert
exception.InnerException.ShouldBeOfType<SecurityTokenInvalidSignatureException>();
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Apple_With_Unknown_Signing_Key()
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<JwtSecurityTokenHandler, FrozenJwtSecurityTokenHandler>();
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.PublicKeyEndpoint = "https://appleid.apple.local/auth/keys/none";
options.ValidateTokens = true;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var exception = await Assert.ThrowsAsync<Exception>(() => AuthenticateUserAsync(server));

// Assert
exception.InnerException.ShouldBeOfType<SecurityTokenSignatureKeyNotFoundException>();
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Apple_With_Null_Token()
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.TokenEndpoint = "https://appleid.apple.local/auth/token/null";
options.ValidateTokens = true;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var exception = await Assert.ThrowsAsync<Exception>(() => AuthenticateUserAsync(server));

// Assert
exception.InnerException.ShouldBeOfType<InvalidOperationException>();
exception.InnerException.Message.ShouldBe("No Apple ID token was returned in the OAuth token response.");
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Apple_With_Malformed_Token()
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.TokenEndpoint = "https://appleid.apple.local/auth/token/malformed";
options.ValidateTokens = true;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var exception = await Assert.ThrowsAsync<Exception>(() => AuthenticateUserAsync(server));

// Assert
exception.InnerException.ShouldBeOfType<ArgumentException>();
exception.InnerException.Message.ShouldStartWith("IDX");
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Apple_With_No_Token()
{
// Arrange
void ConfigureServices(IServiceCollection services)
{
services.PostConfigureAll<AppleAuthenticationOptions>((options) =>
{
options.TokenEndpoint = "https://appleid.apple.local/auth/token/none";
options.ValidateTokens = true;
});
}

using (var server = CreateTestServer(ConfigureServices))
{
// Act
var exception = await Assert.ThrowsAsync<Exception>(() => AuthenticateUserAsync(server));

// Assert
exception.InnerException.ShouldBeOfType<InvalidOperationException>();
exception.InnerException.Message.ShouldBe("No Apple ID token was returned in the OAuth token response.");
}
}

private sealed class FrozenJwtSecurityTokenHandler : JwtSecurityTokenHandler
{
protected override void ValidateLifetime(DateTime? notBefore, DateTime? expires, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
// Do not validate the lifetime as the token is old
// Do not validate the lifetime as the test token has expired
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/Apple/bundle.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,67 @@
"refresh_token": "secret-refresh-token",
"token_type": "bearer"
}
},
{
"uri": "https://appleid.apple.local/auth/keys/none",
"method": "GET",
"contentFormat": "json",
"contentJson": {
"keys": []
}
},
{
"uri": "https://appleid.apple.local/auth/keys/invalid",
"method": "GET",
"contentFormat": "json",
"contentJson": {
"keys": [
{
"kty": "RSA",
"kid": "AIDOPK1",
"use": "sig",
"alg": "RS256",
"n": "lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJmwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaZ_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w",
"e": "AQAB"
}
]
}
},
{
"uri": "https://appleid.apple.local/auth/token/null",
"method": "POST",
"contentFormat": "json",
"contentJson": {
"access_token": "secret-access-token",
"expires_in": "0",
"id_token": null,
"refresh_token": "secret-refresh-token",
"token_type": "bearer"
}
},
{
"uri": "https://appleid.apple.local/auth/token/none",
"method": "POST",
"contentFormat": "json",
"contentJson": {
"access_token": "secret-access-token",
"expires_in": "0",
"id_token": "",
"refresh_token": "secret-refresh-token",
"token_type": "bearer"
}
},
{
"uri": "https://appleid.apple.local/auth/token/malformed",
"method": "POST",
"contentFormat": "json",
"contentJson": {
"access_token": "secret-access-token",
"expires_in": "0",
"id_token": "1",
"refresh_token": "secret-refresh-token",
"token_type": "bearer"
}
}
]
}