Skip to content

Commit

Permalink
Merge pull request #32 from Authress/support-alt-domain-for-auth-checks
Browse files Browse the repository at this point in the history
Allow authress domains that match the api one can be used for authori…
  • Loading branch information
wparad authored Feb 25, 2024
2 parents b19991c + 6178e3c commit 9d14300
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 2 deletions.
6 changes: 4 additions & 2 deletions src/Authress.SDK/Client/TokenVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,11 @@ public async Task<VerifiedUserIdentity> VerifyToken(string authorizationHeaderVa
throw new ArgumentNullException("The authress custom domain must be specified in the AuthressSettings.");
}

var completeIssuerUrl = new Uri(Sanitizers.SanitizeUrl(authressCustomDomain));
var completeIssuerUrl = new Uri(Sanitizers.SanitizeIssuerUrl(authressCustomDomain));
var altIssuerUrl = new Uri(Sanitizers.SanitizeUrl(authressCustomDomain));
try {
if (new Uri(unverifiedJwtPayload.Issuer).GetLeftPart(UriPartial.Authority) != completeIssuerUrl.GetLeftPart(UriPartial.Authority)) {
if (new Uri(unverifiedJwtPayload.Issuer).GetLeftPart(UriPartial.Authority) != completeIssuerUrl.GetLeftPart(UriPartial.Authority)
&& new Uri(unverifiedJwtPayload.Issuer).GetLeftPart(UriPartial.Authority) != altIssuerUrl.GetLeftPart(UriPartial.Authority)) {
throw new TokenVerificationException($"Unauthorized: Invalid Issuer: {unverifiedJwtPayload.Issuer}");
}
} catch (Exception) {
Expand Down
20 changes: 20 additions & 0 deletions src/Authress.SDK/Utilities/Sanitizers.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Text.RegularExpressions;

namespace Authress.SDK.Utilities {
Expand All @@ -18,5 +19,24 @@ internal static string SanitizeUrl(string urlString) {

return $"https://{urlString}";
}

internal static string SanitizeIssuerUrl(string rawUrlString) {
var sanitizedUrlString = rawUrlString;
if (!sanitizedUrlString.StartsWith("http")) {
sanitizedUrlString = Regex.IsMatch(sanitizedUrlString, @"^(localhost|authress.localhost.localstack.cloud:4566$)") ? $"http://{sanitizedUrlString}" : $"https://{sanitizedUrlString}";
}

var sanitizedUrl = new Uri(sanitizedUrlString);
var domainBaseUrlMatch = Regex.Match(sanitizedUrl.GetLeftPart(UriPartial.Authority), @"^https?://([a-z0-9-]+)[.][a-z0-9-]+[.]authress[.]io$");
if (domainBaseUrlMatch.Success) {
var newSanitizedUrl = new UriBuilder(sanitizedUrl)
{
Host = $"{domainBaseUrlMatch.Groups[1].Value}.login.authress.io"
};
sanitizedUrlString = newSanitizedUrl.Uri.ToString();
}

return sanitizedUrlString.Replace(@"[/]+$", "");
}
}
}
79 changes: 79 additions & 0 deletions tests/Authress.SDK/Client/Tokenverifier/VerifyTokenTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,85 @@ public async Task ValidateEddsaToken() {
mockHttpClient.VerifyAll();
}

[Fact]
public async Task ValidateTokenWithAltCustomDomain() {
var testUserId = Guid.NewGuid().ToString();
var testKeyId = Guid.NewGuid().ToString();
var authressClientTokenProvider = new AuthressClientTokenProvider($"{testUserId}.{testKeyId}.account.{eddsaKeys.Item1}", "authress.login.authress.io");
// setup
var edDsaJwkResponse = new JwkResponse { Keys = new List<Jwk> { new Jwk { Alg = Alg.EdDSA, kid = testKeyId, x = eddsaKeys.Item2 } } };
var jwtToken = await authressClientTokenProvider.GetBearerToken();

var mockHttpClient = new Mock<HttpClientHandler>(MockBehavior.Strict);
mockHttpClient.Protected().SetupSequence<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() => new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = edDsaJwkResponse.ToHttpContent() });

