diff --git a/Abblix.Jwt.UnitTests/JwtEncryptionTests.cs b/Abblix.Jwt.UnitTests/JwtEncryptionTests.cs index fde7d790..35811abf 100644 --- a/Abblix.Jwt.UnitTests/JwtEncryptionTests.cs +++ b/Abblix.Jwt.UnitTests/JwtEncryptionTests.cs @@ -21,7 +21,6 @@ // info@abblix.com using System.Text.Json.Nodes; -using Abblix.Utils; using Microsoft.IdentityModel.Tokens; using Xunit; diff --git a/Abblix.Jwt/JsonWebTokenCreator.cs b/Abblix.Jwt/JsonWebTokenCreator.cs index ab173ab6..d12fe242 100644 --- a/Abblix.Jwt/JsonWebTokenCreator.cs +++ b/Abblix.Jwt/JsonWebTokenCreator.cs @@ -65,7 +65,7 @@ public Task IssueAsync( { TokenType = jwt.Header.Type, Issuer = jwt.Payload.Issuer, - Audience = jwt.Payload.Audiences?.SingleOrDefault(), //TODO replace JwtSecurityTokenHandler with own code to overcome this limitation + Audience = jwt.Payload.Audiences.SingleOrDefault(), //TODO replace JwtSecurityTokenHandler with own code to overcome this limitation IssuedAt = CheckDateOverflow(jwt.Payload.IssuedAt, nameof(jwt.Payload.IssuedAt)), NotBefore = CheckDateOverflow(jwt.Payload.NotBefore, nameof(jwt.Payload.NotBefore)), diff --git a/Abblix.Oidc.Server.Mvc/Controllers/AuthenticationController.cs b/Abblix.Oidc.Server.Mvc/Controllers/AuthenticationController.cs index 768b3f97..bfbc883e 100644 --- a/Abblix.Oidc.Server.Mvc/Controllers/AuthenticationController.cs +++ b/Abblix.Oidc.Server.Mvc/Controllers/AuthenticationController.cs @@ -22,7 +22,6 @@ using System.Net.Mime; using Abblix.Oidc.Server.Common.Constants; -using Abblix.Oidc.Server.Common.Exceptions; using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces; using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces; using Abblix.Oidc.Server.Endpoints.CheckSession.Interfaces; @@ -57,18 +56,21 @@ namespace Abblix.Oidc.Server.Mvc.Controllers; public sealed class AuthenticationController : ControllerBase { /// - /// Handles the pushed authorization endpoint. This endpoint is used for receiving and processing pushed authorization requests - /// from clients, validating the request, and generating a response that either contains a URI for the stored authorization request - /// or an error message. + /// Handles the pushed authorization endpoint. This endpoint is used for receiving and processing pushed + /// authorization requests from clients, validating the request, and generating a response that either contains + /// a URI for the stored authorization request or an error message. /// /// The handler responsible for processing pushed authorization requests. /// The service for formatting the authorization response. /// The authorization request received from the client. /// Additional client request information for contextual validation. - /// An action result containing the formatted authorization response, which can be a success or an error response. + /// + /// An action result containing the formatted authorization response, which can be a success or an error response. + /// /// - /// This method first validates the incoming authorization request. If the request is valid, it is processed and stored, - /// and a response containing the request URI is returned. If the request is invalid, an error response is generated. + /// This method first validates the incoming authorization request. + /// If the request is valid, it is processed and stored, and a response containing the request URI is returned. + /// If the request is invalid, an error response is generated. /// [HttpPost(Path.PushAuthorizationRequest)] [Consumes(MediaTypes.FormUrlEncoded)] @@ -86,7 +88,8 @@ public async Task> PushAuthorizeAsync( } /// - /// Handles requests to the authorization endpoint, performing user authentication and getting consent for requested scopes. + /// Handles requests to the authorization endpoint, performing user authentication and getting consent for + /// requested scopes. /// /// The handler responsible for processing authorization requests. /// The formatter used to generate a response for the authorization request. @@ -201,13 +204,33 @@ public async Task CheckSessionAsync( } /// - /// Handles the backchannel authentication endpoint, initiating an out-of-band authentication process. + /// Handles the backchannel authentication endpoint, initiating the authentication flow that occurs outside + /// the traditional user-agent interaction as specified by CIBA. /// /// + /// The method implements the CIBA (Client-Initiated Backchannel Authentication) protocol, + /// enabling the authentication of a user through an out-of-band mechanism. + /// Clients initiate the authentication request, and the user's authentication happens through a separate channel + /// (e.g., mobile device). + /// + /// For more details, refer to the CIBA documentation: /// /// CIBA - Client Initiated Backchannel Authentication Documentation /// /// + /// + /// Service that processes the authentication request, validating and initiating the backchannel flow. + /// + /// Service that formats the response to the client, based on the result of the backchannel authentication request. + /// + /// + /// The backchannel authentication request containing user-related authentication parameters. + /// + /// The client request providing the client-related information needed for the request. + /// + /// An representing the HTTP response to the backchannel authentication request. + /// The response may indicate successful initiation of the process or an error if the request fails validation. + /// [HttpPost(Path.BackchannelAuthentication)] [Consumes(MediaTypes.FormUrlEncoded)] public async Task BackChannelAuthenticationAsync( @@ -218,9 +241,7 @@ public async Task BackChannelAuthenticationAsync( { var mappedAuthenticationRequest = authenticationRequest.Map(); var mappedClientRequest = clientRequest.Map(); - var response = await handler.HandleAsync(mappedAuthenticationRequest, mappedClientRequest); - return await formatter.FormatResponseAsync(mappedAuthenticationRequest, response); } diff --git a/Abblix.Oidc.Server.Mvc/Formatters/BackChannelAuthenticationResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/BackChannelAuthenticationResponseFormatter.cs index 888f1d4f..e2c230a5 100644 --- a/Abblix.Oidc.Server.Mvc/Formatters/BackChannelAuthenticationResponseFormatter.cs +++ b/Abblix.Oidc.Server.Mvc/Formatters/BackChannelAuthenticationResponseFormatter.cs @@ -20,37 +20,61 @@ // CONTACT: For license inquiries or permissions, contact Abblix LLP at // info@abblix.com +using Abblix.Oidc.Server.Common.Constants; using Abblix.Oidc.Server.Common.Exceptions; -using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces; using Abblix.Oidc.Server.Model; using Abblix.Oidc.Server.Mvc.Formatters.Interfaces; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Abblix.Oidc.Server.Mvc.Formatters; /// -/// Formats responses for back-channel authentication. +/// Handles the formatting of responses for back-channel authentication requests. +/// This class ensures that the appropriate HTTP responses are generated based on the type of back-channel +/// authentication response, encapsulating success or error scenarios as defined by OAuth 2.0 standards. /// public class BackChannelAuthenticationResponseFormatter : IBackChannelAuthenticationResponseFormatter { /// - /// Formats a response for back-channel authentication asynchronously. + /// This method transforms the back-channel authentication response into a suitable HTTP response, + /// ensuring that different outcomes (success or various types of errors) are appropriately represented + /// as HTTP status codes and payloads. /// - /// The back-channel authentication request. - /// The back-channel authentication response to be formatted. + /// The original back-channel authentication request that triggered the response. + /// The back-channel authentication response that needs to be formatted into an HTTP result. + /// /// A representing the asynchronous operation, /// with the formatted response as an . + /// + /// This method formats successful authentication requests as `200 OK`, client-related errors as `401 Unauthorized` + /// or `403 Forbidden`, and general errors as `400 Bad Request`. + /// It provides consistent handling for different response types, + /// ensuring the API behaves predictably according to the OAuth 2.0 back-channel authentication specification. + /// public Task FormatResponseAsync( BackChannelAuthenticationRequest request, BackChannelAuthenticationResponse response) { return Task.FromResult(response switch { + // If the authentication was successful, return a 200 OK response with the success details. BackChannelAuthenticationSuccess success => new OkObjectResult(success), + // If the error indicates an invalid client, return a 401 Unauthorized response. + BackChannelAuthenticationError { Error: ErrorCodes.InvalidClient, ErrorDescription: var description } + => new UnauthorizedObjectResult(new ErrorResponse(ErrorCodes.InvalidClient, description)), + + // If access was denied, return a 403 Forbidden response. + BackChannelAuthenticationError { Error: ErrorCodes.AccessDenied, ErrorDescription: var description } + => new ObjectResult(new ErrorResponse(ErrorCodes.InvalidClient, description)) + { StatusCode = StatusCodes.Status403Forbidden }, + + // For any other type of error, return a 400 Bad Request response. BackChannelAuthenticationError { Error: var error, ErrorDescription: var description } => new BadRequestObjectResult(new ErrorResponse(error, description)), + // If the response type is unexpected, throw an exception for further debugging. _ => throw new UnexpectedTypeException(nameof(response), response.GetType()), }); } diff --git a/Abblix.Oidc.Server.Mvc/Formatters/CheckSessionResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/CheckSessionResponseFormatter.cs index df67fbd4..6aa6514b 100644 --- a/Abblix.Oidc.Server.Mvc/Formatters/CheckSessionResponseFormatter.cs +++ b/Abblix.Oidc.Server.Mvc/Formatters/CheckSessionResponseFormatter.cs @@ -31,22 +31,21 @@ namespace Abblix.Oidc.Server.Mvc.Formatters; /// /// Provides a response formatter for Check Session frames. /// -internal class CheckSessionResponseFormatter : ICheckSessionResponseFormatter +public class CheckSessionResponseFormatter : ICheckSessionResponseFormatter { /// /// Formats a response for a Check Session frame asynchronously. /// /// The Check Session response containing HTML content. - /// A representing the asynchronous operation, with the formatted response as an . + /// A representing the asynchronous operation, + /// with the formatted response as an . public Task FormatResponseAsync(CheckSessionResponse response) { - var content = response.HtmlContent; - var result = new ContentResult { StatusCode = StatusCodes.Status200OK, ContentType = MediaTypeNames.Text.Html, - Content = content, + Content = response.HtmlContent, }; return Task.FromResult(result); diff --git a/Abblix.Oidc.Server.Mvc/Formatters/Interfaces/IBackChannelAuthenticationResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/Interfaces/IBackChannelAuthenticationResponseFormatter.cs index 2106bdaa..18882e42 100644 --- a/Abblix.Oidc.Server.Mvc/Formatters/Interfaces/IBackChannelAuthenticationResponseFormatter.cs +++ b/Abblix.Oidc.Server.Mvc/Formatters/Interfaces/IBackChannelAuthenticationResponseFormatter.cs @@ -20,7 +20,6 @@ // CONTACT: For license inquiries or permissions, contact Abblix LLP at // info@abblix.com -using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces; using Abblix.Oidc.Server.Model; using Microsoft.AspNetCore.Mvc; diff --git a/Abblix.Oidc.Server.Mvc/Formatters/IntrospectionResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/IntrospectionResponseFormatter.cs index d131ce48..de47dd28 100644 --- a/Abblix.Oidc.Server.Mvc/Formatters/IntrospectionResponseFormatter.cs +++ b/Abblix.Oidc.Server.Mvc/Formatters/IntrospectionResponseFormatter.cs @@ -48,14 +48,14 @@ public class IntrospectionResponseFormatter : IIntrospectionResponseFormatter /// public Task FormatResponseAsync(IntrospectionRequest request, IntrospectionResponse response) { - return Task.FromResult(response switch + return Task.FromResult(response switch { IntrospectionSuccessResponse success => Format(success), IntrospectionErrorResponse error => new UnauthorizedObjectResult(new ErrorResponse(error.Error, error.ErrorDescription)), - _ => throw new ArgumentOutOfRangeException(nameof(response)) + _ => throw new ArgumentOutOfRangeException(nameof(response)), }); } diff --git a/Abblix.Oidc.Server.Tests/OidcClient.cs b/Abblix.Oidc.Server.Tests/OidcClient.cs index 48d74e79..db653847 100644 --- a/Abblix.Oidc.Server.Tests/OidcClient.cs +++ b/Abblix.Oidc.Server.Tests/OidcClient.cs @@ -56,7 +56,7 @@ private async Task DiscoverEndpoints() { var discoveryResponse = JsonDocument.Parse(await _client.GetStringAsync(".well-known/openid-configuration")); - Uri Discover(string name) => new(discoveryResponse.RootElement.GetProperty(name).GetString()); + Uri Discover(string name) => Uri.TryCreate(discoveryResponse.RootElement.GetProperty(name).GetString(), UriKind.RelativeOrAbsolute, out var uri) ? uri : default; _authorizationEndpoint = Discover("authorization_endpoint"); _tokenEndpoint = Discover("token_endpoint"); diff --git a/Abblix.Oidc.Server.UnitTests/Model/ClientRegistrationRequestTest.cs b/Abblix.Oidc.Server.UnitTests/Model/ClientRegistrationRequestTest.cs index f99f9012..d5a23bde 100644 --- a/Abblix.Oidc.Server.UnitTests/Model/ClientRegistrationRequestTest.cs +++ b/Abblix.Oidc.Server.UnitTests/Model/ClientRegistrationRequestTest.cs @@ -33,6 +33,6 @@ public class ClientRegistrationRequestTest "{\"client_name\":\"dynamic_client_1 RqxLk9BdhK8qC3z\",\"grant_types\":[\"implicit\"],\"jwks\":{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"alg\":\"RS256\",\"n\":\"gUOdYo2PpUUnZzozIPJ-7mK2Z5jYBxjj_5iB2TDnElt8yUc-mcCeOQrsaswPgKx2KMSJ50kwrFHHEuNyiDhgNMgtmJ98RuhggXaPF1fmmHss_Wc1OSqyGYLWbEzYGsRck5yTVP4xsPYAeP5xkkLze_FXJvwITNu2aGxXEYwokkrcWgL3AsXtYKClIwmacHhVNEMn-ALe3sMTifx4F8TqmNAlD4FPga094txHJNo2Ho6z4kn5L4uq_WXklDjaIDOqQZtdn0emXig3RHQcOtepFcXt7pcK9E2M3kxKFOMPpY8c4kaDfQ41jv23vbm9oDTh5s3TB0ZwcKJXj4-06gwTWw\"}]},\"token_endpoint_auth_method\":\"client_secret_basic\",\"response_types\":[\"id_token token\"],\"redirect_uris\":[\"https://www.certification.openid.net/test/a/Abblix/callback\"],\"contacts\":[\"certification@oidf.org\"]}")] public void DeserializeClientRegistrationRequestTest(string json) { - var request = JsonSerializer.Deserialize(json); + JsonSerializer.Deserialize(json); } } diff --git a/Abblix.Oidc.Server/Common/OperationResult.cs b/Abblix.Oidc.Server/Common/OperationResult.cs index da0110a8..a75879d9 100644 --- a/Abblix.Oidc.Server/Common/OperationResult.cs +++ b/Abblix.Oidc.Server/Common/OperationResult.cs @@ -37,21 +37,41 @@ private OperationResult() { } /// Represents a successful result containing a value of type . /// /// The value returned by the successful operation. - public sealed record Success(T Value) : OperationResult; + public sealed record Success(T Value) : OperationResult + { + /// + /// Returns a string that represents the current object, either the successful value or an error description. + /// + /// + /// A string representation of the result, displaying the value in case of success, + /// or an error message in case of failure. + /// + public override string ToString() => Value?.ToString() ?? string.Empty; + } /// /// Represents an error result containing an error code and a descriptive message. /// /// The code representing the type or cause of the error. /// A human-readable description of the error. - public sealed record Error(string ErrorCode, string ErrorDescription) : OperationResult; + public sealed record Error(string ErrorCode, string ErrorDescription) : OperationResult + { + /// + /// Returns a string that represents the current object, either the successful value or an error description. + /// + /// + /// A string representation of the result, displaying the value in case of success, + /// or an error message in case of failure. + /// + public override string ToString() + => $"{nameof(ErrorCode)}: {ErrorCode}, {nameof(ErrorDescription)}: {ErrorDescription}"; + } /// /// Implicitly converts a value of type into a successful result. /// /// The value to be wrapped as a successful result. - public static implicit operator OperationResult(T value) - => new Success(value); + public static implicit operator OperationResult(T value) => new Success(value); /// /// Implicitly converts an into an result. @@ -59,19 +79,4 @@ public static implicit operator OperationResult(T value) /// The error response to be wrapped as an error result. public static implicit operator OperationResult(ErrorResponse error) => new Error(error.Error, error.ErrorDescription); - - /// - /// Returns a string that represents the current object, either the successful value or an error description. - /// - /// - /// A string representation of the result, displaying the value in case of success, - /// or an error message in case of failure. - /// - public override string? ToString() - => this switch - { - Success(var value) => value?.ToString(), - Error(var error, var description) => $"Error: {error}, Description: {description}", - _ => $"Unexpected type: {GetType().FullName}", - }; } diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs index f780d9af..925c1ff4 100644 --- a/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs +++ b/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs @@ -20,7 +20,6 @@ // CONTACT: For license inquiries or permissions, contact Abblix LLP at // info@abblix.com -using System.Runtime.InteropServices.ComTypes; using Abblix.Oidc.Server.Common; using Abblix.Oidc.Server.Common.Constants; using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces; diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestUriFetcher.cs b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestUriFetcher.cs index 869d4726..8ef8ff15 100644 --- a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestUriFetcher.cs +++ b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestUriFetcher.cs @@ -28,16 +28,22 @@ namespace Abblix.Oidc.Server.Endpoints.Authorization.RequestFetching; /// -/// Fetches authorization request objects from a specified request URI. +/// Handles fetching of authorization request objects from a specified request URI. +/// This class is responsible for retrieving the pre-registered request objects from an external location +/// indicated by a URI, ensuring the request is complete and valid. +/// It helps enable dynamic request objects, allowing authorization servers to fetch additional +/// data required for processing the authorization request. /// public class RequestUriFetcher : IAuthorizationRequestFetcher { /// /// Initializes a new instance of the class. + /// This constructor sets up the necessary services for fetching the request objects over HTTP. /// - /// The logger used for logging warnings when unable to fetch the request object. + /// The logger used for logging warnings when request fetching fails. /// - /// The factory used to create instances of for making HTTP requests. + /// The factory used to create instances for making HTTP requests to the specified URI. + /// public RequestUriFetcher( ILogger logger, IHttpClientFactory httpClientFactory) @@ -51,45 +57,48 @@ public RequestUriFetcher( /// - /// Asynchronously fetches the authorization request object from the specified request URI. + /// Asynchronously fetches the authorization request object from the given request URI. + /// This method retrieves the request object if the request URI is valid and contains an absolute URL. + /// It then returns the authorization request object or logs an error if the fetch fails. /// - /// - /// The authorization request containing a RequestUri from which to fetch the request object. + /// The authorization request, which contains the RequestUri. /// - /// A task representing the asynchronous operation. The task result contains the authorization request object - /// fetched from the URI or an error. + /// A task representing the asynchronous operation, with the result being the fetched request object or an error. + /// /// - /// If the authorization request contains a valid absolute URI in the RequestUri property, - /// this method attempts to fetch the request object from that URI. - /// If the fetch operation is successful, the request object is returned; otherwise, an error is logged, - /// and an error response is returned. + /// The method checks for conflicts between the `Request` and `RequestUri` parameters. + /// If both are present, it returns an error since only one should be used. + /// Otherwise, it proceeds to fetch the request object from the `RequestUri` and returns the result. /// public async Task FetchAsync(AuthorizationRequest request) { if (request is { Request: not null, RequestUri: not null }) { + // Log an error if both request parameters are provided, as this violates the request format rules. return ErrorFactory.InvalidRequest( $"Only one of the parameters {Parameters.Request} and {Parameters.RequestUri} can be used"); } - if (request is { RequestUri: { IsAbsoluteUri: true } requestUri }) + if (request is not { RequestUri: { IsAbsoluteUri: true } requestUri }) { - var client = _httpClientFactory.CreateClient(); - string requestObject; - try - { - requestObject = await client.GetStringAsync(requestUri); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Unable to get the request object from {RequestUri}", requestUri); - return ErrorFactory.InvalidRequestUri( - $"Unable to get the request object from {Parameters.RequestUri}"); - } + return request; + } - return request with { RedirectUri = null, Request = requestObject }; + // If the request contains a valid absolute URI, proceed to fetch the request object. + var client = _httpClientFactory.CreateClient(); + string requestObject; + try + { + requestObject = await client.GetStringAsync(requestUri); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Unable to get the request object from {RequestUri}", requestUri); + return ErrorFactory.InvalidRequestUri( + $"Unable to get the request object from {Parameters.RequestUri}"); } - return request; + // Return the updated request with the fetched request object and nullify the redirect URI. + return request with { RedirectUri = null, Request = requestObject }; } } diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetcher.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetcher.cs index 047c6476..128f105d 100644 --- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetcher.cs +++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetcher.cs @@ -29,8 +29,8 @@ namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.RequestFetching; /// -/// Handles the fetching and processing of backchannel authentication request objects, including the validation -/// of JWTs (JSON Web Tokens) and the binding of JSON payloads to models. +/// Handles the fetching and processing of backchannel authentication request objects, including the validation of JWTs +/// (JSON Web Tokens) and the binding of JSON payloads to models. /// public class RequestObjectFetcher : RequestObjectFetcherBase, IBackChannelAuthenticationRequestFetcher { diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/RequestedExpiryValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/RequestedExpiryValidator.cs index e1e8e2e6..064ca026 100644 --- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/RequestedExpiryValidator.cs +++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/RequestedExpiryValidator.cs @@ -69,7 +69,7 @@ public RequestedExpiryValidator(IOptionsSnapshot options) { context.ExpiresIn = _options.Value.BackChannelAuthentication.DefaultExpiry; } - else if (_options.Value.BackChannelAuthentication.MaximumExpiry >= context.Request.RequestedExpiry.Value) + else if (context.Request.RequestedExpiry.Value <= _options.Value.BackChannelAuthentication.MaximumExpiry) { context.ExpiresIn = context.Request.RequestedExpiry.Value; } diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ScopeValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ScopeValidator.cs index d2f0489a..f82e26dc 100644 --- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ScopeValidator.cs +++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ScopeValidator.cs @@ -21,7 +21,6 @@ // info@abblix.com using Abblix.Oidc.Server.Common.Constants; -using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces; using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces; using Abblix.Oidc.Server.Features.ScopeManagement; diff --git a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/IdTokenHintValidator.cs b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/IdTokenHintValidator.cs index 88d8b4d6..edf5ab7b 100644 --- a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/IdTokenHintValidator.cs +++ b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/IdTokenHintValidator.cs @@ -66,12 +66,21 @@ public IdTokenHintValidator(IAuthServiceJwtValidator jwtValidator) case ValidJsonWebToken { Token: var idToken, Token.Payload.Audiences: var audiences }: if (!request.ClientId.HasValue()) { - // TODO what we should do if there are multiple audiences??? - context.ClientId = audiences.Single(); + try + { + context.ClientId = audiences.Single(); + } + catch (Exception) + { + return new EndSessionRequestValidationError( + ErrorCodes.InvalidRequest, + "The audience in the id token hint is missing or have multiple values."); + } } else if (!audiences.Contains(request.ClientId, StringComparer.Ordinal)) { - return new EndSessionRequestValidationError(ErrorCodes.InvalidRequest, + return new EndSessionRequestValidationError( + ErrorCodes.InvalidRequest, "The id token hint contains token issued for the client other than specified"); } diff --git a/Abblix.Oidc.Server/Endpoints/Introspection/Interfaces/ValidIntrospectionRequest.cs b/Abblix.Oidc.Server/Endpoints/Introspection/Interfaces/ValidIntrospectionRequest.cs index 548e3a37..5c854fc6 100644 --- a/Abblix.Oidc.Server/Endpoints/Introspection/Interfaces/ValidIntrospectionRequest.cs +++ b/Abblix.Oidc.Server/Endpoints/Introspection/Interfaces/ValidIntrospectionRequest.cs @@ -63,10 +63,12 @@ private ValidIntrospectionRequest(IntrospectionRequest model) /// public static ValidIntrospectionRequest InvalidToken(IntrospectionRequest model) { - // Note that to avoid disclosing too much of the authorization server's state to a third party, the authorization server - // SHOULD NOT include any additional information about an inactive token, including why the token is inactive. + // Note that to avoid disclosing too much of the authorization server's state to a third party, + // the authorization server SHOULD NOT include any additional information about an inactive token, + // including why the token is inactive. - // That is why we do not return the token here even if it is valid, but for example it was issued for another client. + // That is why we do not return the token here even if it is valid, but for example, + // it was issued for another client. return new(model); } diff --git a/Abblix.Oidc.Server/Endpoints/Revocation/RevocationRequestValidator.cs b/Abblix.Oidc.Server/Endpoints/Revocation/RevocationRequestValidator.cs index 9919531d..6fa1d0c4 100644 --- a/Abblix.Oidc.Server/Endpoints/Revocation/RevocationRequestValidator.cs +++ b/Abblix.Oidc.Server/Endpoints/Revocation/RevocationRequestValidator.cs @@ -35,7 +35,8 @@ namespace Abblix.Oidc.Server.Endpoints.Revocation; /// /// Validates revocation requests in accordance with OAuth 2.0 standards. /// This class is responsible for ensuring that revocation requests meet the criteria specified in -/// OAuth 2.0 Token Revocation (RFC 7009). It validates the authenticity of the client and the token involved in the request. +/// OAuth 2.0 Token Revocation (RFC 7009). +/// It validates the authenticity of the client and the token involved in the request. /// public class RevocationRequestValidator : IRevocationRequestValidator { @@ -43,8 +44,10 @@ public class RevocationRequestValidator : IRevocationRequestValidator /// Initializes a new instance of the class. /// The constructor sets up the validator with necessary components for client authentication and JWT validation. /// - /// The client request authenticator to be used in the validation process. - /// The JWT validator to be used for validating the token included in the revocation request. + /// + /// The client request authenticator to be used in the validation process. + /// + /// The JWT validator to be used for validating the token included in the revocation request. public RevocationRequestValidator( IClientAuthenticator clientAuthenticator, IAuthServiceJwtValidator jwtValidator) @@ -61,7 +64,8 @@ public RevocationRequestValidator( /// It checks the client's credentials and the validity of the token to be revoked. The validation ensures /// that the token belongs to the authenticated client and is valid as per JWT standards. /// - /// The revocation request to be validated. Contains the token to be revoked and client information. + /// + /// The revocation request to be validated. Contains the token to be revoked and client information. /// Additional client request information for contextual validation. /// /// A representing the asynchronous operation, which upon completion will yield a @@ -75,24 +79,22 @@ public async Task ValidateAsync( var clientInfo = await _clientAuthenticator.TryAuthenticateClientAsync(clientRequest); if (clientInfo == null) { - return new RevocationRequestValidationError(ErrorCodes.InvalidClient, "The client is not authorized"); + return new RevocationRequestValidationError( + ErrorCodes.InvalidClient, + "The client is not authorized"); } var result = await _jwtValidator.ValidateAsync(revocationRequest.Token); switch (result) { - case ValidJsonWebToken { Token: var token }: - - var clientId = token.Payload.ClientId; - if (clientId != clientInfo.ClientId) - { - //TODO maybe log the message: The token was issued to another client? - return ValidRevocationRequest.InvalidToken(revocationRequest); - } + case ValidJsonWebToken { Token.Payload.ClientId: var clientId } when clientId != clientInfo.ClientId: + //TODO maybe log the message: The token was issued to another client? + return ValidRevocationRequest.InvalidToken(revocationRequest); + case ValidJsonWebToken { Token: var token }: return new ValidRevocationRequest(revocationRequest, token); - case JwtValidationError error: //TODO log error + case JwtValidationError: //TODO log error return ValidRevocationRequest.InvalidToken(revocationRequest); default: diff --git a/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs b/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs index 9c54f18e..3c52a551 100644 --- a/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs +++ b/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs @@ -106,7 +106,7 @@ public IEnumerable ClientAuthenticationMethodsSupported case (ValidJsonWebToken, clientInfo: not null): _logger.LogWarning( - ClientAuthenticationMethods.PrivateKeyJwt + " method is not allowed for the client {@ClientId}", + "The authentication method is not allowed for the client {@ClientId}", clientInfo.ClientId); return null; diff --git a/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcherBase.cs b/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcherBase.cs index 07d2f06b..d9ddfcb0 100644 --- a/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcherBase.cs +++ b/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcherBase.cs @@ -76,38 +76,36 @@ protected RequestObjectFetcherBase( protected async Task> FetchAsync(T request, string? requestObject) where T : class { - if (requestObject.HasValue()) - { - _logger.LogDebug("JWT request object was: {RequestObject}", requestObject); + if (!requestObject.HasValue()) + return request; + + _logger.LogDebug("JWT request object was: {RequestObject}", requestObject); - JwtValidationResult? result; - using (var scope = _serviceProvider.CreateScope()) - { - var tokenValidator = scope.ServiceProvider.GetRequiredService(); - (result, _) = await tokenValidator.ValidateAsync( - requestObject, ValidationOptions.ValidateIssuerSigningKey); - } + JwtValidationResult? result; + using (var scope = _serviceProvider.CreateScope()) + { + var tokenValidator = scope.ServiceProvider.GetRequiredService(); + (result, _) = await tokenValidator.ValidateAsync( + requestObject, ValidationOptions.ValidateIssuerSigningKey); + } - switch (result) - { - case ValidJsonWebToken { Token.Payload.Json: var payload }: - var updatedRequest = await _jsonObjectBinder.BindModelAsync(payload, request); - if (updatedRequest == null) - return InvalidRequestObject("Unable to bind request object"); + switch (result) + { + case ValidJsonWebToken { Token.Payload.Json: var payload }: + var updatedRequest = await _jsonObjectBinder.BindModelAsync(payload, request); + if (updatedRequest == null) + return InvalidRequestObject("Unable to bind request object"); - return updatedRequest; + return updatedRequest; - case JwtValidationError error: - _logger.LogWarning("The request object contains invalid token: {@Error}", error); - return InvalidRequestObject("The request object is invalid."); + case JwtValidationError error: + _logger.LogWarning("The request object contains invalid token: {@Error}", error); + return InvalidRequestObject("The request object is invalid."); - default: - throw new UnexpectedTypeException(nameof(result), result.GetType()); - } + default: + throw new UnexpectedTypeException(nameof(result), result.GetType()); } - return request; - static OperationResult.Error InvalidRequestObject(string description) => new(ErrorCodes.InvalidRequestObject, description); } diff --git a/Abblix.Oidc.Server/Model/ConfigurationResponse.cs b/Abblix.Oidc.Server/Model/ConfigurationResponse.cs index 1e278db1..b96339f8 100644 --- a/Abblix.Oidc.Server/Model/ConfigurationResponse.cs +++ b/Abblix.Oidc.Server/Model/ConfigurationResponse.cs @@ -246,7 +246,7 @@ public static class Parameters /// enabling clients to send a fully self-contained authorization request. /// [JsonPropertyName(Parameters.RequestParameterSupported)] - public bool RequestParameterSupported { init; get; } = default!; + public bool RequestParameterSupported { init; get; } /// /// Lists the prompt values supported by the OpenID Provider, @@ -261,7 +261,7 @@ public static class Parameters /// and authentication of the information source. /// [JsonPropertyName(Parameters.UserInfoSigningAlgValuesSupported)] - public IEnumerable? UserInfoSigningAlgValuesSupported { init; get; } = default!; + public IEnumerable? UserInfoSigningAlgValuesSupported { init; get; } /// /// Specifies the signing algorithms supported by the OpenID Provider for request objects. @@ -269,7 +269,7 @@ public static class Parameters /// security measures against tampering and ensuring the authenticity of the request. /// [JsonPropertyName(Parameters.RequestObjectSigningAlgValuesSupported)] - public IEnumerable? RequestObjectSigningAlgValuesSupported { init; get; } = default!; + public IEnumerable? RequestObjectSigningAlgValuesSupported { init; get; } /// /// Indicates whether the OpenID Provider mandates that all request objects must be signed.