From 8bc36e6a6af48e14a49eb329978d11bc28178668 Mon Sep 17 00:00:00 2001 From: Donald Gray Date: Mon, 12 Aug 2024 17:18:42 +0100 Subject: [PATCH] Support HS256 signed keys --- .../RoleProvisioning/Oidc/Auth0ClientTests.cs | 7 +++-- .../Auth/RoleProvisioning/Oidc/Auth0Client.cs | 2 +- .../RoleProvisioning/Oidc/JwtTokenParser.cs | 27 +++++++++++++++---- .../Oidc/OidcRoleProviderHandler.cs | 6 +++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/IIIFAuth2/IIIFAuth2.API.Tests/Infrastructure/Auth/RoleProvisioning/Oidc/Auth0ClientTests.cs b/src/IIIFAuth2/IIIFAuth2.API.Tests/Infrastructure/Auth/RoleProvisioning/Oidc/Auth0ClientTests.cs index 924b177..f7d3e51 100644 --- a/src/IIIFAuth2/IIIFAuth2.API.Tests/Infrastructure/Auth/RoleProvisioning/Oidc/Auth0ClientTests.cs +++ b/src/IIIFAuth2/IIIFAuth2.API.Tests/Infrastructure/Auth/RoleProvisioning/Oidc/Auth0ClientTests.cs @@ -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._)) - .MustHaveHappened(); + "test-secret", A._)).MustHaveHappened(); } [Fact] @@ -172,7 +171,7 @@ public async Task GetDlcsRolesForCode_ReturnsEmptyRoles_IfNoMappedClaims() ClaimType = claimType, }; A.CallTo(() => jwtTokenHandler.GetClaimsFromToken(idToken, A._, A._, A._, - A._)) + A._, A._)) .Returns(new ClaimsPrincipal(new ClaimsIdentity())); var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK); @@ -207,7 +206,7 @@ public async Task GetDlcsRolesForCode_ReturnsMappedClaims() } }; A.CallTo(() => jwtTokenHandler.GetClaimsFromToken(idToken, A._, A._, A._, - A._)) + A._, A._)) .Returns(new ClaimsPrincipal(new ClaimsIdentity(new []{new Claim(claimType, "foobar")}))); var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK); diff --git a/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/Auth0Client.cs b/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/Auth0Client.cs index 674e8cd..eddee1d 100644 --- a/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/Auth0Client.cs +++ b/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/Auth0Client.cs @@ -87,7 +87,7 @@ public async Task> 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(); var dlcsRoles = claimsConverter.GetDlcsRolesFromClaims(claimsPrincipal, oidcConfiguration); diff --git a/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/JwtTokenParser.cs b/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/JwtTokenParser.cs index 9006a67..9705c51 100644 --- a/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/JwtTokenParser.cs +++ b/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/JwtTokenParser.cs @@ -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; @@ -17,10 +18,11 @@ public interface IJwtTokenHandler /// Path where jwks can be found /// Valid "iss" value /// Valid "aud" value + /// ClientSecret, if known. Used for symmetric validation /// Current cancellation token /// if jwt is valid, else null Task GetClaimsFromToken(string jwtToken, Uri jwksUri, string issuer, string audience, - CancellationToken cancellationToken); + string? clientSecret, CancellationToken cancellationToken); } public class JwtTokenHandler : IJwtTokenHandler @@ -41,17 +43,16 @@ public JwtTokenHandler(HttpClient httpClient, IAppCache appCache, IOptions public async Task 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, @@ -75,6 +76,22 @@ public JwtTokenHandler(HttpClient httpClient, IAppCache appCache, IOptions> 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 GetWebKeySetForDomain(Uri jwksPath, CancellationToken cancellationToken) { var cacheKey = $"{jwksPath}:jwks"; diff --git a/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/OidcRoleProviderHandler.cs b/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/OidcRoleProviderHandler.cs index f74c499..84beaf9 100644 --- a/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/OidcRoleProviderHandler.cs +++ b/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/Oidc/OidcRoleProviderHandler.cs @@ -105,6 +105,12 @@ private async Task EnsureSecrets(OidcConfiguration configuration, Guid accessSer var secretsJson = await secretsManagerCache.GetSecretString(secretName); var secret = JsonSerializer.Deserialize(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) {