var mockFactory = new Mock<IHttpClientHandlerFactory>(MockBehavior.Strict);
mockFactory.Setup(factory => factory.Create()).Returns(mockHttpClient.Object);

var mockHttpClientProvider = new HttpClientProvider(null, null, mockFactory.Object);
var tokenVerifier = new SDK.TokenVerifier("authress.api-eu-west.authress.io", mockHttpClientProvider);

var result = await tokenVerifier.VerifyToken(jwtToken);
result.Should().BeEquivalentTo(new VerifiedUserIdentity { UserId = testUserId });

mockFactory.Verify(mockFactory => mockFactory.Create(), Times.Once());
mockHttpClient.VerifyAll();
}

[Fact]
public async Task ValidateTokenWithAltCustomDomainForBoth() {
var testUserId = Guid.NewGuid().ToString();
var testKeyId = Guid.NewGuid().ToString();
var authressClientTokenProvider = new AuthressClientTokenProvider($"{testUserId}.{testKeyId}.account.{eddsaKeys.Item1}", "authress.api-eu-west.authress.io");
// setup
var edDsaJwkResponse = new JwkResponse { Keys = new List<Jwk> { new Jwk { Alg = Alg.EdDSA, kid = testKeyId, x = eddsaKeys.Item2 } } };
var jwtToken = await authressClientTokenProvider.GetBearerToken();

var mockHttpClient = new Mock<HttpClientHandler>(MockBehavior.Strict);
mockHttpClient.Protected().SetupSequence<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() => new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = edDsaJwkResponse.ToHttpContent() });

var mockFactory = new Mock<IHttpClientHandlerFactory>(MockBehavior.Strict);
mockFactory.Setup(factory => factory.Create()).Returns(mockHttpClient.Object);

var mockHttpClientProvider = new HttpClientProvider(null, null, mockFactory.Object);
var tokenVerifier = new SDK.TokenVerifier("authress.api-eu-west.authress.io", mockHttpClientProvider);

var result = await tokenVerifier.VerifyToken(jwtToken);
result.Should().BeEquivalentTo(new VerifiedUserIdentity { UserId = testUserId });

mockFactory.Verify(mockFactory => mockFactory.Create(), Times.Once());
mockHttpClient.VerifyAll();
}


[Fact]
public async Task ValidateTokenWithNoCustomDomain() {
var testUserId = Guid.NewGuid().ToString();
var testKeyId = Guid.NewGuid().ToString();
var authressClientTokenProvider = new AuthressClientTokenProvider($"{testUserId}.{testKeyId}.account.{eddsaKeys.Item1}", "authress.login.authress.io");
// setup
var edDsaJwkResponse = new JwkResponse { Keys = new List<Jwk> { new Jwk { Alg = Alg.EdDSA, kid = testKeyId, x = eddsaKeys.Item2 } } };
var jwtToken = await authressClientTokenProvider.GetBearerToken();

var mockHttpClient = new Mock<HttpClientHandler>(MockBehavior.Strict);
mockHttpClient.Protected().SetupSequence<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() => new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = edDsaJwkResponse.ToHttpContent() });

var mockFactory = new Mock<IHttpClientHandlerFactory>(MockBehavior.Strict);
mockFactory.Setup(factory => factory.Create()).Returns(mockHttpClient.Object);

var mockHttpClientProvider = new HttpClientProvider(null, null, mockFactory.Object);
var tokenVerifier = new SDK.TokenVerifier("authress.login.authress.io", mockHttpClientProvider);

var result = await tokenVerifier.VerifyToken(jwtToken);
result.Should().BeEquivalentTo(new VerifiedUserIdentity { UserId = testUserId });

mockFactory.Verify(mockFactory => mockFactory.Create(), Times.Once());
mockHttpClient.VerifyAll();
}

[Fact]
public async Task ValidateEddsaTokenWithExtraSpaces() {
var testUserId = Guid.NewGuid().ToString();
Expand Down

0 comments on commit 9d14300

Please sign in to comment.