Skip to content

Commit

Permalink
Ensure backchannel is used with the IdTokenValidator (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
frederikprijck authored Feb 14, 2023
1 parent b287df8 commit 7470353
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 13 deletions.
6 changes: 4 additions & 2 deletions src/Auth0.OidcClient.Core/Auth0ClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace Auth0.OidcClient
public abstract class Auth0ClientBase : IAuth0Client
{
private readonly IdTokenRequirements _idTokenRequirements;
private readonly IdTokenValidator _idTokenValidator;
private readonly Auth0ClientOptions _options;
private readonly string _userAgent;
private IdentityModel.OidcClient.OidcClient _oidcClient;
Expand All @@ -43,6 +44,7 @@ protected Auth0ClientBase(Auth0ClientOptions options, string platformName)
_options = options;
_idTokenRequirements = new IdTokenRequirements($"https://{_options.Domain}/", _options.ClientId, options.Leeway, options.MaxAge);
_userAgent = CreateAgentString(platformName);
_idTokenValidator = new IdTokenValidator(options.BackchannelHandler);
}

/// <inheritdoc />
Expand All @@ -65,7 +67,7 @@ public async Task<LoginResult> LoginAsync(object extraParameters = null, Cancell
_idTokenRequirements.Organization = finalExtraParameters["organization"];
}

await IdTokenValidator.AssertTokenMeetsRequirements(_idTokenRequirements, result.IdentityToken); // Nonce is created & tested by OidcClient
await _idTokenValidator.AssertTokenMeetsRequirements(_idTokenRequirements, result.IdentityToken); // Nonce is created & tested by OidcClient
}

return result;
Expand Down Expand Up @@ -121,7 +123,7 @@ public async Task<RefreshTokenResult> RefreshTokenAsync(string refreshToken, obj
_idTokenRequirements.Organization = finalExtraParameters["Organization"];
}

await IdTokenValidator.AssertTokenMeetsRequirements(_idTokenRequirements, result.IdentityToken); // Nonce is created & tested by OidcClient
await _idTokenValidator.AssertTokenMeetsRequirements(_idTokenRequirements, result.IdentityToken); // Nonce is created & tested by OidcClient
}

return result;
Expand Down
13 changes: 10 additions & 3 deletions src/Auth0.OidcClient.Core/Tokens/AsymmetricSignatureVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Threading.Tasks;

