Skip to content

Commit

Permalink
fix: signing validate jwks instead a single jwk
Browse files Browse the repository at this point in the history
  • Loading branch information
brunobritodev committed Jun 13, 2023
1 parent 2bdd8b7 commit e756c37
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 11 deletions.
4 changes: 3 additions & 1 deletion samples/1_AspNet.Default/AspNet.Default.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
<ProjectReference Include="..\..\src\NetDevPack.Security.Jwt.Core\NetDevPack.Security.Jwt.Core.csproj" />
<ProjectReference Include="..\..\src\NetDevPack.Security.Jwt.Store.EntityFrameworkCore\NetDevPack.Security.Jwt.Store.EntityFrameworkCore.csproj" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="NetDevPack.Security.Jwt.AspNetCoreTests" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NetDevPack.Security.Jwt.Core.Interfaces;

namespace NetDevPack.Security.Jwt.AspNetCore;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ public override ClaimsPrincipal ValidateToken(string token, TokenValidationParam
//JwtSecurityToken incomingToken = ReadJwtToken(token);

//Retrieve the corresponding Public Key from our data store
var keyMaterialTask = jwtService.GetCurrentSecurityKey();
var keyMaterialTask = jwtService.GetLastKeys();
Task.WaitAll(keyMaterialTask);
validationParameters.IssuerSigningKey = keyMaterialTask.Result;
validationParameters.IssuerSigningKeys = keyMaterialTask.Result.Select(s => s.GetSecurityKey());

//And let the framework take it from here.
//var handler = new JsonWebTokenHandler();
Expand Down
3 changes: 2 additions & 1 deletion src/NetDevPack.Security.Jwt.Core/Interfaces/IJwtService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public interface IJwtService
Task<SecurityKey> GetCurrentSecurityKey();
Task<SigningCredentials> GetCurrentSigningCredentials();
Task<EncryptingCredentials> GetCurrentEncryptingCredentials();
Task<ReadOnlyCollection<KeyMaterial>> GetLastKeys(int i);
Task<ReadOnlyCollection<KeyMaterial>> GetLastKeys(int? i = null);
Task RevokeKey(string keyId, string reason = null);
}
[Obsolete("Deprecate, use IJwtServiceInstead")]
public interface IJsonWebKeySetService : IJwtService{}
13 changes: 9 additions & 4 deletions src/NetDevPack.Security.Jwt.Core/Jwt/JwtService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,11 @@ public async Task<EncryptingCredentials> GetCurrentEncryptingCredentials()
return new EncryptingCredentials(current, _options.Value.Jwe.Alg, _options.Value.Jwe.EncryptionAlgorithmContent);
}

public Task<ReadOnlyCollection<KeyMaterial>> GetLastKeys(int i)
public Task<ReadOnlyCollection<KeyMaterial>> GetLastKeys(int? i = null)
{
return _store.GetLastKeys(5);
return _store.GetLastKeys(_options.Value.AlgorithmsToKeep);
}


private async Task<bool> CheckCompatibility(KeyMaterial currentKey)
{
if (currentKey.Type != _options.Value.Jws.Kty())
Expand All @@ -74,9 +73,15 @@ private async Task<bool> CheckCompatibility(KeyMaterial currentKey)
return true;
}

public async Task RevokeKey(string keyId, string reason = null)
{
var key = await _store.Get(keyId);

await _store.Revoke(key, reason);
}
private bool NeedsUpdate(KeyMaterial current)
{
return current == null || current.IsExpired(_options.Value.DaysUntilExpire);
return current == null || current.IsExpired(_options.Value.DaysUntilExpire) || current.IsRevoked;
}


