Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support HS256 signed keys #45

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ public async Task GetDlcsRolesForCode_PassesIdTokenToTokenHandler()

// Assert
A.CallTo(() => jwtTokenHandler.GetClaimsFromToken(idToken, jwksUri, "https://dlcs-dev.uk.auth0.com/", "test-id",
A<CancellationToken>._))
.MustHaveHappened();
"test-secret", A<CancellationToken>._)).MustHaveHappened();
}

[Fact]
Expand All @@ -172,7 +171,7 @@ public async Task GetDlcsRolesForCode_ReturnsEmptyRoles_IfNoMappedClaims()
ClaimType = claimType,
};
A.CallTo(() => jwtTokenHandler.GetClaimsFromToken(idToken, A<Uri>._, A<string>._, A<string>._,
A<CancellationToken>._))
A<string>._, A<CancellationToken>._))
.Returns(new ClaimsPrincipal(new ClaimsIdentity()));

var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
Expand Down Expand Up @@ -207,7 +206,7 @@ public async Task GetDlcsRolesForCode_ReturnsMappedClaims()
}
};
A.CallTo(() => jwtTokenHandler.GetClaimsFromToken(idToken, A<Uri>._, A<string>._, A<string>._,
A<CancellationToken>._))
A<string>._, A<CancellationToken>._))
.Returns(new ClaimsPrincipal(new ClaimsIdentity(new []{new Claim(claimType, "foobar")})));

var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public async Task<IReadOnlyCollection<string>> GetDlcsRolesForCode(OidcConfigura

var claimsPrincipal =
await jwtTokenHandler.GetClaimsFromToken(auth0Token.IdToken, GetJwksUri(oidcConfiguration),
issuer, audience, cancellationToken);
issuer, audience, oidcConfiguration.ClientSecret, cancellationToken);
if (claimsPrincipal == null) return Array.Empty<string>();

var dlcsRoles = claimsConverter.GetDlcsRolesFromClaims(claimsPrincipal, oidcConfiguration);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using IIIFAuth2.API.Settings;
using LazyCache;
using Microsoft.Extensions.Caching.Memory;
Expand All @@ -17,10 +18,11 @@ public interface IJwtTokenHandler
/// <param name="jwksUri">Path where jwks can be found</param>
/// <param name="issuer">Valid "iss" value</param>
/// <param name="audience">Valid "aud" value</param>
/// <param name="clientSecret">ClientSecret, if known. Used for symmetric validation</param>
/// <param name="cancellationToken">Current cancellation token</param>
/// <returns><see cref="ClaimsPrincipal"/> if jwt is valid, else null</returns>
Task<ClaimsPrincipal?> GetClaimsFromToken(string jwtToken, Uri jwksUri, string issuer, string audience,
CancellationToken cancellationToken);
string? clientSecret, CancellationToken cancellationToken);
}

public class JwtTokenHandler : IJwtTokenHandler
Expand All @@ -41,17 +43,16 @@ public JwtTokenHandler(HttpClient httpClient, IAppCache appCache, IOptions<AuthS

/// <inheritdoc />
public async Task<ClaimsPrincipal?> GetClaimsFromToken(string jwtToken, Uri jwksUri, string issuer,
string audience, CancellationToken cancellationToken)
string audience, string? clientSecret, CancellationToken cancellationToken)
{
try
{
var jwks = await GetWebKeySetForDomain(jwksUri, cancellationToken);

var issuerSigningKeys = await GetSigningKeys(jwksUri, clientSecret, cancellationToken);
var tokenHandler = new JwtSecurityTokenHandler();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = jwks.GetSigningKeys(),
IssuerSigningKeys = issuerSigningKeys,
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
Expand All @@ -75,6 +76,22 @@ public JwtTokenHandler(HttpClient httpClient, IAppCache appCache, IOptions<AuthS
return null;
}

private async Task<IList<SecurityKey>> GetSigningKeys(Uri jwksUri, string? clientSecret, CancellationToken
cancellationToken)
{
// jwks used for "alg": "RS256"
var jwks = await GetWebKeySetForDomain(jwksUri, cancellationToken);
var issuerSigningKeys = jwks.GetSigningKeys();

if (!string.IsNullOrWhiteSpace(clientSecret))
{
// client-secret for "alg": "HS256"
issuerSigningKeys.Add(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(clientSecret)));
}

return issuerSigningKeys;
}

private async Task<JsonWebKeySet> GetWebKeySetForDomain(Uri jwksPath, CancellationToken cancellationToken)
{
var cacheKey = $"{jwksPath}:jwks";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ private async Task EnsureSecrets(OidcConfiguration configuration, Guid accessSer
var secretsJson = await secretsManagerCache.GetSecretString(secretName);
var secret = JsonSerializer.Deserialize<Secrets>(secretsJson, Options);
configuration.ClientSecret = secret?.ClientSecret ?? string.Empty;

if (string.IsNullOrWhiteSpace(configuration.ClientSecret))
{
logger.LogWarning("Fetched secretsmanager secret {SecretName} for {AccessService} but got no value",
secretName, accessServiceId);
}
}
catch (Exception ex)
{
Expand Down