From 4f502917e942aba375d622fdf9a32d7f3dca15e1 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Tue, 13 May 2025 17:57:52 +0530 Subject: [PATCH 01/10] Reference latest Auth0.AuthenticationApi package --- .../Auth0.AspNetCore.Authentication.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj b/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj index 07def8e..b41c2ee 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj +++ b/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj @@ -5,6 +5,7 @@ + From 32d7d9faa918a991b0de13d247945232ac5c5671 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Mon, 14 Jul 2025 18:23:14 +0530 Subject: [PATCH 02/10] Add IAuth0AuthenticationApiClient to define the methods accessible from Auth0.AuthenticationApi package --- .../IAuth0AuthenticationApiClient.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs diff --git a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs b/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs new file mode 100644 index 0000000..9fbb981 --- /dev/null +++ b/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Auth0.AuthenticationApi.Models.Ciba; +using Auth0.AuthenticationApi.Models.Mfa; + +using Models = Auth0.AuthenticationApi.Models; +using CancellationToken = System.Threading.CancellationToken; +using IAuthenticationApiClient = Auth0.AuthenticationApi.IAuthenticationApiClient; + +namespace Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; + +public interface IAuth0AuthenticationApiClient : IDisposable +{ + /// + Uri BaseUri { get; } + + /// + Task ChangePasswordAsync(Models.ChangePasswordRequest request, CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(Models.AuthorizationCodeTokenRequest request, + CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(Models.AuthorizationCodePkceTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(Models.ClientCredentialsTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(Models.RefreshTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(Models.PasswordlessEmailTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(Models.PasswordlessSmsTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task RevokeRefreshTokenAsync(Models.RevokeRefreshTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task SignupUserAsync(Models.SignupUserRequest request, CancellationToken cancellationToken = default); + + /// + Task StartPasswordlessEmailFlowAsync( + Models.PasswordlessEmailRequest request, CancellationToken cancellationToken = default); + + /// + Task StartPasswordlessSmsFlowAsync(Models.PasswordlessSmsRequest request, + CancellationToken cancellationToken = default); + + /// + Task ClientInitiatedBackchannelAuthorization( + ClientInitiatedBackchannelAuthorizationRequest request, CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync( + ClientInitiatedBackchannelAuthorizationTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task AssociateMfaAuthenticatorAsync(AssociateMfaAuthenticatorRequest request, + CancellationToken cancellationToken = default); + + /// + Task> ListMfaAuthenticatorsAsync(string accessToken, + CancellationToken cancellationToken = default); + + /// + Task DeleteMfaAuthenticatorAsync(DeleteMfaAuthenticatorRequest request, + CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(MfaOobTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(MfaOtpTokenRequest request, CancellationToken cancellationToken = default); + + /// + Task GetTokenAsync(MfaRecoveryCodeRequest request, + CancellationToken cancellationToken = default); + + /// + Task MfaChallengeAsync(MfaChallengeRequest request, CancellationToken cancellationToken = default); +} \ No newline at end of file From 3016750205cf86dabe0187346719eea3cf24899b Mon Sep 17 00:00:00 2001 From: kailash-b Date: Mon, 14 Jul 2025 18:43:19 +0530 Subject: [PATCH 03/10] Add implementation for IAuth0AuthenticationApiClient --- .../Auth0AuthenticationApiClient.cs | 151 ++++++++++++++++++ .../IAuth0AuthenticationApiClient.cs | 4 + 2 files changed, 155 insertions(+) create mode 100644 src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs diff --git a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs b/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs new file mode 100644 index 0000000..660abd5 --- /dev/null +++ b/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +using Auth0.AuthenticationApi; +using Auth0.AuthenticationApi.Models; +using Auth0.AuthenticationApi.Models.Ciba; +using Auth0.AuthenticationApi.Models.Mfa; + +namespace Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; + +/// +public class Auth0AuthenticationApiClient : IAuth0AuthenticationApiClient +{ + private readonly IAuthenticationApiClient _authenticationApiClient; + + public Auth0AuthenticationApiClient(IAuthenticationApiClient authenticationApiClient) + { + _authenticationApiClient = authenticationApiClient ?? throw new ArgumentNullException(nameof(authenticationApiClient)); + } + + /// + public void Dispose() => _authenticationApiClient.Dispose(); + + /// + public Uri BaseUri => _authenticationApiClient.BaseUri; + + /// + public Task ChangePasswordAsync(ChangePasswordRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.ChangePasswordAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(AuthorizationCodeTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(AuthorizationCodePkceTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(ClientCredentialsTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(PasswordlessEmailTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(PasswordlessSmsTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task RevokeRefreshTokenAsync(RevokeRefreshTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.RevokeRefreshTokenAsync(request, cancellationToken); + } + + /// + public Task SignupUserAsync(SignupUserRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.SignupUserAsync(request, cancellationToken); + } + + /// + public Task StartPasswordlessEmailFlowAsync(PasswordlessEmailRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.StartPasswordlessEmailFlowAsync(request, cancellationToken); + } + + /// + public Task StartPasswordlessSmsFlowAsync(PasswordlessSmsRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.StartPasswordlessSmsFlowAsync(request, cancellationToken); + } + + /// + public Task ClientInitiatedBackchannelAuthorization(ClientInitiatedBackchannelAuthorizationRequest request, + CancellationToken cancellationToken = default) + { + return _authenticationApiClient.ClientInitiatedBackchannelAuthorization(request, cancellationToken); + } + + /// + public Task GetTokenAsync(ClientInitiatedBackchannelAuthorizationTokenRequest request, + CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task AssociateMfaAuthenticatorAsync(AssociateMfaAuthenticatorRequest request, + CancellationToken cancellationToken = default) + { + return _authenticationApiClient.AssociateMfaAuthenticatorAsync(request, cancellationToken); + } + + /// + public Task> ListMfaAuthenticatorsAsync(string accessToken, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.ListMfaAuthenticatorsAsync(accessToken, cancellationToken); + } + + /// + public Task DeleteMfaAuthenticatorAsync(DeleteMfaAuthenticatorRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.DeleteMfaAuthenticatorAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(MfaOobTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(MfaOtpTokenRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task GetTokenAsync(MfaRecoveryCodeRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.GetTokenAsync(request, cancellationToken); + } + + /// + public Task MfaChallengeAsync(MfaChallengeRequest request, CancellationToken cancellationToken = default) + { + return _authenticationApiClient.MfaChallenge(request, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs b/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs index 9fbb981..a59fd13 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs +++ b/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs @@ -11,6 +11,10 @@ namespace Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; +/// +/// Encapsulates the to provide a strongly-typed interface for +/// Auth0 Authentication API related operations. +/// public interface IAuth0AuthenticationApiClient : IDisposable { /// From 47de2d47dd23a4f04633961bcb5b12eb71d4d14b Mon Sep 17 00:00:00 2001 From: kailash-b Date: Mon, 14 Jul 2025 19:40:08 +0530 Subject: [PATCH 04/10] Add extension method to inject IAuth0AuthenticationApiClient dependency --- .../Auth0AuthenticationApiClient.cs | 13 ++++----- .../Auth0WebAppAuthenticationBuilder.cs | 27 ++++++++++++++++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs b/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs index 660abd5..f151f30 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs +++ b/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs @@ -7,6 +7,7 @@ using Auth0.AuthenticationApi.Models; using Auth0.AuthenticationApi.Models.Ciba; using Auth0.AuthenticationApi.Models.Mfa; +using Models = Auth0.AuthenticationApi.Models; namespace Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; @@ -33,37 +34,37 @@ public Task ChangePasswordAsync(ChangePasswordRequest request, Cancellat } /// - public Task GetTokenAsync(AuthorizationCodeTokenRequest request, CancellationToken cancellationToken = default) + public Task GetTokenAsync(AuthorizationCodeTokenRequest request, CancellationToken cancellationToken = default) { return _authenticationApiClient.GetTokenAsync(request, cancellationToken); } /// - public Task GetTokenAsync(AuthorizationCodePkceTokenRequest request, CancellationToken cancellationToken = default) + public Task GetTokenAsync(AuthorizationCodePkceTokenRequest request, CancellationToken cancellationToken = default) { return _authenticationApiClient.GetTokenAsync(request, cancellationToken); } /// - public Task GetTokenAsync(ClientCredentialsTokenRequest request, CancellationToken cancellationToken = default) + public Task GetTokenAsync(ClientCredentialsTokenRequest request, CancellationToken cancellationToken = default) { return _authenticationApiClient.GetTokenAsync(request, cancellationToken); } /// - public Task GetTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default) + public Task GetTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default) { return _authenticationApiClient.GetTokenAsync(request, cancellationToken); } /// - public Task GetTokenAsync(PasswordlessEmailTokenRequest request, CancellationToken cancellationToken = default) + public Task GetTokenAsync(PasswordlessEmailTokenRequest request, CancellationToken cancellationToken = default) { return _authenticationApiClient.GetTokenAsync(request, cancellationToken); } /// - public Task GetTokenAsync(PasswordlessSmsTokenRequest request, CancellationToken cancellationToken = default) + public Task GetTokenAsync(PasswordlessSmsTokenRequest request, CancellationToken cancellationToken = default) { return _authenticationApiClient.GetTokenAsync(request, cancellationToken); } diff --git a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs index 945ac05..afd8741 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs +++ b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs @@ -1,10 +1,14 @@ -using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using System; +using System.Threading.Tasks; + +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using System; -using System.Threading.Tasks; + +using Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; using Auth0.AspNetCore.Authentication.BackchannelLogout; +using Auth0.AuthenticationApi; namespace Auth0.AspNetCore.Authentication { @@ -61,6 +65,23 @@ public Auth0WebAppAuthenticationBuilder WithBackchannelLogout() return this; } + /// + /// Configures the to leverage Auth0.AuthenticationApi + /// + /// An instance of + public Auth0WebAppAuthenticationBuilder WithAuth0AuthenticationApiClient() + { + _services.AddTransient( + sp => + { + var options = sp.GetRequiredService>().Value; + return new Auth0AuthenticationApiClient.Auth0AuthenticationApiClient( + new AuthenticationApiClient(new Uri($"https://{options.Domain}"))); + } + ); + return this; + } + private void EnableWithAccessToken(Action configureOptions) { var auth0WithAccessTokensOptions = new Auth0WebAppWithAccessTokenOptions(); From a311f6963e7779f7d279b116877daf939569e6d1 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Mon, 14 Jul 2025 19:40:45 +0530 Subject: [PATCH 05/10] Upgrade Microsoft.IdentityModel.Protocols.OpenIdConnect to 8.12.1 --- .../Auth0.AspNetCore.Authentication.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj b/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj index b41c2ee..f558216 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj +++ b/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj @@ -10,7 +10,6 @@ - From 3fcb462a43ce22c8f39d4951364f67443c065cb0 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Mon, 14 Jul 2025 22:41:59 +0530 Subject: [PATCH 06/10] Add test cases --- .../Auth0AuthenticationApiClientTests.cs | 406 ++++++++++++++++++ .../Auth0WebAppAuthenticationBuilderTests.cs | 70 +++ 2 files changed, 476 insertions(+) create mode 100644 tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0AuthenticationApiClientTests.cs create mode 100644 tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0WebAppAuthenticationBuilderTests.cs diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0AuthenticationApiClientTests.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0AuthenticationApiClientTests.cs new file mode 100644 index 0000000..e3e7e0f --- /dev/null +++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0AuthenticationApiClientTests.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +using Auth0.AuthenticationApi; +using Auth0.AuthenticationApi.Models; +using Auth0.AuthenticationApi.Models.Ciba; +using Auth0.AuthenticationApi.Models.Mfa; + +using FluentAssertions; +using Moq; +using Xunit; +using Models = Auth0.AuthenticationApi.Models; + +namespace Auth0.AspNetCore.Authentication.IntegrationTests; + +public class Auth0AuthenticationApiClientTests +{ + private readonly Mock _mockAuthenticationApiClient; + private readonly Auth0AuthenticationApiClient.Auth0AuthenticationApiClient _auth0AuthenticationApiClient; + + public Auth0AuthenticationApiClientTests() + { + _mockAuthenticationApiClient = new Mock(); + _auth0AuthenticationApiClient = new Auth0AuthenticationApiClient.Auth0AuthenticationApiClient(_mockAuthenticationApiClient.Object); + } + + [Fact] + public void Constructor_When_AuthenticationApiClient_Is_Null_Throws_ArgumentNullException() + { + var act = () => new Auth0AuthenticationApiClient.Auth0AuthenticationApiClient(null); + + act.Should().Throw() + .And.ParamName.Should().Be("authenticationApiClient"); + } + + [Fact] + public void Constructor_When_AuthenticationApiClient_Is_Valid_Creates_Instance() + { + var result = new Auth0AuthenticationApiClient.Auth0AuthenticationApiClient(_mockAuthenticationApiClient.Object); + + result.Should().NotBeNull(); + } + + [Fact] + public void Dispose_Calls_Dispose_On_Underlying_Client() + { + _auth0AuthenticationApiClient.Dispose(); + _mockAuthenticationApiClient.Verify(x => x.Dispose(), Times.Once); + } + + [Fact] + public void BaseUri_Return_BaseUri_From_Underlying_Client() + { + var expectedUri = new Uri("https://example.auth0.com"); + _mockAuthenticationApiClient.Setup(x => x.BaseUri).Returns(expectedUri); + + var result = _auth0AuthenticationApiClient.BaseUri; + + result.Should().Be(expectedUri); + _mockAuthenticationApiClient.Verify(x => x.BaseUri, Times.Once); + } + + [Fact] + public async Task ChangePasswordAsync_Calls_Underlying_Client_With_Request() + { + var request = new ChangePasswordRequest(); + var expectedResult = "password-changed"; + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.ChangePasswordAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.ChangePasswordAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.ChangePasswordAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task ChangePasswordAsync_With_Default_CancellationToken_Calls_Underlying_Client() + { + var request = new ChangePasswordRequest(); + var expectedResult = "password-changed"; + _mockAuthenticationApiClient.Setup( + x => x.ChangePasswordAsync(request, default)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.ChangePasswordAsync(request); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.ChangePasswordAsync(request, default), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_With_AuthorizationCodeTokenRequest_Calls_Underlying_Client() + { + var request = new AuthorizationCodeTokenRequest(); + var expectedResult = new Models.AccessTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_WithAuthorizationCodePkceTokenRequest_Calls_Underlying_Client() + { + var request = new AuthorizationCodePkceTokenRequest(); + var expectedResult = new Models.AccessTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_With_ClientCredentialsTokenRequest_Calls_Underlying_Client() + { + var request = new ClientCredentialsTokenRequest(); + var expectedResult = new Models.AccessTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_With_RefreshTokenRequest_Calls_Underlying_Client() + { + var request = new RefreshTokenRequest(); + var expectedResult = new Models.AccessTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_With_PasswordlessEmailTokenRequest_Calls_Underlying_Client() + { + var request = new PasswordlessEmailTokenRequest(); + var expectedResult = new Models.AccessTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_With_PasswordlessSmsTokenRequest_Calls_Underlying_Client() + { + var request = new PasswordlessSmsTokenRequest(); + var expectedResult = new Models.AccessTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task RevokeRefreshTokenAsync_Calls_Underlying_Client() + { + var request = new RevokeRefreshTokenRequest(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup( + x => x.RevokeRefreshTokenAsync(request, cancellationToken)) + .Returns(Task.CompletedTask); + + await _auth0AuthenticationApiClient.RevokeRefreshTokenAsync(request, cancellationToken); + + _mockAuthenticationApiClient.Verify( + x => x.RevokeRefreshTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task SignupUserAsync_Calls_Underlying_Client() + { + var request = new SignupUserRequest(); + var expectedResult = new SignupUserResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup( + x => x.SignupUserAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.SignupUserAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.SignupUserAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task StartPasswordlessEmailFlowAsync_Calls_Underlying_Client() + { + var request = new PasswordlessEmailRequest(); + var expectedResult = new PasswordlessEmailResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup( + x => x.StartPasswordlessEmailFlowAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = + await _auth0AuthenticationApiClient.StartPasswordlessEmailFlowAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.StartPasswordlessEmailFlowAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task StartPasswordlessSmsFlowAsync_Calls_Underlying_Client() + { + var request = new PasswordlessSmsRequest(); + var expectedResult = new PasswordlessSmsResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup( + x => x.StartPasswordlessSmsFlowAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = + await _auth0AuthenticationApiClient.StartPasswordlessSmsFlowAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.StartPasswordlessSmsFlowAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task ClientInitiatedBackchannelAuthorization_Calls_Underlying_Client() + { + var request = new ClientInitiatedBackchannelAuthorizationRequest(); + var expectedResult = new ClientInitiatedBackchannelAuthorizationResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup( + x => x.ClientInitiatedBackchannelAuthorization(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = + await _auth0AuthenticationApiClient.ClientInitiatedBackchannelAuthorization(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.ClientInitiatedBackchannelAuthorization(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_WithClientInitiatedBackchannelAuthorizationTokenRequest_Calls_Underlying_Client() + { + var request = new ClientInitiatedBackchannelAuthorizationTokenRequest(); + var expectedResult = new ClientInitiatedBackchannelAuthorizationTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = + await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task AssociateMfaAuthenticatorAsync_Calls_Underlying_Client() + { + var request = new AssociateMfaAuthenticatorRequest(); + var expectedResult = new AssociateMfaAuthenticatorResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.AssociateMfaAuthenticatorAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = + await _auth0AuthenticationApiClient.AssociateMfaAuthenticatorAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.AssociateMfaAuthenticatorAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task ListMfaAuthenticatorsAsync_Calls_Underlying_Client() + { + var accessToken = "access-token"; + var expectedResult = new List(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup( + x => x.ListMfaAuthenticatorsAsync(accessToken, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.ListMfaAuthenticatorsAsync(accessToken, cancellationToken); + + result.Should().BeEquivalentTo(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.ListMfaAuthenticatorsAsync(accessToken, cancellationToken), Times.Once); + } + + [Fact] + public async Task DeleteMfaAuthenticatorAsync_Calls_Underlying_Client() + { + var request = new DeleteMfaAuthenticatorRequest(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.DeleteMfaAuthenticatorAsync(request, cancellationToken)) + .Returns(Task.CompletedTask); + + await _auth0AuthenticationApiClient.DeleteMfaAuthenticatorAsync(request, cancellationToken); + + _mockAuthenticationApiClient.Verify( + x => x.DeleteMfaAuthenticatorAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_WithMfaOobTokenRequest_Calls_Underlying_Client() + { + var request = new MfaOobTokenRequest(); + var expectedResult = new MfaOobTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_WithMfaOtpTokenRequest_Calls_Underlying_Client() + { + var request = new MfaOtpTokenRequest(); + var expectedResult = new MfaOtpTokenResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task GetTokenAsync_WithMfaRecoveryCodeRequest_Calls_Underlying_Client() + { + var request = new MfaRecoveryCodeRequest(); + var expectedResult = new MfaRecoveryCodeResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify( + x => x.GetTokenAsync(request, cancellationToken), Times.Once); + } + + [Fact] + public async Task MfaChallengeAsync_Calls_Underlying_Client() + { + var request = new MfaChallengeRequest(); + var expectedResult = new MfaChallengeResponse(); + var cancellationToken = new CancellationToken(); + _mockAuthenticationApiClient.Setup(x => x.MfaChallenge(request, cancellationToken)) + .ReturnsAsync(expectedResult); + + var result = await _auth0AuthenticationApiClient.MfaChallengeAsync(request, cancellationToken); + + result.Should().Be(expectedResult); + _mockAuthenticationApiClient.Verify(x => x.MfaChallenge(request, cancellationToken), Times.Once); + } +} \ No newline at end of file diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0WebAppAuthenticationBuilderTests.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0WebAppAuthenticationBuilderTests.cs new file mode 100644 index 0000000..53a1fc6 --- /dev/null +++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0WebAppAuthenticationBuilderTests.cs @@ -0,0 +1,70 @@ +using System.Linq; + +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; + +using Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; + +namespace Auth0.AspNetCore.Authentication.IntegrationTests; + +public class Auth0WebAppAuthenticationBuilderTests +{ + [Fact] + public void WithAuth0AuthenticationApiClient_Should_Register_IAuth0AuthenticationApiClient_As_Transient() + { + var services = new ServiceCollection(); + var options = new Auth0WebAppOptions { Domain = "test-domain.auth0.com" }; + var builder = new Auth0WebAppAuthenticationBuilder(services, options); + + builder.WithAuth0AuthenticationApiClient(); + + var serviceDescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(IAuth0AuthenticationApiClient)); + serviceDescriptor.Should().NotBeNull(); + serviceDescriptor?.Lifetime.Should().Be(ServiceLifetime.Transient); + } + + [Fact] + public void WithAuth0AuthenticationApiClient_Should_Return_Same_Builder_Instance() + { + var services = new ServiceCollection(); + var options = new Auth0WebAppOptions { Domain = "test-domain.auth0.com" }; + var builder = new Auth0WebAppAuthenticationBuilder(services, options); + + var result = builder.WithAuth0AuthenticationApiClient(); + + result.Should().BeSameAs(builder); + } + + [Fact] + public void WithAuth0AuthenticationApiClient_Should_Create_Client_With_Correct_Domain() + { + var services = new ServiceCollection(); + var options = new Auth0WebAppOptions { Domain = "test-domain.auth0.com" }; + services.AddSingleton(Options.Create(options)); + var builder = new Auth0WebAppAuthenticationBuilder(services, options); + + builder.WithAuth0AuthenticationApiClient(); + + var serviceProvider = services.BuildServiceProvider(); + var client = serviceProvider.GetRequiredService(); + + client.Should().NotBeNull(); + client.Should().BeOfType(); + } + + [Fact] + public void WithAuth0AuthenticationApiClient_When_Called_Multiple_Times_Should_Register_Service_Multiple_Times() + { + var services = new ServiceCollection(); + var options = new Auth0WebAppOptions { Domain = "test-domain.auth0.com" }; + var builder = new Auth0WebAppAuthenticationBuilder(services, options); + + builder.WithAuth0AuthenticationApiClient(); + builder.WithAuth0AuthenticationApiClient(); + + var serviceDescriptors = services.Where(s => s.ServiceType == typeof(IAuth0AuthenticationApiClient)); + serviceDescriptors.Should().HaveCount(2); + } +} \ No newline at end of file From 050589dad78485171d48b86e226b834ad73fa64b Mon Sep 17 00:00:00 2001 From: kailash-b Date: Tue, 15 Jul 2025 13:49:18 +0530 Subject: [PATCH 07/10] Use .net8.0 in all workflows --- .github/workflows/release.yml | 4 ++-- .github/workflows/rl-secure.yml | 2 +- .github/workflows/snyk.yml | 2 +- .../Auth0.AspNetCore.Authentication.Playground.csproj | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c1aae4..2c219d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: rl-scanner: uses: ./.github/workflows/rl-secure.yml with: - project-path: "src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj" + project-path: " " artifact-name: "auth0-aspnetcore-authentication.tgz" secrets: RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} @@ -35,7 +35,7 @@ jobs: needs: rl-scanner uses: ./.github/workflows/nuget-release.yml with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x project-paths: "['src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj']" secrets: nuget-token: ${{ secrets.NUGET_APIKEY }} diff --git a/.github/workflows/rl-secure.yml b/.github/workflows/rl-secure.yml index 0b788e6..8257c4e 100644 --- a/.github/workflows/rl-secure.yml +++ b/.github/workflows/rl-secure.yml @@ -41,7 +41,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Create NuGet packages shell: pwsh diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index b765e11..8373df7 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -37,7 +37,7 @@ jobs: - name: Install .NET Core uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: 8.0.x - name: Dotnet Restore run: dotnet restore diff --git a/playground/Auth0.AspNetCore.Authentication.Playground/Auth0.AspNetCore.Authentication.Playground.csproj b/playground/Auth0.AspNetCore.Authentication.Playground/Auth0.AspNetCore.Authentication.Playground.csproj index d1fcdd0..d084459 100644 --- a/playground/Auth0.AspNetCore.Authentication.Playground/Auth0.AspNetCore.Authentication.Playground.csproj +++ b/playground/Auth0.AspNetCore.Authentication.Playground/Auth0.AspNetCore.Authentication.Playground.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 From 9f10c0bed5b13f83be9971b66bd60d2deffc632a Mon Sep 17 00:00:00 2001 From: kailash-b Date: Tue, 15 Jul 2025 14:03:10 +0530 Subject: [PATCH 08/10] Update examples.md --- EXAMPLES.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index b67fd76..c83c4e9 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -8,6 +8,7 @@ - [Roles](#roles) - [Backchannel Logout](#backchannel-logout) - [Blazor Server](#blazor-server) +- [Using AuthenticationApiClient within a Controller](#using-authenticationapiclient-within-a-controller) ## Login and Logout Triggering login or logout is done using ASP.NET's `HttpContext`: @@ -444,3 +445,45 @@ public class LogoutModel : PageModel } } ``` + +## Using AuthenticationApiClient within a Controller +We can setup the dependency injection for `Auth0AuthenticationApiClient` using the `WithAuth0AuthenticationApiClient` extension method as below. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + services.WithAuth0AuthenticationApiClient(options => + { + options.Domain = Configuration["Auth0:Domain"]; + options.ClientId = Configuration["Auth0:ClientId"]; + options.ClientSecret = Configuration["Auth0:ClientSecret"]; + }); +} +``` + +For using `Auth0AuthenticationApiClient`, request an instance of `IAuth0AuthenticationApiClient` in the constructor as below. + +```csharp +[ApiController] +[Route("api/[controller]")] +public class MyController : ControllerBase +{ + private readonly IAuth0AuthenticationApiClient _auth0Client; + + public MyController(IAuth0AuthenticationApiClient auth0Client) + { + _auth0Client = auth0Client; + } + + [HttpGet("revoke-refresh-token")] + public async Task RevokeRefreshToken() + { + var request = new RevokeRefreshTokenRequest + { + Token = "your_refresh_token" + }; + await _auth0Client.RevokeRefreshTokenAsync(request); + return Ok(); + } +} +``` \ No newline at end of file From a0ebd9b6321af74fdfa5fcaebed965f044698558 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Wed, 16 Jul 2025 16:24:43 +0530 Subject: [PATCH 09/10] Address review comments --- .github/workflows/release.yml | 2 +- EXAMPLES.md | 153 ++++++++++++++++++++++++---------- 2 files changed, 110 insertions(+), 45 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c219d4..df323fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: rl-scanner: uses: ./.github/workflows/rl-secure.yml with: - project-path: " " + project-path: "src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj" artifact-name: "auth0-aspnetcore-authentication.tgz" secrets: RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} diff --git a/EXAMPLES.md b/EXAMPLES.md index c83c4e9..c2210fa 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1,16 +1,34 @@ # Examples using auth0-aspnetcore-authentication - -- [Login and Logout](#login-and-logout) -- [Scopes](#scopes) -- [Calling an API](#calling-an-api) -- [Organizations](#organizations) -- [Extra parameters](#extra-parameters) -- [Roles](#roles) -- [Backchannel Logout](#backchannel-logout) -- [Blazor Server](#blazor-server) -- [Using AuthenticationApiClient within a Controller](#using-authenticationapiclient-within-a-controller) - -## Login and Logout +## Table of Contents + +1. [Login and Logout](#1-login-and-logout) +2. [Scopes](#2-scopes) +3. [Calling an API](#3-calling-an-api) + - [Retrieving the access token](#31-retrieving-the-access-token) + - [Refresh tokens](#32-refresh-tokens) + - [Detecting the absence of a refresh token](#321-detecting-the-absense-of-a-refresh-token) +4. [Organizations](#4-organizations) + - [Log in to an organization](#41-log-in-to-an-organization) + - [Organization claim validation](#42-organization-claim-validation) + - [Accept user invitations](#43-accept-user-invitations) +5. [Extra Parameters](#5-extra-parameters) + - [Extra parameters when logging in](#51-extra-parameters-when-logging-in) + - [Extra parameters when logging out](#52-extra-parameters-when-logging-out) +6. [Roles](#6-roles) + - [Integrate roles in your ASP.NET application](#61-integrate-roles-in-your-aspnet-application) +7. [Backchannel Logout](#7-backchannel-logout) + - [Distributed caching](#71-distributed-caching) +8. [Blazor Server](#8-blazor-server) + - [Register the SDK](#81-register-the-sdk) + - [Add login and logout](#82-add-login-and-logout) +9. [AuthenticationApiClient in your ASP.NET Core](#9-authenticationapiclient-in-your-aspnet-core) + - [Configuring your application to use Auth0.AuthenticationApi](#91-configuring-your-application-to-use-auth0authenticationapi) + - [Example Use Case 1: Passwordless Email Login](#92-example-use-case-1-passwordless-email-login) + - [Example Use Case 2: Revoking a Refresh Token](#93-example-use-case-2-revoking-a-refresh-token) + + + +## 1. Login and Logout Triggering login or logout is done using ASP.NET's `HttpContext`: ```csharp @@ -38,7 +56,7 @@ public async Task Logout() } ``` -## Scopes +## 2. Scopes By default, this SDK requests the `openid profile` scopes, if needed you can configure the SDK to request a different set of scopes. As `openid` is a [required scope](https://auth0.com/docs/scopes/openid-connect-scopes), the SDK will ensure the `openid` scope is always added, even when explicitly omitted when setting the scope. @@ -64,7 +82,7 @@ await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authentica > :information_source: Specifying the scopes when calling `HttpContext.ChallengeAsync` will take precedence over any globally configured scopes. -## Calling an API +## 3. Calling an API If you want to call an API from your ASP.NET MVC application, you need to obtain an access token issued for the API you want to call. As the SDK is configured to use OAuth's [Implicit Grant with Form Post](https://auth0.com/docs/flows/implicit-flow-with-form-post), no access token will be returned by default. In order to do so, we should be using the [Authorization Code Grant](https://auth0.com/docs/flows/authorization-code-flow), which requires the use of a `ClientSecret`. @@ -97,7 +115,7 @@ await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authentica > :information_source: Specifying the Audience when calling `HttpContext.ChallengeAsync` will take precedence over any globally configured Audience. -### Retrieving the access token +### 3.1. Retrieving the access token As the SDK uses the OpenId Connect middleware, the ID token is decoded and the corresponding claims are added to the `ClaimsIdentity`, making them available by using `User.Claims`. @@ -118,7 +136,7 @@ public async Task Profile() } ``` -### Refresh tokens +### 3.2. Refresh tokens In the case where the application needs to use an access token to access an API, there may be a situation where the access token expires before the application's session does. In order to ensure you have a valid access token at all times, you can configure the SDK to use refresh tokens: @@ -140,7 +158,7 @@ public void ConfigureServices(IServiceCollection services) } ``` -#### Detecting the absense of a refresh token +#### 3.2.1. Detecting the absense of a refresh token In the event where the API, defined in your Auth0 dashboard, isn't configured to [allow offline access](https://auth0.com/docs/get-started/dashboard/api-settings), or the user was already logged in before the use of refresh tokens was enabled (e.g. a user logs in a few minutes before the use of refresh tokens is deployed), it might be useful to detect the absense of a refresh token in order to react accordingly (e.g. log the user out locally and force them to re-login). @@ -167,13 +185,13 @@ The above snippet checks whether the SDK is configured to use refresh tokens, if > :information_source: In order for Auth0 to redirect back to the application's login URL, ensure to add the configured redirect URL to the application's `Allowed Logout URLs` in Auth0's dashboard. -## Organizations +## 4. Organizations [Organizations](https://auth0.com/docs/organizations) is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications. Note that Organizations is currently only available to customers on our Enterprise and Startup subscription plans. -### Log in to an organization +### 4.1. Log in to an organization Log in to an organization by specifying the `Organization` when calling `AddAuth0WebAppAuthentication`: @@ -198,7 +216,7 @@ await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authentica > :information_source: Specifying the Organization when calling `HttpContext.ChallengeAsync` will take precedence over any globally configured Organization. -### Organization claim validation +### 4.2. Organization claim validation If you don't provide an `organization` parameter at login, the SDK can't validate the `org_id` (or `org_name`) claim you get back in the ID token. In that case, you should validate the `org_id` (or `org_name`) claim yourself (e.g. by checking it against a list of valid organization ID's (or names) or comparing it with the application's URL). @@ -226,7 +244,7 @@ services.AddAuth0WebAppAuthentication(options => For more information, please read [Work with Tokens and Organizations](https://auth0.com/docs/organizations/using-tokens) on Auth0 Docs. -### Accept user invitations +### 4.3. Accept user invitations Accept a user invitation through the SDK by creating a route within your application that can handle the user invitation URL, and log the user in by passing the `organization` and `invitation` parameters from this URL. ```csharp @@ -238,17 +256,17 @@ public class InvitationController : Controller { .WithOrganization(organization) .WithInvitation(invitation) .Build(); - + await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties); } } ``` -## Extra Parameters +## 5. Extra Parameters Auth0's `/authorize` and `/v2/logout` endpoint support additional querystring parameters that aren't first-class citizens in this SDK. If you need to support any of those parameters, you can configure the SDK to do so. -### Extra parameters when logging in +### 5.1. Extra parameters when logging in In order to send extra parameters to Auth0's `/authorize` endpoint upon logging in, set `LoginParameters` when calling `AddAuth0WebAppAuthentication`. @@ -275,7 +293,7 @@ await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authentica > :information_source: Specifying any extra parameter when calling `HttpContext.ChallengeAsync` will take precedence over any globally configured parameter. -### Extra parameters when logging out +### 5.2. Extra parameters when logging out The same as with the login request, you can send parameters to the `logout` endpoint by calling `WithParameter` on the `LogoutAuthenticationPropertiesBuilder`. ```csharp @@ -288,7 +306,7 @@ await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme ``` > :information_source: The example above uses a parameter without an actual value, for more information see https://auth0.com/docs/logout/log-users-out-of-idps. -## Roles +## 6. Roles Before you can add [Role Based Access Control](https://auth0.com/docs/manage-users/access-control/rbac), you will need to ensure the required roles are created and assigned to the corresponding user(s). Follow the guidance explained in [assign-roles-to-users](https://auth0.com/docs/users/assign-roles-to-users) to ensure your user gets assigned the admin role. @@ -304,7 +322,7 @@ exports.onExecutePostLogin = async (event, api) => { > :information_source: As this SDK uses the OpenId Connect middleware, it expects roles to exist in the `http://schemas.microsoft.com/ws/2008/06/identity/claims/role` claim. -### Integrate roles in your ASP.NET application +### 6.1. Integrate roles in your ASP.NET application You can use the [Role based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles) mechanism to make sure that only the users with specific roles can access certain actions. Add the `[Authorize(Roles = "...")]` attribute to your controller action. ```csharp @@ -315,7 +333,7 @@ public IActionResult Admin() } ``` -## Backchannel Logout +## 7. Backchannel Logout Backchannel logout can be configured by calling `WithBackchannelLogout()` when calling `AddAuth0WebAppAuthentication`. @@ -363,7 +381,7 @@ The implementation of `CustomLogoutTokenHandler` will heaviliy depend on your si } ``` -### Distributed caching +### 7.1. Distributed caching If you want to connect the backchannel logout to a [distributed cache](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed), such as redis, to store the logout tokens, you can use: ```csharp @@ -392,11 +410,11 @@ public class CustomDistributedLogoutTokenHandler : ILogoutTokenHandler } ``` -## Blazor Server +## 8. Blazor Server The `Auth0-AspNetCore-Authentication` SDK works with Blazor Server in an almost identical way as how it's integrated in ASP.NET Core MVC. -### Register the SDK +### 8.1. Register the SDK Registering the SDK is identical as with ASP.NET Core MVC, where you should call `builder.Services.AddAuth0WebAppAuthentication` inside `Program.cs`, and ensure the authentication middleware (`UseAuthentication()` and `UseAuthorization()`) is registered. ```csharp @@ -415,7 +433,7 @@ app.UseAuthentication(); app.UseAuthorization(); ``` -### Add login and logout +### 8.2. Add login and logout Adding login and logout capabilities is different in the sense that you should create a `PageModel` implementation for both to allow the user to be redirected to Auth0. ```csharp @@ -446,8 +464,17 @@ public class LogoutModel : PageModel } ``` -## Using AuthenticationApiClient within a Controller -We can setup the dependency injection for `Auth0AuthenticationApiClient` using the `WithAuth0AuthenticationApiClient` extension method as below. +## 9. AuthenticationApiClient in your ASP.NET Core +While `Auth0.AspNetCore.Authentication` is an SDK for integrating Auth0 authentication flows into ASP.NET Core applications, +the `AuthenticationApiClient` which is a part of [`Auth0.AuthenticationApi`](https://www.nuget.org/packages/Auth0.AuthenticationApi/),provides comprehensive client functionality for interacting with Auth0's Authentication API endpoints. It handles user authentication, token management, and various OAuth 2.0/OpenID Connect flows. + +There can be multiple scenarios where the conventional authentication flows might not suffice. There could be scenarios where we need to directly call Auth0's APIs for specific tasks, such as user management or advanced authentication scenarios. +In such cases, `Auth0.AuthenticationApi` provides a powerful way to interact with Auth0's Authentication API endpoints. + +**Note :** While `Auth0.AspNetCore.Authentication` is focused on integrating Auth0 authentication flows into ASP.NET Core applications, the scope of `Auth0AuthenticationApiClient` goes beyond this SDK. The interaction with Auth0's APIs happen via `Auth0AuthenticationApiClient` (via `Auth0.AuthenticationApi` package internally). + +### 9.1. Configuring your application to use Auth0.AuthenticationApi +Your application needs to be configured to use the `Auth0.AuthenticationApi` client. This involves calling `services.WithAuth0AuthenticationApiClient()` in your `Startup.cs` or `Program.cs` file with the appropriate options as below. ```csharp public void ConfigureServices(IServiceCollection services) @@ -461,29 +488,67 @@ public void ConfigureServices(IServiceCollection services) } ``` -For using `Auth0AuthenticationApiClient`, request an instance of `IAuth0AuthenticationApiClient` in the constructor as below. +The above configuration will register the `Auth0AuthenticationApiClient` with the specified options, allowing you to use it throughout your application. + +### 9.2. Example Use Case 1: Passwordless Email Login + +A user might want to implement passwordless authentication in their ASP.NET Core application, such as sending a magic link to a user's email. This flow is not handled by `auth0-aspnet-core` authentication middleware and requires direct interaction with Auth0's Authentication API. The below example demonstrates how we can leverage `Auth0AuthenticationApiClient` to initiate the passwordless email flow. ```csharp -[ApiController] -[Route("api/[controller]")] -public class MyController : ControllerBase +// Assuming you have already configured the Auth0AuthenticationApiClient +public class PasswordlessController : ControllerBase { private readonly IAuth0AuthenticationApiClient _auth0Client; - public MyController(IAuth0AuthenticationApiClient auth0Client) + public PasswordlessController(IAuth0AuthenticationApiClient auth0Client) { _auth0Client = auth0Client; } - [HttpGet("revoke-refresh-token")] - public async Task RevokeRefreshToken() + [HttpPost("start-passwordless")] + public async Task StartPasswordless([FromBody] string email) + { + var request = new PasswordlessEmailRequest + { + Email = email, + Type = PasswordlessEmailRequestType.Link, + ClientId = "YOUR_CLIENT_ID", + ClientSecret = "YOUR_CLIENT_SECRET" + }; + + await _auth0Client.StartPasswordlessEmailFlowAsync(request); + return Ok("Magic link sent to email."); + } +} +``` + +### 9.3. Example Use Case 2: Revoking a Refresh Token + +In some scenarios, you may want to revoke a refresh token that was previously issued to a user, such as during logout or when you suspect the token has been compromised. You can use the `Auth0AuthenticationApiClient` to call Auth0's token revocation endpoint. + +Below is an example of how to revoke a refresh token using the client. + +```csharp +// Assuming you have already configured the Auth0AuthenticationApiClient +public class TokenRevocationService +{ + private readonly IAuth0AuthenticationApiClient _auth0Client; + + public TokenRevocationService(IAuth0AuthenticationApiClient auth0Client) + { + _auth0Client = auth0Client; + } + + public async Task RevokeRefreshTokenAsync(string refreshToken) { var request = new RevokeRefreshTokenRequest { - Token = "your_refresh_token" + RefreshToken = refreshToken, + ClientId = "YOUR_CLIENT_ID", + ClientSecret = "YOUR_CLIENT_SECRET" }; - await _auth0Client.RevokeRefreshTokenAsync(request); - return Ok(); + + await _auth0Client.RevokeTokenAsync(request); } } ``` \ No newline at end of file From a44e97b115cc819be72effd48c72e6caf54a8470 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Wed, 16 Jul 2025 16:53:55 +0530 Subject: [PATCH 10/10] Remove Auth0 prefix in AuthenticationApiClient --- EXAMPLES.md | 24 +++---- .../Auth0WebAppAuthenticationBuilder.cs | 12 ++-- .../AuthenticationApiClient.cs} | 12 ++-- .../IAuthenticationApiClient.cs} | 51 +++++++-------- .../Auth0AuthenticationApiClientTests.cs | 65 +++++++++---------- .../Auth0WebAppAuthenticationBuilderTests.cs | 20 +++--- 6 files changed, 89 insertions(+), 95 deletions(-) rename src/Auth0.AspNetCore.Authentication/{Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs => AuthenticationApi/AuthenticationApiClient.cs} (93%) rename src/Auth0.AspNetCore.Authentication/{Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs => AuthenticationApi/IAuthenticationApiClient.cs} (55%) diff --git a/EXAMPLES.md b/EXAMPLES.md index c2210fa..e18047e 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -471,15 +471,15 @@ the `AuthenticationApiClient` which is a part of [`Auth0.AuthenticationApi`](htt There can be multiple scenarios where the conventional authentication flows might not suffice. There could be scenarios where we need to directly call Auth0's APIs for specific tasks, such as user management or advanced authentication scenarios. In such cases, `Auth0.AuthenticationApi` provides a powerful way to interact with Auth0's Authentication API endpoints. -**Note :** While `Auth0.AspNetCore.Authentication` is focused on integrating Auth0 authentication flows into ASP.NET Core applications, the scope of `Auth0AuthenticationApiClient` goes beyond this SDK. The interaction with Auth0's APIs happen via `Auth0AuthenticationApiClient` (via `Auth0.AuthenticationApi` package internally). +**Note :** For interactions with the Auth0 API's end-points we leverage the rich support and infrastructure provided by `Auth0.AuthenticationApi` (via `Auth0.AspNetCore.Authentication.AuthenticationApiClient`). There is no implementation in `Auth0.AspNetCore.Authentication` that directly talks to Auth0's API endpoints. ### 9.1. Configuring your application to use Auth0.AuthenticationApi -Your application needs to be configured to use the `Auth0.AuthenticationApi` client. This involves calling `services.WithAuth0AuthenticationApiClient()` in your `Startup.cs` or `Program.cs` file with the appropriate options as below. +Your application needs to be configured to use the `Auth0.AuthenticationApi` client. This involves calling `services.WithAuthenticationApiClient()` in your `Startup.cs` or `Program.cs` file with the appropriate options as below. ```csharp public void ConfigureServices(IServiceCollection services) { - services.WithAuth0AuthenticationApiClient(options => + services.WithAuthenticationApiClient(options => { options.Domain = Configuration["Auth0:Domain"]; options.ClientId = Configuration["Auth0:ClientId"]; @@ -488,19 +488,19 @@ public void ConfigureServices(IServiceCollection services) } ``` -The above configuration will register the `Auth0AuthenticationApiClient` with the specified options, allowing you to use it throughout your application. +The above configuration will register the `AuthenticationApiClient` with the specified options, allowing you to use it throughout your application. ### 9.2. Example Use Case 1: Passwordless Email Login -A user might want to implement passwordless authentication in their ASP.NET Core application, such as sending a magic link to a user's email. This flow is not handled by `auth0-aspnet-core` authentication middleware and requires direct interaction with Auth0's Authentication API. The below example demonstrates how we can leverage `Auth0AuthenticationApiClient` to initiate the passwordless email flow. +A user might want to implement passwordless authentication in their ASP.NET Core application, such as sending a magic link to a user's email. This flow is not handled by `auth0-aspnet-core` authentication middleware and requires direct interaction with Auth0's Authentication API. The below example demonstrates how we can leverage `AuthenticationApiClient` to initiate the passwordless email flow. ```csharp -// Assuming you have already configured the Auth0AuthenticationApiClient +// Assuming you have already configured the AuthenticationApiClient public class PasswordlessController : ControllerBase { - private readonly IAuth0AuthenticationApiClient _auth0Client; + private readonly IAuthenticationApiClient _auth0Client; - public PasswordlessController(IAuth0AuthenticationApiClient auth0Client) + public PasswordlessController(IAuthenticationApiClient auth0Client) { _auth0Client = auth0Client; } @@ -524,17 +524,17 @@ public class PasswordlessController : ControllerBase ### 9.3. Example Use Case 2: Revoking a Refresh Token -In some scenarios, you may want to revoke a refresh token that was previously issued to a user, such as during logout or when you suspect the token has been compromised. You can use the `Auth0AuthenticationApiClient` to call Auth0's token revocation endpoint. +In some scenarios, you may want to revoke a refresh token that was previously issued to a user, such as during logout or when you suspect the token has been compromised. You can use the `AuthenticationApiClient` to call Auth0's token revocation endpoint. Below is an example of how to revoke a refresh token using the client. ```csharp -// Assuming you have already configured the Auth0AuthenticationApiClient +// Assuming you have already configured the AuthenticationApiClient public class TokenRevocationService { - private readonly IAuth0AuthenticationApiClient _auth0Client; + private readonly IAuthenticationApiClient _auth0Client; - public TokenRevocationService(IAuth0AuthenticationApiClient auth0Client) + public TokenRevocationService(IAuthenticationApiClient auth0Client) { _auth0Client = auth0Client; } diff --git a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs index afd8741..31719eb 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs +++ b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs @@ -6,9 +6,9 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; using Auth0.AspNetCore.Authentication.BackchannelLogout; -using Auth0.AuthenticationApi; +using AuthenticationApiClient = Auth0.AuthenticationApi.AuthenticationApiClient; +using IAuthenticationApiClient = Auth0.AspNetCore.Authentication.AuthenticationApi.IAuthenticationApiClient; namespace Auth0.AspNetCore.Authentication { @@ -66,16 +66,16 @@ public Auth0WebAppAuthenticationBuilder WithBackchannelLogout() } /// - /// Configures the to leverage Auth0.AuthenticationApi + /// Configures the to leverage Auth0.AuthenticationApi /// /// An instance of - public Auth0WebAppAuthenticationBuilder WithAuth0AuthenticationApiClient() + public Auth0WebAppAuthenticationBuilder WithAuthenticationApiClient() { - _services.AddTransient( + _services.AddTransient( sp => { var options = sp.GetRequiredService>().Value; - return new Auth0AuthenticationApiClient.Auth0AuthenticationApiClient( + return new AuthenticationApi.AuthenticationApiClient( new AuthenticationApiClient(new Uri($"https://{options.Domain}"))); } ); diff --git a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs b/src/Auth0.AspNetCore.Authentication/AuthenticationApi/AuthenticationApiClient.cs similarity index 93% rename from src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs rename to src/Auth0.AspNetCore.Authentication/AuthenticationApi/AuthenticationApiClient.cs index f151f30..53e4375 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/Auth0AuthenticationApiClient.cs +++ b/src/Auth0.AspNetCore.Authentication/AuthenticationApi/AuthenticationApiClient.cs @@ -2,21 +2,19 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - -using Auth0.AuthenticationApi; using Auth0.AuthenticationApi.Models; using Auth0.AuthenticationApi.Models.Ciba; using Auth0.AuthenticationApi.Models.Mfa; using Models = Auth0.AuthenticationApi.Models; -namespace Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; +namespace Auth0.AspNetCore.Authentication.AuthenticationApi; -/// -public class Auth0AuthenticationApiClient : IAuth0AuthenticationApiClient +/// +public class AuthenticationApiClient : IAuthenticationApiClient { - private readonly IAuthenticationApiClient _authenticationApiClient; + private readonly Auth0.AuthenticationApi.IAuthenticationApiClient _authenticationApiClient; - public Auth0AuthenticationApiClient(IAuthenticationApiClient authenticationApiClient) + public AuthenticationApiClient(Auth0.AuthenticationApi.IAuthenticationApiClient authenticationApiClient) { _authenticationApiClient = authenticationApiClient ?? throw new ArgumentNullException(nameof(authenticationApiClient)); } diff --git a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs b/src/Auth0.AspNetCore.Authentication/AuthenticationApi/IAuthenticationApiClient.cs similarity index 55% rename from src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs rename to src/Auth0.AspNetCore.Authentication/AuthenticationApi/IAuthenticationApiClient.cs index a59fd13..7b93e9c 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0AuthenticationApiClient/IAuth0AuthenticationApiClient.cs +++ b/src/Auth0.AspNetCore.Authentication/AuthenticationApi/IAuthenticationApiClient.cs @@ -1,91 +1,88 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; - using Auth0.AuthenticationApi.Models.Ciba; using Auth0.AuthenticationApi.Models.Mfa; - using Models = Auth0.AuthenticationApi.Models; using CancellationToken = System.Threading.CancellationToken; -using IAuthenticationApiClient = Auth0.AuthenticationApi.IAuthenticationApiClient; -namespace Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; +namespace Auth0.AspNetCore.Authentication.AuthenticationApi; /// -/// Encapsulates the to provide a strongly-typed interface for +/// Encapsulates the to provide a strongly-typed interface for /// Auth0 Authentication API related operations. /// -public interface IAuth0AuthenticationApiClient : IDisposable +public interface IAuthenticationApiClient : IDisposable { - /// + /// Uri BaseUri { get; } - /// + /// Task ChangePasswordAsync(Models.ChangePasswordRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(Models.AuthorizationCodeTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(Models.AuthorizationCodePkceTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(Models.ClientCredentialsTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(Models.RefreshTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(Models.PasswordlessEmailTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(Models.PasswordlessSmsTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task RevokeRefreshTokenAsync(Models.RevokeRefreshTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task SignupUserAsync(Models.SignupUserRequest request, CancellationToken cancellationToken = default); - /// + /// Task StartPasswordlessEmailFlowAsync( Models.PasswordlessEmailRequest request, CancellationToken cancellationToken = default); - /// + /// Task StartPasswordlessSmsFlowAsync(Models.PasswordlessSmsRequest request, CancellationToken cancellationToken = default); - /// + /// Task ClientInitiatedBackchannelAuthorization( ClientInitiatedBackchannelAuthorizationRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync( ClientInitiatedBackchannelAuthorizationTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task AssociateMfaAuthenticatorAsync(AssociateMfaAuthenticatorRequest request, CancellationToken cancellationToken = default); - /// + /// Task> ListMfaAuthenticatorsAsync(string accessToken, CancellationToken cancellationToken = default); - /// + /// Task DeleteMfaAuthenticatorAsync(DeleteMfaAuthenticatorRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(MfaOobTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(MfaOtpTokenRequest request, CancellationToken cancellationToken = default); - /// + /// Task GetTokenAsync(MfaRecoveryCodeRequest request, CancellationToken cancellationToken = default); - /// + /// Task MfaChallengeAsync(MfaChallengeRequest request, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0AuthenticationApiClientTests.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0AuthenticationApiClientTests.cs index e3e7e0f..5ff0145 100644 --- a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0AuthenticationApiClientTests.cs +++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0AuthenticationApiClientTests.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - -using Auth0.AuthenticationApi; +using Auth0.AspNetCore.Authentication.AuthenticationApi; using Auth0.AuthenticationApi.Models; using Auth0.AuthenticationApi.Models.Ciba; using Auth0.AuthenticationApi.Models.Mfa; @@ -15,21 +14,21 @@ namespace Auth0.AspNetCore.Authentication.IntegrationTests; -public class Auth0AuthenticationApiClientTests +public class AuthenticationApiClientTests { - private readonly Mock _mockAuthenticationApiClient; - private readonly Auth0AuthenticationApiClient.Auth0AuthenticationApiClient _auth0AuthenticationApiClient; + private readonly Mock _mockAuthenticationApiClient; + private readonly AuthenticationApiClient _authenticationApiClient; - public Auth0AuthenticationApiClientTests() + public AuthenticationApiClientTests() { - _mockAuthenticationApiClient = new Mock(); - _auth0AuthenticationApiClient = new Auth0AuthenticationApiClient.Auth0AuthenticationApiClient(_mockAuthenticationApiClient.Object); + _mockAuthenticationApiClient = new Mock(); + _authenticationApiClient = new AuthenticationApiClient(_mockAuthenticationApiClient.Object); } [Fact] public void Constructor_When_AuthenticationApiClient_Is_Null_Throws_ArgumentNullException() { - var act = () => new Auth0AuthenticationApiClient.Auth0AuthenticationApiClient(null); + var act = () => new AuthenticationApiClient(null); act.Should().Throw() .And.ParamName.Should().Be("authenticationApiClient"); @@ -38,7 +37,7 @@ public void Constructor_When_AuthenticationApiClient_Is_Null_Throws_ArgumentNull [Fact] public void Constructor_When_AuthenticationApiClient_Is_Valid_Creates_Instance() { - var result = new Auth0AuthenticationApiClient.Auth0AuthenticationApiClient(_mockAuthenticationApiClient.Object); + var result = new AuthenticationApiClient(_mockAuthenticationApiClient.Object); result.Should().NotBeNull(); } @@ -46,7 +45,7 @@ public void Constructor_When_AuthenticationApiClient_Is_Valid_Creates_Instance() [Fact] public void Dispose_Calls_Dispose_On_Underlying_Client() { - _auth0AuthenticationApiClient.Dispose(); + _authenticationApiClient.Dispose(); _mockAuthenticationApiClient.Verify(x => x.Dispose(), Times.Once); } @@ -56,7 +55,7 @@ public void BaseUri_Return_BaseUri_From_Underlying_Client() var expectedUri = new Uri("https://example.auth0.com"); _mockAuthenticationApiClient.Setup(x => x.BaseUri).Returns(expectedUri); - var result = _auth0AuthenticationApiClient.BaseUri; + var result = _authenticationApiClient.BaseUri; result.Should().Be(expectedUri); _mockAuthenticationApiClient.Verify(x => x.BaseUri, Times.Once); @@ -71,7 +70,7 @@ public async Task ChangePasswordAsync_Calls_Underlying_Client_With_Request() _mockAuthenticationApiClient.Setup(x => x.ChangePasswordAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.ChangePasswordAsync(request, cancellationToken); + var result = await _authenticationApiClient.ChangePasswordAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -87,7 +86,7 @@ public async Task ChangePasswordAsync_With_Default_CancellationToken_Calls_Under x => x.ChangePasswordAsync(request, default)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.ChangePasswordAsync(request); + var result = await _authenticationApiClient.ChangePasswordAsync(request); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -103,7 +102,7 @@ public async Task GetTokenAsync_With_AuthorizationCodeTokenRequest_Calls_Underly _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -119,7 +118,7 @@ public async Task GetTokenAsync_WithAuthorizationCodePkceTokenRequest_Calls_Unde _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -135,7 +134,7 @@ public async Task GetTokenAsync_With_ClientCredentialsTokenRequest_Calls_Underly _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -151,7 +150,7 @@ public async Task GetTokenAsync_With_RefreshTokenRequest_Calls_Underlying_Client _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -167,7 +166,7 @@ public async Task GetTokenAsync_With_PasswordlessEmailTokenRequest_Calls_Underly _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -183,7 +182,7 @@ public async Task GetTokenAsync_With_PasswordlessSmsTokenRequest_Calls_Underlyin _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -199,7 +198,7 @@ public async Task RevokeRefreshTokenAsync_Calls_Underlying_Client() x => x.RevokeRefreshTokenAsync(request, cancellationToken)) .Returns(Task.CompletedTask); - await _auth0AuthenticationApiClient.RevokeRefreshTokenAsync(request, cancellationToken); + await _authenticationApiClient.RevokeRefreshTokenAsync(request, cancellationToken); _mockAuthenticationApiClient.Verify( x => x.RevokeRefreshTokenAsync(request, cancellationToken), Times.Once); @@ -215,7 +214,7 @@ public async Task SignupUserAsync_Calls_Underlying_Client() x => x.SignupUserAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.SignupUserAsync(request, cancellationToken); + var result = await _authenticationApiClient.SignupUserAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -233,7 +232,7 @@ public async Task StartPasswordlessEmailFlowAsync_Calls_Underlying_Client() .ReturnsAsync(expectedResult); var result = - await _auth0AuthenticationApiClient.StartPasswordlessEmailFlowAsync(request, cancellationToken); + await _authenticationApiClient.StartPasswordlessEmailFlowAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -251,7 +250,7 @@ public async Task StartPasswordlessSmsFlowAsync_Calls_Underlying_Client() .ReturnsAsync(expectedResult); var result = - await _auth0AuthenticationApiClient.StartPasswordlessSmsFlowAsync(request, cancellationToken); + await _authenticationApiClient.StartPasswordlessSmsFlowAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -269,7 +268,7 @@ public async Task ClientInitiatedBackchannelAuthorization_Calls_Underlying_Clien .ReturnsAsync(expectedResult); var result = - await _auth0AuthenticationApiClient.ClientInitiatedBackchannelAuthorization(request, cancellationToken); + await _authenticationApiClient.ClientInitiatedBackchannelAuthorization(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -286,7 +285,7 @@ public async Task GetTokenAsync_WithClientInitiatedBackchannelAuthorizationToken .ReturnsAsync(expectedResult); var result = - await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -303,7 +302,7 @@ public async Task AssociateMfaAuthenticatorAsync_Calls_Underlying_Client() .ReturnsAsync(expectedResult); var result = - await _auth0AuthenticationApiClient.AssociateMfaAuthenticatorAsync(request, cancellationToken); + await _authenticationApiClient.AssociateMfaAuthenticatorAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -320,7 +319,7 @@ public async Task ListMfaAuthenticatorsAsync_Calls_Underlying_Client() x => x.ListMfaAuthenticatorsAsync(accessToken, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.ListMfaAuthenticatorsAsync(accessToken, cancellationToken); + var result = await _authenticationApiClient.ListMfaAuthenticatorsAsync(accessToken, cancellationToken); result.Should().BeEquivalentTo(expectedResult); _mockAuthenticationApiClient.Verify( @@ -335,7 +334,7 @@ public async Task DeleteMfaAuthenticatorAsync_Calls_Underlying_Client() _mockAuthenticationApiClient.Setup(x => x.DeleteMfaAuthenticatorAsync(request, cancellationToken)) .Returns(Task.CompletedTask); - await _auth0AuthenticationApiClient.DeleteMfaAuthenticatorAsync(request, cancellationToken); + await _authenticationApiClient.DeleteMfaAuthenticatorAsync(request, cancellationToken); _mockAuthenticationApiClient.Verify( x => x.DeleteMfaAuthenticatorAsync(request, cancellationToken), Times.Once); @@ -350,7 +349,7 @@ public async Task GetTokenAsync_WithMfaOobTokenRequest_Calls_Underlying_Client() _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -366,7 +365,7 @@ public async Task GetTokenAsync_WithMfaOtpTokenRequest_Calls_Underlying_Client() _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -382,7 +381,7 @@ public async Task GetTokenAsync_WithMfaRecoveryCodeRequest_Calls_Underlying_Clie _mockAuthenticationApiClient.Setup(x => x.GetTokenAsync(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.GetTokenAsync(request, cancellationToken); + var result = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify( @@ -398,7 +397,7 @@ public async Task MfaChallengeAsync_Calls_Underlying_Client() _mockAuthenticationApiClient.Setup(x => x.MfaChallenge(request, cancellationToken)) .ReturnsAsync(expectedResult); - var result = await _auth0AuthenticationApiClient.MfaChallengeAsync(request, cancellationToken); + var result = await _authenticationApiClient.MfaChallengeAsync(request, cancellationToken); result.Should().Be(expectedResult); _mockAuthenticationApiClient.Verify(x => x.MfaChallenge(request, cancellationToken), Times.Once); diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0WebAppAuthenticationBuilderTests.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0WebAppAuthenticationBuilderTests.cs index 53a1fc6..13a9231 100644 --- a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0WebAppAuthenticationBuilderTests.cs +++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0WebAppAuthenticationBuilderTests.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Options; using Xunit; -using Auth0.AspNetCore.Authentication.Auth0AuthenticationApiClient; +using Auth0.AspNetCore.Authentication.AuthenticationApi; namespace Auth0.AspNetCore.Authentication.IntegrationTests; @@ -18,9 +18,9 @@ public void WithAuth0AuthenticationApiClient_Should_Register_IAuth0Authenticatio var options = new Auth0WebAppOptions { Domain = "test-domain.auth0.com" }; var builder = new Auth0WebAppAuthenticationBuilder(services, options); - builder.WithAuth0AuthenticationApiClient(); + builder.WithAuthenticationApiClient(); - var serviceDescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(IAuth0AuthenticationApiClient)); + var serviceDescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(IAuthenticationApiClient)); serviceDescriptor.Should().NotBeNull(); serviceDescriptor?.Lifetime.Should().Be(ServiceLifetime.Transient); } @@ -32,7 +32,7 @@ public void WithAuth0AuthenticationApiClient_Should_Return_Same_Builder_Instance var options = new Auth0WebAppOptions { Domain = "test-domain.auth0.com" }; var builder = new Auth0WebAppAuthenticationBuilder(services, options); - var result = builder.WithAuth0AuthenticationApiClient(); + var result = builder.WithAuthenticationApiClient(); result.Should().BeSameAs(builder); } @@ -45,13 +45,13 @@ public void WithAuth0AuthenticationApiClient_Should_Create_Client_With_Correct_D services.AddSingleton(Options.Create(options)); var builder = new Auth0WebAppAuthenticationBuilder(services, options); - builder.WithAuth0AuthenticationApiClient(); + builder.WithAuthenticationApiClient(); var serviceProvider = services.BuildServiceProvider(); - var client = serviceProvider.GetRequiredService(); + var client = serviceProvider.GetRequiredService(); client.Should().NotBeNull(); - client.Should().BeOfType(); + client.Should().BeOfType(); } [Fact] @@ -61,10 +61,10 @@ public void WithAuth0AuthenticationApiClient_When_Called_Multiple_Times_Should_R var options = new Auth0WebAppOptions { Domain = "test-domain.auth0.com" }; var builder = new Auth0WebAppAuthenticationBuilder(services, options); - builder.WithAuth0AuthenticationApiClient(); - builder.WithAuth0AuthenticationApiClient(); + builder.WithAuthenticationApiClient(); + builder.WithAuthenticationApiClient(); - var serviceDescriptors = services.Where(s => s.ServiceType == typeof(IAuth0AuthenticationApiClient)); + var serviceDescriptors = services.Where(s => s.ServiceType == typeof(IAuthenticationApiClient)); serviceDescriptors.Should().HaveCount(2); } } \ No newline at end of file