namespace Auth0.OidcClient.Tokens
{
internal class AsymmetricSignatureVerifier : ISignatureVerifier
{
private readonly IList<JsonWebKey> keys;
private readonly JsonWebKeys jsonWebKeys;

public static async Task<AsymmetricSignatureVerifier> ForJwks(string issuer)
public AsymmetricSignatureVerifier(HttpMessageHandler backchannel = null)
{
var jsonWebKeys = await JsonWebKeys.GetForIssuer(issuer);
return new AsymmetricSignatureVerifier(jsonWebKeys.Keys);
jsonWebKeys = new JsonWebKeys(backchannel);
}

public async Task<AsymmetricSignatureVerifier> ForJwks(string issuer)
{
var jsonWebKeysSet = await jsonWebKeys.GetForIssuer(issuer);
return new AsymmetricSignatureVerifier(jsonWebKeysSet.Keys);
}

public AsymmetricSignatureVerifier(IList<JsonWebKey> keys)
Expand Down
14 changes: 11 additions & 3 deletions src/Auth0.OidcClient.Core/Tokens/IdTokenValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;

Expand All @@ -12,8 +13,15 @@ namespace Auth0.OidcClient.Tokens
/// <summary>
/// Perform validation of a JWT ID token in compliance with specified <see cref="IdTokenRequirements"/>.
/// </summary>
internal static class IdTokenValidator
internal class IdTokenValidator
{
private readonly AsymmetricSignatureVerifier assymetricSignatureVerifier;

public IdTokenValidator(HttpMessageHandler backchannel = null)
{
assymetricSignatureVerifier = new AsymmetricSignatureVerifier(backchannel);
}

/// <summary>
/// Assert that all the <see cref="IdTokenRequirements"/> are met by a JWT ID token for a given point in time.
/// </summary>
Expand All @@ -26,7 +34,7 @@ internal static class IdTokenValidator
/// meet the requirements specified by <paramref name="required"/>.
/// </exception>
/// <returns><see cref="Task"/> that will complete when the token is validated.</returns>
internal static async Task AssertTokenMeetsRequirements(this IdTokenRequirements required, string rawIDToken, DateTime? pointInTime = null, ISignatureVerifier signatureVerifier = null)
internal async Task AssertTokenMeetsRequirements(IdTokenRequirements required, string rawIDToken, DateTime? pointInTime = null, ISignatureVerifier signatureVerifier = null)
{
if (string.IsNullOrWhiteSpace(rawIDToken))
throw new IdTokenValidationException("ID token is required but missing.");
Expand All @@ -36,7 +44,7 @@ internal static async Task AssertTokenMeetsRequirements(this IdTokenRequirements
// For now we want to support HS256 + ClientSecret as we just had a major release.
// TODO: In the next major (v4.0) we should remove this condition as well as Auth0ClientOptions.ClientSecret
if (token.SignatureAlgorithm != "HS256")
(signatureVerifier ?? await AsymmetricSignatureVerifier.ForJwks(required.Issuer)).VerifySignature(rawIDToken);
(signatureVerifier ?? await assymetricSignatureVerifier.ForJwks(required.Issuer)).VerifySignature(rawIDToken);

AssertTokenClaimsMeetRequirements(required, token, pointInTime ?? DateTime.Now);
}
Expand Down
16 changes: 12 additions & 4 deletions src/Auth0.OidcClient.Core/Tokens/JsonWebKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,32 @@
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Auth0.OidcClient.Tokens
{
static class JsonWebKeys
class JsonWebKeys
{
public static async Task<JsonWebKeySet> GetForIssuer(string issuer)
private readonly HttpMessageHandler backchannel;

public JsonWebKeys(HttpMessageHandler backchannel = null)
{
this.backchannel = backchannel;
}

public async Task<JsonWebKeySet> GetForIssuer(string issuer)
{
var metadataAddress = new UriBuilder(issuer) { Path = "/.well-known/openid-configuration" }.Uri.OriginalString;
var openIdConfiguration = await GetOpenIdConfiguration(metadataAddress);
return openIdConfiguration.JsonWebKeySet;
}

private static Task<OpenIdConnectConfiguration> GetOpenIdConfiguration(string metadataAddress)
private Task<OpenIdConnectConfiguration> GetOpenIdConfiguration(string metadataAddress)
{
try
{
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataAddress, new OpenIdConnectConfigurationRetriever());
var configurationManager = backchannel == null ? new ConfigurationManager<OpenIdConnectConfiguration>(metadataAddress, new OpenIdConnectConfigurationRetriever()) : new ConfigurationManager<OpenIdConnectConfiguration>(metadataAddress, new OpenIdConnectConfigurationRetriever(), new System.Net.Http.HttpClient(backchannel));
return configurationManager.GetConfigurationAsync();
}
catch (Exception e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.18.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Auth0.OidcClient.Core\Auth0.OidcClient.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Moq" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
using Auth0.OidcClient.Tokens;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Moq;
using Moq.Protected;
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -28,7 +35,7 @@ public class IdTokenValidatorUnitTests

private Task ValidateToken(string token, IdTokenRequirements reqs = null, DateTime? when = null, ISignatureVerifier signatureVerifier = null)
{
return IdTokenValidator.AssertTokenMeetsRequirements(reqs ?? defaultReqs, token, when ?? tokensWereValid, signatureVerifier ?? rs256NoSignature);
return new OidcClient.Tokens.IdTokenValidator().AssertTokenMeetsRequirements(reqs ?? defaultReqs, token, when ?? tokensWereValid, signatureVerifier ?? rs256NoSignature);
}

[Fact]
Expand Down Expand Up @@ -294,5 +301,33 @@ public async void DoesNotThrowWhenOrgIdAvailableButNotARequirement()

await ValidateToken(token);
}

[Fact]
public async void UsesTheBackchannelWhenProvidedToFetchTheOpenIdConfiguration()
{
var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1qQXlNamczTWpFMVF6WXhNamhGUkVKR09FRkVSRGMzTlRoRU9EWTNRak13UVRSR056UkdRUSJ9.eyJuaWNrbmFtZSI6ImRhbWllbmcrdGVzdDQyIiwibmFtZSI6ImRhbWllbmcrdGVzdDQyQGdtYWlsLmNvbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci81MzFiMDJkYTllOWVjNzg3ZDBlMWE1NzA1YzQ0YzU2Nj9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRmRhLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE5LTExLTAxVDE3OjQ0OjE2LjY5NVoiLCJlbWFpbCI6ImRhbWllbmcrdGVzdDQyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9hdXRoMC1kb3RuZXQtaW50ZWdyYXRpb24tdGVzdHMuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVkYTY0NTNjMTIyZmI2MGE5MjRlOTI2MSIsImF1ZCI6InFtc3M5QTY2c3RQV1RPWGpSNlgxT2VBMERMYWRvTlAyIiwiaWF0IjoxNTcyNjMwMjU2LCJleHAiOjE1NzI2NjYyNTYsIm5vbmNlIjoiU09ySE9hdTlxMGl0eDRpVEZfaVgydyJ9.NomT02whkH42ISpcd_JvG4ZvQQhzPKfoWCwcgrhyLeWmnmHTo704WtsnfCqR72uw26D-ZGA5n2Yu4Jdcv2A8_leGEQm3p45-ramIDwWUu2J30m_op_5I4wFvgpbRrWSrD1_3qK1GrDnrdv8psGL8VgCf3pLLDbqbkzDmtE6OtEfDp2hEFwXs9YntREXu5Z-ufFFLz9VU5uyRg7JA95YGQNIRhzMFoUNKZAO19nrBq3HKc_iR_W9g9Y3iLPLgVVazq6zHjn3cXNKpr7JN6MUKqIB-YYJ1KDEvmaMO60xs2DAhhnkUN1OhXBLTgQ9xbCJeaxE7N48YMxPAu3HHT-rhZg";

var mockHandler = new Mock<HttpMessageHandler>(MockBehavior.Strict);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(req => req.RequestUri.ToString() == $"https://auth0-dotnet-integration-tests.auth0.com/.well-known/openid-configuration"),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(new OpenIdConnectConfiguration() { JsonWebKeySet = signingKeys }), Encoding.UTF8, "application/json"),
});

await new OidcClient.Tokens.IdTokenValidator(mockHandler.Object).AssertTokenMeetsRequirements(signedReqs, token, new DateTime(2019, 11, 1, 20, 00, 00, DateTimeKind.Utc));


mockHandler.Protected().Verify(
"SendAsync",
Times.Exactly(1),
ItExpr.Is<HttpRequestMessage>(req => req.RequestUri.ToString() == $"https://auth0-dotnet-integration-tests.auth0.com/.well-known/openid-configuration"),
ItExpr.IsAny<CancellationToken>());
}
}
}

0 comments on commit 7470353

Please sign in to comment.