From 7cfda3af70e17fb17287ca18c99b86abb6b288eb Mon Sep 17 00:00:00 2001 From: kailash-b Date: Tue, 13 May 2025 17:57:52 +0530 Subject: [PATCH 1/8] 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 603e415..0e16ecc 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj +++ b/src/Auth0.AspNetCore.Authentication/Auth0.AspNetCore.Authentication.csproj @@ -5,6 +5,7 @@ + From fb4ec7e899deea081770dcd77396509e1f117503 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Sun, 18 May 2025 22:25:34 +0530 Subject: [PATCH 2/8] Reference IAuthenticationApiClient as a dependency --- .../Auth0WebAppAuthenticationBuilder.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs index 945ac05..d0edda2 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs +++ b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs @@ -5,6 +5,7 @@ using System; using System.Threading.Tasks; using Auth0.AspNetCore.Authentication.BackchannelLogout; +using Auth0.AuthenticationApi; namespace Auth0.AspNetCore.Authentication { @@ -61,6 +62,23 @@ public Auth0WebAppAuthenticationBuilder WithBackchannelLogout() return this; } + /// + /// Configures the IAuthenticationApiClient to leverage Auth0.AuthenticationApi + /// + /// + public Auth0WebAppAuthenticationBuilder WithAuthenticationApiClient() + { + _services.AddSingleton( + sp => + { + var options = sp.GetRequiredService>().Value; + return new AuthenticationApiClient(new Uri($"https://{options.Domain}")); + } + ); + _services.AddTransient(); + return this; + } + private void EnableWithAccessToken(Action configureOptions) { var auth0WithAccessTokensOptions = new Auth0WebAppWithAccessTokenOptions(); From ac8f7f004056f7585ed1b6313b848b0a26d99a4e Mon Sep 17 00:00:00 2001 From: kailash-b Date: Mon, 19 May 2025 16:19:01 +0530 Subject: [PATCH 3/8] Add IAuth0CibaService and its implementation --- .../Auth0CibaService.cs | 119 ++++++++++++++++++ .../IAuth0CibaService.cs | 75 +++++++++++ 2 files changed, 194 insertions(+) create mode 100644 src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/Auth0CibaService.cs create mode 100644 src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/IAuth0CibaService.cs diff --git a/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/Auth0CibaService.cs b/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/Auth0CibaService.cs new file mode 100644 index 0000000..2522d3b --- /dev/null +++ b/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/Auth0CibaService.cs @@ -0,0 +1,119 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using Auth0.AuthenticationApi; +using Auth0.AuthenticationApi.Models.Ciba; +using Auth0.Core.Exceptions; + +namespace Auth0.AspNetCore.Authentication.ClientInitiatedBackChannelAuthentication; + +internal class Auth0CibaService : IAuth0CibaService +{ + private readonly IAuthenticationApiClient _authenticationApiClient; + private readonly Auth0WebAppOptions _options; + private readonly ILogger _logger; + + public Auth0CibaService( + IAuthenticationApiClient authenticationApiClient, + IOptions optionsAccessor, + ILogger logger) + { + _authenticationApiClient = authenticationApiClient; + _options = optionsAccessor.Value; + _logger = logger; + } + + public async Task InitiateAuthenticationAsync(CibaInitiationRequest request) + { + try + { + var cibaRequest = new ClientInitiatedBackchannelAuthorizationRequest + { + ClientId = _options.ClientId, + ClientSecret = _options.ClientSecret, + ClientAssertionSecurityKey = _options.ClientAssertionSecurityKey, + ClientAssertionSecurityKeyAlgorithm = _options.ClientAssertionSecurityKeyAlgorithm, + Audience = request.Audience, + LoginHint = request.LoginHint, + Scope = request.Scope, + RequestExpiry = request.RequestExpiry, + AdditionalProperties = request.AdditionalProperties, + BindingMessage = request.BindingMessage, + }; + + _logger.LogInformation("Initiating CIBA request!"); + var response = await _authenticationApiClient.ClientInitiatedBackchannelAuthorization(cibaRequest); + + return new CibaInitiationDetails() + { + AuthRequestId = response.AuthRequestId, + ExpiresIn = response.ExpiresIn, + Interval = response.Interval, + IsSuccessful = true, + ErrorMessage = null + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error initiating CIBA request"); + throw; + } + } + + public async Task PollForTokensAsync(CibaInitiationDetails initDetails) + { + var request = new ClientInitiatedBackchannelAuthorizationTokenRequest() + { + ClientId = _options.ClientId, + ClientSecret = _options.ClientSecret, + ClientAssertionSecurityKey = _options.ClientAssertionSecurityKey, + ClientAssertionSecurityKeyAlgorithm = _options.ClientAssertionSecurityKeyAlgorithm, + AuthRequestId = initDetails.AuthRequestId + }; + + while (true) + { + _logger.LogDebug($"Polling CIBA token endpoint for auth_req_id: {initDetails.AuthRequestId} "); + try + { + var response = await _authenticationApiClient.GetTokenAsync(request); + + return new CibaCompletionDetails + { + AccessToken = response.AccessToken, + IdToken = response.IdToken, + TokenType = response.TokenType, + Scope = response.Scope, + ExpiresIn = response.ExpiresIn, + RefreshToken = response.RefreshToken, + IsSuccessful = true, + IsAuthenticationPending = false, + }; + } + catch (ErrorApiException ex) + { + _logger.LogWarning( + ex, + $"CIBA polling error for auth_req_id: {initDetails.AuthRequestId}." + + $" Error: {ex.ApiError.Error}, Description: {ex.ApiError.Message}"); + + if (ex.ApiError.Error.Contains("authorization_pending", StringComparison.OrdinalIgnoreCase)) + { + await Task.Delay(TimeSpan.FromSeconds(initDetails.Interval ?? 5)); + continue; + } + + return new CibaCompletionDetails + { + IsAuthenticationPending = false, + Error = ex.ApiError.Error, + ErrorMessage = ex.ApiError.Message, + IsSuccessful = false + }; + } + } + } +} \ No newline at end of file diff --git a/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/IAuth0CibaService.cs b/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/IAuth0CibaService.cs new file mode 100644 index 0000000..f3e5cfe --- /dev/null +++ b/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/IAuth0CibaService.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +using Auth0.AuthenticationApi.Models.Ciba; + +namespace Auth0.AspNetCore.Authentication.ClientInitiatedBackChannelAuthentication; + +public class CibaInitiationDetails +{ + public string? AuthRequestId { get; init; } + public int ExpiresIn { get; init; } + public int? Interval { get; init; } + public bool IsSuccessful { get; init; } = true; + public string? ErrorMessage { get; init; } +} + +public class CibaInitiationRequest +{ + /// + public string? BindingMessage { get; set; } + + /// + public LoginHint? LoginHint { get; set; } + + /// + public string? Scope { get; set; } + + /// + public string? Audience { get; set; } + + /// + public int? RequestExpiry { get; set; } + + /// + public IDictionary AdditionalProperties { get; set; } = new Dictionary(); +} + +public class CibaCompletionDetails : ClientInitiatedBackchannelAuthorizationTokenResponse +{ + /// + /// Signifies if the authentication is pending. + /// + public bool IsAuthenticationPending { get; set; } = true; + + /// + /// Signifies if the authentication is successful. + /// + public bool IsSuccessful { get; set; } = false; + + /// + /// The error received in case of expiry or consent rejection + /// + public string? Error { get; set; } + + /// + /// The error message received in case of expiry or consent rejection + /// + public string? ErrorMessage { get; set; } +} + +public interface IAuth0CibaService +{ + /// + /// Initiates a Client-Initiated Backchannel Authentication (CIBA) flow. + /// + /// Contains the information required for initiating the CIBA request. + Task InitiateAuthenticationAsync(CibaInitiationRequest request); + + /// + /// Polls the token endpoint to check the status of a CIBA request and retrieve tokens upon completion. + /// + /// The information required to poll for the CIBA status. + /// Details about the CIBA completion status or the retrieved tokens. + Task PollForTokensAsync(CibaInitiationDetails cibaInitiationDetails); +} \ No newline at end of file From 414fd03c4de2c3b8b6f23592aaca8c2eaf0ff5b3 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Mon, 19 May 2025 16:19:25 +0530 Subject: [PATCH 4/8] Add test cases for Auth0CibaService --- .../Auth0CibaServiceTest.cs | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0CibaServiceTest.cs diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0CibaServiceTest.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0CibaServiceTest.cs new file mode 100644 index 0000000..c25c095 --- /dev/null +++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0CibaServiceTest.cs @@ -0,0 +1,192 @@ +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +using Moq; +using FluentAssertions; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Xunit; + +using Auth0.AspNetCore.Authentication.ClientInitiatedBackChannelAuthentication; +using Auth0.AuthenticationApi; +using Auth0.AuthenticationApi.Models.Ciba; +using Auth0.Core.Exceptions; + +namespace Auth0.AspNetCore.Authentication.IntegrationTests; + +public class Auth0CibaServiceTest +{ + private readonly IAuth0CibaService _auth0CibaService; + private readonly Mock _mockAuthenticationApiClient = new(); + + public Auth0CibaServiceTest() + { + _auth0CibaService = new Auth0CibaService(_mockAuthenticationApiClient.Object, Options.Create( + new Auth0WebAppOptions() + { + ClientId = "clientId", + ClientSecret = "secret" + }), new NullLogger()); + } + + [Fact] + public async Task InitiateAuthenticationAsync_ReturnsCibaInitiationDetails_OnSuccessfulRequest() + { + // Arrange + var request = new CibaInitiationRequest + { + Audience = "test-audience", + LoginHint = new LoginHint { Format = "test-format", Issuer = "test-issuer", Subject = "test-subject" }, + Scope = "openid", + RequestExpiry = 300, + AdditionalProperties = null, + BindingMessage = "test-binding-message" + }; + + var cibaResponse = new ClientInitiatedBackchannelAuthorizationResponse + { + AuthRequestId = "test-auth-request-id", + ExpiresIn = 300, + Interval = 5 + }; + + _mockAuthenticationApiClient + .Setup(client => + client.ClientInitiatedBackchannelAuthorization( + It.IsAny(), It.IsAny())) + .ReturnsAsync(cibaResponse); + + // Act + var result = await _auth0CibaService.InitiateAuthenticationAsync(request); + + // Assert + result.Should().NotBeNull(); + result.IsSuccessful.Should().BeTrue(); + cibaResponse.Interval.Should().Be(result.Interval); + cibaResponse.ExpiresIn.Should().Be(result.ExpiresIn); + cibaResponse.AuthRequestId.Should().Be(result.AuthRequestId); + } + + [Fact] + public async Task InitiateAuthenticationAsync_ThrowsException_OnApiError() + { + // Arrange + var request = new CibaInitiationRequest + { + Audience = "test-audience", + LoginHint = new LoginHint { Format = "test-format", Issuer = "test-issuer", Subject = "test-subject" }, + Scope = "openid" + }; + + _mockAuthenticationApiClient + .Setup(client => + client.ClientInitiatedBackchannelAuthorization( + It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception("API error")); + + // Act & Assert + await Assert.ThrowsAsync(() => _auth0CibaService.InitiateAuthenticationAsync(request)); + } + + [Fact] + public async Task PollForTokensAsync_ReturnsCibaCompletionDetails_OnSuccessfulTokenRetrieval() + { + // Arrange + var initDetails = new CibaInitiationDetails + { + AuthRequestId = "test-auth-request-id", + Interval = 5 + }; + + var tokenResponse = new ClientInitiatedBackchannelAuthorizationTokenResponse + { + AccessToken = "test-access-token", + IdToken = "test-id-token", + TokenType = "Bearer", + Scope = "openid", + ExpiresIn = 3600, + RefreshToken = "test-refresh-token" + }; + + _mockAuthenticationApiClient + .Setup(client => client.GetTokenAsync(It.IsAny(), + It.IsAny())) + .ReturnsAsync(tokenResponse); + + // Act + var result = await _auth0CibaService.PollForTokensAsync(initDetails); + + // Assert + result.Should().NotBeNull(); + result.IsSuccessful.Should().BeTrue(); + tokenResponse.AccessToken.Should().BeEquivalentTo(result.AccessToken); + tokenResponse.IdToken.Should().BeEquivalentTo(result.IdToken); + tokenResponse.TokenType.Should().BeEquivalentTo(result.TokenType); + tokenResponse.Scope.Should().BeEquivalentTo(result.Scope); + tokenResponse.ExpiresIn.Should().Be(result.ExpiresIn); + tokenResponse.RefreshToken.Should().BeEquivalentTo(result.RefreshToken); + } + + [Fact] + public async Task PollForTokensAsync_ReturnsPendingStatus_OnAuthorizationPendingError() + { + // Arrange + var initDetails = new CibaInitiationDetails + { + AuthRequestId = "test-auth-request-id", + Interval = 1 + }; + + _mockAuthenticationApiClient + .SetupSequence(client => + client.GetTokenAsync(It.IsAny(), + It.IsAny())) + .ThrowsAsync(new ErrorApiException(HttpStatusCode.InternalServerError, new ApiError + { Error = "authorization_pending", Message = "Authorization is pending" })) + .ReturnsAsync(new ClientInitiatedBackchannelAuthorizationTokenResponse() + { + AccessToken = "test-access-token", + IdToken = "test-id-token", + TokenType = "Bearer", + Scope = "openid", + ExpiresIn = 3600 + }); + + // Act + var result = await _auth0CibaService.PollForTokensAsync(initDetails); + + // Assert + result.Should().NotBeNull(); + result.IsSuccessful.Should().BeTrue(); + result.AccessToken.Should().Be("test-access-token"); + } + + [Fact] + public async Task PollForTokensAsync_ReturnsErrorDetails_OnNonPendingError() + { + // Arrange + var initDetails = new CibaInitiationDetails + { + AuthRequestId = "test-auth-request-id", + Interval = 5 + }; + + var apiError = new ApiError { Error = "invalid_request", Message = "Invalid request" }; + + _mockAuthenticationApiClient + .Setup(client => client.GetTokenAsync(It.IsAny(), + It.IsAny())) + .ThrowsAsync(new ErrorApiException(HttpStatusCode.InternalServerError, apiError)); + + // Act + var result = await _auth0CibaService.PollForTokensAsync(initDetails); + + // Assert + result.Should().NotBeNull(); + result.IsSuccessful.Should().BeFalse(); + apiError.Error.Should().BeEquivalentTo(result.Error); + apiError.Message.Should().BeEquivalentTo(result.ErrorMessage); + } +} \ No newline at end of file From bca3fc743b5de85459ee2a7064d99d88c381f1af Mon Sep 17 00:00:00 2001 From: kailash-b Date: Mon, 19 May 2025 16:21:08 +0530 Subject: [PATCH 5/8] Register Auth0CibaService in DI --- .../Auth0WebAppAuthenticationBuilder.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs index d0edda2..4ce4861 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs +++ b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs @@ -5,6 +5,7 @@ using System; using System.Threading.Tasks; using Auth0.AspNetCore.Authentication.BackchannelLogout; +using Auth0.AspNetCore.Authentication.ClientInitiatedBackChannelAuthentication; using Auth0.AuthenticationApi; namespace Auth0.AspNetCore.Authentication @@ -69,16 +70,21 @@ public Auth0WebAppAuthenticationBuilder WithBackchannelLogout() public Auth0WebAppAuthenticationBuilder WithAuthenticationApiClient() { _services.AddSingleton( - sp => - { - var options = sp.GetRequiredService>().Value; - return new AuthenticationApiClient(new Uri($"https://{options.Domain}")); - } - ); + _ => new AuthenticationApiClient(new Uri($"https://{_options.Domain}"))); _services.AddTransient(); return this; } + /// + /// Configures the IAuth0CibaService to leverage the CIBA features. + /// + /// + public Auth0WebAppAuthenticationBuilder WithClientInitiatedBackchannelAuthentication() + { + _services.AddScoped(); + return this; + } + private void EnableWithAccessToken(Action configureOptions) { var auth0WithAccessTokensOptions = new Auth0WebAppWithAccessTokenOptions(); From 2af0fed765994ec8905f3a25d386a0febf109191 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Tue, 20 May 2025 16:24:10 +0530 Subject: [PATCH 6/8] Updating Examples.md --- EXAMPLES.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index b67fd76..696b121 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -444,3 +444,95 @@ public class LogoutModel : PageModel } } ``` + +# Accessing Auth0.AuthenticationApi features +`Auth0.AuthenticationApi` package is our standalone Authentication package that supports a wide range of +options for Authentication. We can access these features like below : + +```csharp +/// Register the dependency in the container as below. +/// Program.cs / Startup.cs +builder.Services.AddAuth0WebAppAuthentication(options => +{ + options.Domain = "domain"; + options.ClientId = "ClientId"; + options.ClientSecret = "clientSecret"; +}).WithAuthenticationApiClient(); + + +/// Accessing the Api Client in the controller +public AccountController(IAuthenticationApiClient apiClient) +{ + _apiClient = apiClient; +} + +[Authorize] +public async Task LoginWithAuthenticationApi() +{ + await _apiClient.GetTokenAsync(new ClientCredentialsTokenRequest() + { + ClientId = "", + ClientSecret = "" + }, new CancellationToken()); +} +``` + +# Accessing specific features like CIBA +Although `Auth0.AuthenticationApi` package has a wide range of options for Authentication. +We can access the CIBA feature as below. It aims to make it easy to integrate into an appllication. + +```csharp +/// Register the dependency in the container as below. +/// Program.cs / Startup.cs +builder.Services.AddAuth0WebAppAuthentication(options => +{ + options.Domain = "domain"; + options.ClientId = "ClientId"; // required + options.ClientSecret = "clientSecret"; // required +}).WithClientInitiatedBackchannelAuthentication(); + + +/// Accessing the Auth0CibaService in the controller +public AccountController(IAuth0CibaService auth0CibaService) +{ + _auth0CibaService = auth0CibaService; +} + +public async Task InitiateLoginWithCiba(string returnUrl = "/") +{ + var response = await _auth0CibaService.InitiateAuthenticationAsync(new CibaInitiationRequest() + { + Scope = "openid profile", + BindingMessage = "BindingMessage", + LoginHint = new LoginHint() + { + Format = "iss_sub", + Issuer = "https://dx-sdks-testing.us.auth0.com/", + Subject = "userId" + } + }); + + // Cache the details for polling. + TempData["AuthRequestId"] = response.AuthRequestId; + TempData["CibaInitiationDetails"] = JsonSerializer.Serialize(response); + return RedirectToAction("Waiting"); +} + +[HttpGet] +public async Task CheckCibaStatus() +{ + var cibaInitiateResponse = JsonSerializer.Deserialize(TempData["CibaInitiationDetails"]?.ToString() ?? string.Empty); + var authRequestId = cibaInitiateResponse?.AuthRequestId; + if (string.IsNullOrEmpty(authRequestId)) + { + return Json(new { isError = true }); + } + + var status = await _auth0CibaService.PollForTokensAsync(cibaInitiateResponse); + + if (status.IsSuccessful) + { + // Parse the accessToken / IdToken and use it as required. + } +} +``` From 40501995aa7123bfc1ff508f48961f257066d100 Mon Sep 17 00:00:00 2001 From: kailash-b Date: Thu, 12 Jun 2025 12:05:26 +0530 Subject: [PATCH 7/8] Refactor to address review comments --- EXAMPLES.md | 2 + .../Auth0WebAppAuthenticationBuilder.cs | 6 +- .../Auth0CibaService.cs | 60 +++++++++++-------- .../IAuth0CibaService.cs | 16 +++-- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 696b121..bb2ece6 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -518,6 +518,8 @@ public async Task InitiateLoginWithCiba(string returnUrl = "/") return RedirectToAction("Waiting"); } +// You could use the built-in polling mechanism or could implement your own polling mechanism by +// accessing the `GetTokenAsync` method using the AuthenticationApiClient as shown in the example before. [HttpGet] public async Task CheckCibaStatus() { diff --git a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs index 4ce4861..30c4c78 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs +++ b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs @@ -7,6 +7,7 @@ using Auth0.AspNetCore.Authentication.BackchannelLogout; using Auth0.AspNetCore.Authentication.ClientInitiatedBackChannelAuthentication; using Auth0.AuthenticationApi; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace Auth0.AspNetCore.Authentication { @@ -81,7 +82,10 @@ public Auth0WebAppAuthenticationBuilder WithAuthenticationApiClient() /// public Auth0WebAppAuthenticationBuilder WithClientInitiatedBackchannelAuthentication() { - _services.AddScoped(); + _services.TryAddSingleton( + _ => new AuthenticationApiClient(new Uri($"https://{_options.Domain}"))); + _services.TryAddScoped(); + return this; } diff --git a/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/Auth0CibaService.cs b/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/Auth0CibaService.cs index 2522d3b..84013f2 100644 --- a/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/Auth0CibaService.cs +++ b/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/Auth0CibaService.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -16,6 +17,12 @@ internal class Auth0CibaService : IAuth0CibaService private readonly Auth0WebAppOptions _options; private readonly ILogger _logger; + /// + /// Initiates an instance of Auth0CibaService which can be used to execute the CIBA workflow. + /// + /// Instance of + /// + /// public Auth0CibaService( IAuthenticationApiClient authenticationApiClient, IOptions optionsAccessor, @@ -26,7 +33,9 @@ public Auth0CibaService( _logger = logger; } - public async Task InitiateAuthenticationAsync(CibaInitiationRequest request) + /// + public async Task InitiateAuthenticationAsync( + CibaInitiationRequest request) { try { @@ -63,7 +72,9 @@ public async Task InitiateAuthenticationAsync(CibaInitiat } } - public async Task PollForTokensAsync(CibaInitiationDetails initDetails) + /// + public async Task PollForTokensAsync( + CibaInitiationDetails initDetails, CancellationToken cancellationToken) { var request = new ClientInitiatedBackchannelAuthorizationTokenRequest() { @@ -74,24 +85,27 @@ public async Task PollForTokensAsync(CibaInitiationDetail AuthRequestId = initDetails.AuthRequestId }; - while (true) + var completionDetails = new CibaCompletionDetails() + { + IsSuccessful = false, + IsAuthenticationPending = true + }; + + while (completionDetails is { IsAuthenticationPending: true, IsSuccessful: false }) { _logger.LogDebug($"Polling CIBA token endpoint for auth_req_id: {initDetails.AuthRequestId} "); try { - var response = await _authenticationApiClient.GetTokenAsync(request); - - return new CibaCompletionDetails - { - AccessToken = response.AccessToken, - IdToken = response.IdToken, - TokenType = response.TokenType, - Scope = response.Scope, - ExpiresIn = response.ExpiresIn, - RefreshToken = response.RefreshToken, - IsSuccessful = true, - IsAuthenticationPending = false, - }; + var response = await _authenticationApiClient.GetTokenAsync(request, cancellationToken); + + completionDetails.AccessToken = response.AccessToken; + completionDetails.IdToken = response.IdToken; + completionDetails.TokenType = response.TokenType; + completionDetails.Scope = response.Scope; + completionDetails.ExpiresIn = response.ExpiresIn; + completionDetails.RefreshToken = response.RefreshToken; + completionDetails.IsSuccessful = true; + completionDetails.IsAuthenticationPending = false; } catch (ErrorApiException ex) { @@ -102,18 +116,16 @@ public async Task PollForTokensAsync(CibaInitiationDetail if (ex.ApiError.Error.Contains("authorization_pending", StringComparison.OrdinalIgnoreCase)) { - await Task.Delay(TimeSpan.FromSeconds(initDetails.Interval ?? 5)); + await Task.Delay(TimeSpan.FromSeconds(initDetails.Interval)); continue; } - return new CibaCompletionDetails - { - IsAuthenticationPending = false, - Error = ex.ApiError.Error, - ErrorMessage = ex.ApiError.Message, - IsSuccessful = false - }; + completionDetails.IsAuthenticationPending = false; + completionDetails.Error = ex.ApiError.Error; + completionDetails.ErrorMessage = ex.ApiError.Message; + completionDetails.IsSuccessful = false; } } + return completionDetails; } } \ No newline at end of file diff --git a/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/IAuth0CibaService.cs b/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/IAuth0CibaService.cs index f3e5cfe..3995c4b 100644 --- a/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/IAuth0CibaService.cs +++ b/src/Auth0.AspNetCore.Authentication/ClientInitiatedBackChannelAuthentication/IAuth0CibaService.cs @@ -1,16 +1,21 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Auth0.AuthenticationApi.Models.Ciba; namespace Auth0.AspNetCore.Authentication.ClientInitiatedBackChannelAuthentication; -public class CibaInitiationDetails +public class CibaInitiationDetails : ClientInitiatedBackchannelAuthorizationResponse { - public string? AuthRequestId { get; init; } - public int ExpiresIn { get; init; } - public int? Interval { get; init; } + /// + /// Indicates whether the polling was successful. + /// public bool IsSuccessful { get; init; } = true; + + /// + /// Indicates any errors that occurred during the initiation of the CIBA request. + /// public string? ErrorMessage { get; init; } } @@ -70,6 +75,7 @@ public interface IAuth0CibaService /// Polls the token endpoint to check the status of a CIBA request and retrieve tokens upon completion. /// /// The information required to poll for the CIBA status. + /// /// Details about the CIBA completion status or the retrieved tokens. - Task PollForTokensAsync(CibaInitiationDetails cibaInitiationDetails); + Task PollForTokensAsync(CibaInitiationDetails cibaInitiationDetails, CancellationToken cancellationToken = default); } \ No newline at end of file From 780887309ace1f765ca6b3c3e0008729d9a1f90e Mon Sep 17 00:00:00 2001 From: kailash-b Date: Fri, 13 Jun 2025 15:23:50 +0530 Subject: [PATCH 8/8] Update Examples.md --- EXAMPLES.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index bb2ece6..2219032 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -8,6 +8,8 @@ - [Roles](#roles) - [Backchannel Logout](#backchannel-logout) - [Blazor Server](#blazor-server) +- [Accessing Auth0.AuthenticationApi features](#accessing-auth0authenticationapi-features) +- [Accessing specific features like CIBA](#accessing-specific-features-like-ciba) ## Login and Logout Triggering login or logout is done using ASP.NET's `HttpContext`: @@ -445,9 +447,9 @@ public class LogoutModel : PageModel } ``` -# Accessing Auth0.AuthenticationApi features +## Accessing Auth0.AuthenticationApi features `Auth0.AuthenticationApi` package is our standalone Authentication package that supports a wide range of -options for Authentication. We can access these features like below : +options for Authentication. For example, you can use it to implement the [client credentials flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow), as shown below : ```csharp /// Register the dependency in the container as below. @@ -477,7 +479,7 @@ public async Task LoginWithAuthenticationApi() } ``` -# Accessing specific features like CIBA +## Accessing specific features like CIBA Although `Auth0.AuthenticationApi` package has a wide range of options for Authentication. We can access the CIBA feature as below. It aims to make it easy to integrate into an appllication. @@ -486,7 +488,7 @@ We can access the CIBA feature as below. It aims to make it easy to integrate in /// Program.cs / Startup.cs builder.Services.AddAuth0WebAppAuthentication(options => { - options.Domain = "domain"; + options.Domain = "domain"; // required options.ClientId = "ClientId"; // required options.ClientSecret = "clientSecret"; // required }).WithClientInitiatedBackchannelAuthentication(); @@ -507,7 +509,7 @@ public async Task InitiateLoginWithCiba(string returnUrl = "/") LoginHint = new LoginHint() { Format = "iss_sub", - Issuer = "https://dx-sdks-testing.us.auth0.com/", + Issuer = "https://your-domain/", Subject = "userId" } });