diff --git a/src/Authress.SDK/Client/TokenVerifier.cs b/src/Authress.SDK/Client/TokenVerifier.cs index f2445af..dbfc21d 100644 --- a/src/Authress.SDK/Client/TokenVerifier.cs +++ b/src/Authress.SDK/Client/TokenVerifier.cs @@ -156,9 +156,11 @@ public async Task 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) { diff --git a/src/Authress.SDK/Utilities/Sanitizers.cs b/src/Authress.SDK/Utilities/Sanitizers.cs index b2de374..59fef33 100644 --- a/src/Authress.SDK/Utilities/Sanitizers.cs +++ b/src/Authress.SDK/Utilities/Sanitizers.cs @@ -1,3 +1,4 @@ +using System; using System.Text.RegularExpressions; namespace Authress.SDK.Utilities { @@ -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(@"[/]+$", ""); + } } } diff --git a/tests/Authress.SDK/Client/Tokenverifier/VerifyTokenTests.cs b/tests/Authress.SDK/Client/Tokenverifier/VerifyTokenTests.cs index a4d7e91..d11dd2f 100644 --- a/tests/Authress.SDK/Client/Tokenverifier/VerifyTokenTests.cs +++ b/tests/Authress.SDK/Client/Tokenverifier/VerifyTokenTests.cs @@ -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 { new Jwk { Alg = Alg.EdDSA, kid = testKeyId, x = eddsaKeys.Item2 } } }; + var jwtToken = await authressClientTokenProvider.GetBearerToken(); + + var mockHttpClient = new Mock(MockBehavior.Strict); + mockHttpClient.Protected().SetupSequence>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(() => new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = edDsaJwkResponse.ToHttpContent() }); + + var mockFactory = new Mock(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 { new Jwk { Alg = Alg.EdDSA, kid = testKeyId, x = eddsaKeys.Item2 } } }; + var jwtToken = await authressClientTokenProvider.GetBearerToken(); + + var mockHttpClient = new Mock(MockBehavior.Strict); + mockHttpClient.Protected().SetupSequence>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(() => new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = edDsaJwkResponse.ToHttpContent() }); + + var mockFactory = new Mock(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 { new Jwk { Alg = Alg.EdDSA, kid = testKeyId, x = eddsaKeys.Item2 } } }; + var jwtToken = await authressClientTokenProvider.GetBearerToken(); + + var mockHttpClient = new Mock(MockBehavior.Strict); + mockHttpClient.Protected().SetupSequence>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(() => new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = edDsaJwkResponse.ToHttpContent() }); + + var mockFactory = new Mock(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();