Expand Down
9 changes: 8 additions & 1 deletion src/NetDevPack.Security.Jwt.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.IdentityServer4", ".
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity", "..\samples\Microservice.Sample\Identity\Identity.csproj", "{B96A31FB-827A-43C8-AC4B-BAC66AB528CB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.Sample", "..\samples\Microservice.Sample\Api.Sample\Api.Sample.csproj", "{F7877CF4-6ADF-456F-BC27-B1BB935366FD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Api.Sample", "..\samples\Microservice.Sample\Api.Sample\Api.Sample.csproj", "{F7877CF4-6ADF-456F-BC27-B1BB935366FD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDevPack.Security.Jwt.AspNetCoreTests", "..\tests\NetDevPack.Security.Jwt.AspNetCoreTests\NetDevPack.Security.Jwt.AspNetCoreTests.csproj", "{5BC8EC00-578F-41F8-ACB8-D95D3F0EA589}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -81,6 +83,10 @@ Global
{F7877CF4-6ADF-456F-BC27-B1BB935366FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7877CF4-6ADF-456F-BC27-B1BB935366FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7877CF4-6ADF-456F-BC27-B1BB935366FD}.Release|Any CPU.Build.0 = Release|Any CPU
{5BC8EC00-578F-41F8-ACB8-D95D3F0EA589}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BC8EC00-578F-41F8-ACB8-D95D3F0EA589}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BC8EC00-578F-41F8-ACB8-D95D3F0EA589}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BC8EC00-578F-41F8-ACB8-D95D3F0EA589}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -93,6 +99,7 @@ Global
{7EE1F124-DD0E-452D-8B13-D108FF9DF277} = {BF65DEED-7A2D-44FF-B1F5-9F317FE93ADC}
{B96A31FB-827A-43C8-AC4B-BAC66AB528CB} = {1E9A6B9E-DC4A-4B17-A252-3578BC5BE853}
{F7877CF4-6ADF-456F-BC27-B1BB935366FD} = {1E9A6B9E-DC4A-4B17-A252-3578BC5BE853}
{5BC8EC00-578F-41F8-ACB8-D95D3F0EA589} = {CEA99B61-BEF7-4CE5-A68A-0784625A6B43}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {82CA17A3-4071-4E08-A07A-A6083C804774}
Expand Down
87 changes: 87 additions & 0 deletions tests/NetDevPack.Security.Jwt.AspNetCoreTests/JwtTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using NetDevPack.Security.Jwt.Core.Interfaces;
using System.Security.Claims;
using AspNet.Default;
using FluentAssertions;
using System.Net.Http.Headers;

namespace NetDevPack.Security.Jwt.AspNetCoreTests;

public class JwtTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;

// Top level statement Program.cs problem.
class FakeApplication : WebApplicationFactory<Program>
{
}
public JwtTests()
{
_factory = new FakeApplication();
}

[Fact]
public async Task Should_Validate_Jws()
{
// Arrange
using var scope = _factory.Services.CreateScope();
var scopedServices = scope.ServiceProvider;
var jwtService = scopedServices.GetRequiredService<IJwtService>();
var customClaims = FakeClaims.GenerateClaim().Generate(5);
var currentKey = await jwtService.GetCurrentSigningCredentials();
var jws = CreateJws(currentKey, customClaims);

var client = _factory.CreateClient();
var response = await client.GetAsync($"validate-jws/{jws}");

response.IsSuccessStatusCode.Should().BeTrue();

var claims = await System.Text.Json.JsonSerializer.DeserializeAsync<Dictionary<string, object>>(await response.Content.ReadAsStreamAsync());
claims.Should().Contain(a => a.Key == customClaims.First().Type);
}

[Fact]
public async Task Should_Validate_Jws_With_A_Revoked_Key()
{
// Arrange
using var scope = _factory.Services.CreateScope();
var scopedServices = scope.ServiceProvider;
var jwtService = scopedServices.GetRequiredService<IJwtService>();
var customClaims = FakeClaims.GenerateClaim().Generate(5);
var currentKey = await jwtService.GetCurrentSigningCredentials();
var jws = CreateJws(currentKey, customClaims);

await jwtService.RevokeKey(currentKey.Key.KeyId);


var client = _factory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost/protected-endpoint");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jws);
var response = await client.SendAsync(request);

response.IsSuccessStatusCode.Should().BeTrue();
}


private static string CreateJws(SigningCredentials key, List<Claim> claims)
{
var handler = new JsonWebTokenHandler();
var now = DateTime.Now;
var descriptor = new SecurityTokenDescriptor
{
Issuer = "https://www.devstore.academy", // <- Your website
Audience = "NetDevPack.Security.Jwt.AspNet",
IssuedAt = now,
NotBefore = now,
Expires = now.AddMinutes(60),
Subject = new ClaimsIdentity(claims),
SigningCredentials = key
};

return handler.CreateToken(descriptor);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\samples\1_AspNet.Default\AspNet.Default.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions tests/NetDevPack.Security.Jwt.AspNetCoreTests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
11 changes: 11 additions & 0 deletions tests/NetDevPack.Security.Jwt.Tests/JwtTests/JwtServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ public async Task ShouldGenerateDefaultSigning()
current.Kid.Should().Be(sign.KeyId);
}

[Fact]
public async Task ShouldExpireCurrentAndGenerateNewToSignAndValidateJws()
{
var current = await _jwksService.GetCurrentSigningCredentials();

await _jwksService.GenerateKey();
var newCurrent = await _jwksService.GetCurrentSigningCredentials();

current.Kid.Should().NotBe(newCurrent.Kid);
}

[Fact]
public async Task ShouldNotThrowExceptionWhenGetSignManyTimes()
{
Expand Down

0 comments on commit e756c37

Please sign in to comment.