From 358ac72b5d8d4a5e6bb5366aca8710813ea7940a Mon Sep 17 00:00:00 2001 From: Kirill Kovalev Date: Sat, 28 Sep 2024 11:40:28 +0300 Subject: [PATCH] Added registration and metadata --- .../BackchannelTokenDeliveryModes.cs | 51 +++++++ .../Common/Constants/GrantTypes.cs | 8 +- .../Common/{OperationResult.cs => Result.cs} | 14 +- Abblix.Oidc.Server/Common/StringExtensions.cs | 5 +- .../RequestFetching/RequestObjectFetcher.cs | 4 +- .../BackChannelAuthenticationHandler.cs | 4 +- .../CompositeRequestFetcher.cs | 6 +- ...BackChannelAuthenticationRequestFetcher.cs | 2 +- .../RequestFetching/RequestObjectFetcher.cs | 6 +- .../Validation/UserIdentityValidator.cs | 10 +- .../Revocation/RevocationRequestValidator.cs | 40 ++++-- .../PrivateKeyJwtAuthenticator.cs | 129 +++++++++--------- .../RequestObject/RequestObjectFetcherBase.cs | 6 +- .../Model/ClientRegistrationRequest.cs | 7 +- .../Model/ConfigurationResponse.cs | 38 +++++- Abblix.Oidc.Server/Model/ErrorResponse.cs | 2 +- 16 files changed, 220 insertions(+), 112 deletions(-) create mode 100644 Abblix.Oidc.Server/Common/Constants/BackchannelTokenDeliveryModes.cs rename Abblix.Oidc.Server/Common/{OperationResult.cs => Result.cs} (88%) diff --git a/Abblix.Oidc.Server/Common/Constants/BackchannelTokenDeliveryModes.cs b/Abblix.Oidc.Server/Common/Constants/BackchannelTokenDeliveryModes.cs new file mode 100644 index 00000000..08781fb2 --- /dev/null +++ b/Abblix.Oidc.Server/Common/Constants/BackchannelTokenDeliveryModes.cs @@ -0,0 +1,51 @@ +// Abblix OIDC Server Library +// Copyright (c) Abblix LLP. All rights reserved. +// +// DISCLAIMER: This software is provided 'as-is', without any express or implied +// warranty. Use at your own risk. Abblix LLP is not liable for any damages +// arising from the use of this software. +// +// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed +// in any form outside of the official GitHub repository at: +// https://github.com/Abblix/OIDC.Server. All development and modifications +// must occur within the official repository and are managed solely by Abblix LLP. +// +// Unauthorized use, modification, or distribution of this software is strictly +// prohibited and may be subject to legal action. +// +// For full licensing terms, please visit: +// +// https://oidc.abblix.com/license +// +// CONTACT: For license inquiries or permissions, contact Abblix LLP at +// info@abblix.com + +namespace Abblix.Oidc.Server.Common.Constants; + +/// +/// Defines the available delivery modes for backchannel token delivery in Client-Initiated Backchannel Authentication +/// (CIBA). These modes specify how the authentication server communicates the result of the backchannel authentication +/// process to the client. +/// +public class BackchannelTokenDeliveryModes +{ + /// + /// The "poll" mode where the client periodically polls the authorization server to check if the user has been + /// authenticated. This method is useful in cases where the client prefers to control the polling interval and + /// the process. + /// + public const string Poll = "poll"; + + /// + /// The "ping" mode where the authorization server notifies the client via a callback when the user has been + /// authenticated. The client still needs to make a subsequent request to retrieve the token. + /// + public const string Ping = "ping"; + + /// + /// The "push" mode where the authorization server directly pushes the token to the client once the user has been + /// authenticated. This method streamlines the process by delivering the token to the client without the need for + /// further requests. + /// + public const string Push = "push"; +} diff --git a/Abblix.Oidc.Server/Common/Constants/GrantTypes.cs b/Abblix.Oidc.Server/Common/Constants/GrantTypes.cs index f39cf184..2f67f575 100644 --- a/Abblix.Oidc.Server/Common/Constants/GrantTypes.cs +++ b/Abblix.Oidc.Server/Common/Constants/GrantTypes.cs @@ -40,13 +40,13 @@ public static class GrantTypes public const string ClientCredentials = "client_credentials"; /// - /// Represents the Refresh Token grant type. Used to obtain a new access token using a refresh token. - /// Helpful for maintaining user sessions without requiring reauthentication. + /// Represents the Refresh Token grant type. Used to get a new access token using a refresh token. + /// Helpful for maintaining user sessions without requiring re-authentication. /// public const string RefreshToken = "refresh_token"; /// - /// Represents the Implicit grant type. Used in single-page applications to obtain access tokens directly + /// Represents the Implicit grant type. Used in single-page applications to get access tokens directly /// from the authorization endpoint. Suitable for browser-based applications. /// public const string Implicit = "implicit"; @@ -71,7 +71,7 @@ public static class GrantTypes /// /// Represents the Device Authorization grant type. This grant type is used in scenarios where the client device - /// lacks a browser or has limited input capabilities, allowing it to obtain user authorization from another device + /// lacks a browser or has limited input capabilities, allowing it to get user authorization from another device /// with better input capabilities. It is particularly useful for devices in the IoT (Internet of Things) sector /// and smart devices that require user interaction for authorization. /// diff --git a/Abblix.Oidc.Server/Common/OperationResult.cs b/Abblix.Oidc.Server/Common/Result.cs similarity index 88% rename from Abblix.Oidc.Server/Common/OperationResult.cs rename to Abblix.Oidc.Server/Common/Result.cs index a75879d9..c2a3a6f2 100644 --- a/Abblix.Oidc.Server/Common/OperationResult.cs +++ b/Abblix.Oidc.Server/Common/Result.cs @@ -29,15 +29,15 @@ namespace Abblix.Oidc.Server.Common; /// , or result in an error with an error code and description. /// /// The type of the value returned in case of a successful result. -public abstract record OperationResult +public abstract record Result { - private OperationResult() { } + private Result() { } /// - /// Represents a successful result containing a value of type . + /// Represents a successful result containing a value of specific type. /// /// The value returned by the successful operation. - public sealed record Success(T Value) : OperationResult + public sealed record Success(T Value) : Result { /// /// Returns a string that represents the current object, either the successful value or an error description. @@ -54,7 +54,7 @@ public sealed record Success(T Value) : OperationResult /// /// 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) : Result { /// /// Returns a string that represents the current object, either the successful value or an error description. @@ -71,12 +71,12 @@ public override string ToString() /// 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 Result(T value) => new Success(value); /// /// Implicitly converts an into an result. /// /// The error response to be wrapped as an error result. - public static implicit operator OperationResult(ErrorResponse error) + public static implicit operator Result(ErrorResponse error) => new Error(error.Error, error.ErrorDescription); } diff --git a/Abblix.Oidc.Server/Common/StringExtensions.cs b/Abblix.Oidc.Server/Common/StringExtensions.cs index 63ed9d2f..12bfa971 100644 --- a/Abblix.Oidc.Server/Common/StringExtensions.cs +++ b/Abblix.Oidc.Server/Common/StringExtensions.cs @@ -32,7 +32,7 @@ internal static class StringExtensions /// /// The array of strings to check. /// The flag to search for. - /// True if the flag is found; otherwise, false. + /// True, if the flag is found, otherwise, false. public static bool HasFlag(this string[]? values, string flag) => values != null && values.Contains(flag, StringComparer.OrdinalIgnoreCase); @@ -56,7 +56,8 @@ public static bool TryParse(this string source, string[] allowedValues, char sep var result = new List(sourceValues.Length); foreach (var sourceValue in sourceValues) { - var allowedValue = allowedValues.FirstOrDefault(value => string.Equals(value, sourceValue, StringComparison.OrdinalIgnoreCase)); + var allowedValue = allowedValues.FirstOrDefault( + value => string.Equals(value, sourceValue, StringComparison.OrdinalIgnoreCase)); if (allowedValue == null) { values = default!; diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetcher.cs b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetcher.cs index 363ac3cc..a6bc8ce1 100644 --- a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetcher.cs +++ b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetcher.cs @@ -81,9 +81,9 @@ public async Task FetchAsync(AuthorizationRequest request) var fetchResult = await FetchAsync(request, request.Request); return fetchResult switch { - OperationResult.Success(var authorizationRequest) => authorizationRequest, + Result.Success(var authorizationRequest) => authorizationRequest, - OperationResult.Error(var error, var description) + Result.Error(var error, var description) => ErrorFactory.ValidationError(error, description), _ => throw new UnexpectedTypeException(nameof(fetchResult), fetchResult.GetType()), diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationHandler.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationHandler.cs index 87075de5..bc0f686c 100644 --- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationHandler.cs +++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationHandler.cs @@ -74,11 +74,11 @@ public async Task HandleAsync( var fetchResult = await _fetcher.FetchAsync(request); switch (fetchResult) { - case OperationResult.Success(var requestObject): + case Result.Success(var requestObject): request = requestObject; break; - case OperationResult.Error(var error, var description): + case Result.Error(var error, var description): return new BackChannelAuthenticationError(error, description); default: diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/CompositeRequestFetcher.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/CompositeRequestFetcher.cs index 9c6722ed..488b2634 100644 --- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/CompositeRequestFetcher.cs +++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/CompositeRequestFetcher.cs @@ -55,18 +55,18 @@ public CompositeRequestFetcher(IBackChannelAuthenticationRequestFetcher[] fetche /// The backchannel authentication request to be processed. /// A that represents the outcome of the fetching process. /// It could be a success, fault, or an unexpected type error if the result is not handled correctly. - public async Task> FetchAsync(BackChannelAuthenticationRequest request) + public async Task> FetchAsync(BackChannelAuthenticationRequest request) { foreach (var fetcher in _fetchers) { var result = await fetcher.FetchAsync(request); switch (result) { - case OperationResult.Success(var success): + case Result.Success(var success): request = success; continue; - case OperationResult.Error error: + case Result.Error error: return error; default: diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/IBackChannelAuthenticationRequestFetcher.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/IBackChannelAuthenticationRequestFetcher.cs index 660fbe73..79ea4e8d 100644 --- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/IBackChannelAuthenticationRequestFetcher.cs +++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/IBackChannelAuthenticationRequestFetcher.cs @@ -39,5 +39,5 @@ public interface IBackChannelAuthenticationRequestFetcher /// The backchannel authentication request to be fetched and validated. /// A task that represents the asynchronous operation. The task result contains a /// indicating whether the fetch was successful or if it resulted in an error. - Task> FetchAsync(BackChannelAuthenticationRequest request); + Task> FetchAsync(BackChannelAuthenticationRequest request); } diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetcher.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetcher.cs index 128f105d..ff3b01d2 100644 --- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetcher.cs +++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetcher.cs @@ -61,7 +61,7 @@ public RequestObjectFetcher( /// encapsulates the full request details. /// /// - /// A task representing the asynchronous operation. The task result contains an + /// A task representing the asynchronous operation. The task result contains an /// with either a successfully processed or an error result /// indicating validation issues. /// @@ -70,8 +70,8 @@ public RequestObjectFetcher( /// the backchannel authentication request. /// If the JWT is valid, it binds the JWT payload to the backchannel authentication request model. /// If the JWT is invalid, the method logs a warning and returns an error result encapsulated in - /// an . + /// an . /// - public Task> FetchAsync(BackChannelAuthenticationRequest request) + public Task> FetchAsync(BackChannelAuthenticationRequest request) => FetchAsync(request, request.Request); } diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserIdentityValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserIdentityValidator.cs index 88a47906..d8a4aa1e 100644 --- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserIdentityValidator.cs +++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserIdentityValidator.cs @@ -123,11 +123,11 @@ public UserIdentityValidator( var idTokenResult = await ValidateIdTokenHint(context, request.IdTokenHint); switch (idTokenResult) { - case OperationResult.Success(var idToken): + case Result.Success(var idToken): context.IdToken = idToken; break; - case OperationResult.Error(var error, var description): + case Result.Error(var error, var description): return new BackChannelAuthenticationValidationError(error, description); } } @@ -141,10 +141,10 @@ public UserIdentityValidator( /// The validation context containing the client information. /// The ID token hint string to be validated. /// - /// An representing the validation result, + /// An representing the validation result, /// which can either be a successful token or an error. /// - private async Task> ValidateIdTokenHint( + private async Task> ValidateIdTokenHint( BackChannelAuthenticationValidationContext context, string idTokenHint) { @@ -173,7 +173,7 @@ private async Task> ValidateIdTokenHint( } // Helper method to generate an error response for invalid requests - OperationResult.Error InvalidRequest(string description) + Result.Error InvalidRequest(string description) => new (ErrorCodes.InvalidRequest, description); } } diff --git a/Abblix.Oidc.Server/Endpoints/Revocation/RevocationRequestValidator.cs b/Abblix.Oidc.Server/Endpoints/Revocation/RevocationRequestValidator.cs index 6fa1d0c4..047ba8c7 100644 --- a/Abblix.Oidc.Server/Endpoints/Revocation/RevocationRequestValidator.cs +++ b/Abblix.Oidc.Server/Endpoints/Revocation/RevocationRequestValidator.cs @@ -27,7 +27,7 @@ using Abblix.Oidc.Server.Features.ClientAuthentication; using Abblix.Oidc.Server.Features.Tokens.Validation; using Abblix.Oidc.Server.Model; - +using Microsoft.Extensions.Logging; namespace Abblix.Oidc.Server.Endpoints.Revocation; @@ -44,18 +44,26 @@ 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. /// + /// Provides logging capabilities to record validation outcomes and errors. /// - /// The client request authenticator to be used in the validation process. + /// The client request authenticator to be used in the validation process. Ensures that the client sending the + /// revocation request is authenticated and authorized to revoke tokens. + /// /// - /// The JWT validator to be used for validating the token included in the revocation request. + /// The JWT validator to be used for validating the token included in the revocation request. Ensures that + /// the token is valid and that it belongs to the client requesting revocation. + /// public RevocationRequestValidator( + ILogger logger, IClientAuthenticator clientAuthenticator, IAuthServiceJwtValidator jwtValidator) { + _logger = logger; _clientAuthenticator = clientAuthenticator; _jwtValidator = jwtValidator; } + private readonly ILogger _logger; private readonly IClientAuthenticator _clientAuthenticator; private readonly IAuthServiceJwtValidator _jwtValidator; @@ -65,17 +73,24 @@ public RevocationRequestValidator( /// 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 - /// . - /// The result indicates whether the request is valid or contains any errors. + /// . The result indicates whether the request is valid or + /// contains any errors. /// + /// + /// This method follows the OAuth 2.0 revocation flow, ensuring that the token being revoked belongs to + /// the authenticated client, protecting against cross-client token revocation. In case of validation failure, + /// it logs a warning with the specific cause. + /// public async Task ValidateAsync( RevocationRequest revocationRequest, ClientRequest clientRequest) { + // Authenticate the client making the revocation request. var clientInfo = await _clientAuthenticator.TryAuthenticateClientAsync(clientRequest); if (clientInfo == null) { @@ -87,16 +102,21 @@ public async Task ValidateAsync( var result = await _jwtValidator.ValidateAsync(revocationRequest.Token); switch (result) { + // If token validation fails, log the error and return an invalid token result. + case JwtValidationError error: + _logger.LogWarning("The token validation failed: {@Error}", error); + return ValidRevocationRequest.InvalidToken(revocationRequest); + + // If the token was issued to a different client, log a warning and return an invalid token result. case ValidJsonWebToken { Token.Payload.ClientId: var clientId } when clientId != clientInfo.ClientId: - //TODO maybe log the message: The token was issued to another client? + _logger.LogWarning("The token was issued to another client {ClientId}", clientId); return ValidRevocationRequest.InvalidToken(revocationRequest); + // If the token is valid and belongs to the authenticated client, return a valid revocation request. case ValidJsonWebToken { Token: var token }: return new ValidRevocationRequest(revocationRequest, token); - case JwtValidationError: //TODO log error - return ValidRevocationRequest.InvalidToken(revocationRequest); - + // Unexpected result type, throw an exception to indicate a misconfiguration or unexpected validation outcome. default: throw new UnexpectedTypeException(nameof(result), result.GetType()); } diff --git a/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs b/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs index 3c52a551..4d6b551e 100644 --- a/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs +++ b/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs @@ -74,83 +74,86 @@ public IEnumerable ClientAuthenticationMethodsSupported } /// - /// Attempts to authenticate a client using the Private Key JWT method by validating the JWT provided in the client request. + /// Attempts to authenticate a client using the Private Key JWT method by validating the JWT provided in + /// the client request. /// /// The client request containing the JWT to authenticate. /// The authenticated , or null if authentication fails. public async Task TryAuthenticateClientAsync(ClientRequest request) { - if (request.ClientAssertionType == ClientAssertionTypes.JwtBearer) + if (request.ClientAssertionType != ClientAssertionTypes.JwtBearer) { - if (!request.ClientAssertion.HasValue()) - { + _logger.LogWarning( + $"{ClientAssertionType} is not '{ClientAssertionTypes.JwtBearer}'"); + return null; + } + + if (!request.ClientAssertion.HasValue()) + { + _logger.LogWarning( + $"{ClientAssertionType} is '{ClientAssertionTypes.JwtBearer}', but {ClientAssertion} is empty"); + return null; + } + + JwtValidationResult? result; + ClientInfo? clientInfo; + using (var scope = _serviceProvider.CreateScope()) + { + var tokenValidator = scope.ServiceProvider.GetRequiredService(); + (result, clientInfo) = await tokenValidator.ValidateAsync(request.ClientAssertion); + } + + JsonWebToken token; + switch (result, clientInfo) + { + case (ValidJsonWebToken validToken, { TokenEndpointAuthMethod: ClientAuthenticationMethods.PrivateKeyJwt }): + token = validToken.Token; + break; + + case (ValidJsonWebToken, clientInfo: not null): _logger.LogWarning( - $"{ClientAssertionType} is '{ClientAssertionTypes.JwtBearer}', but {ClientAssertion} is empty"); - return null; - } - - JwtValidationResult? result; - ClientInfo? clientInfo; - using (var scope = _serviceProvider.CreateScope()) - { - var tokenValidator = scope.ServiceProvider.GetRequiredService(); - (result, clientInfo) = await tokenValidator.ValidateAsync(request.ClientAssertion); - } - - JsonWebToken token; - switch (result, clientInfo) - { - case (ValidJsonWebToken validToken, { TokenEndpointAuthMethod: ClientAuthenticationMethods.PrivateKeyJwt }): - token = validToken.Token; - break; - - case (ValidJsonWebToken, clientInfo: not null): - _logger.LogWarning( - "The authentication method is not allowed for the client {@ClientId}", - clientInfo.ClientId); - return null; - - case (ValidJsonWebToken, clientInfo: null): - _logger.LogWarning("Something went wrong, token cannot be validated without client specified"); - return null; - - case (JwtValidationError error, _): - _logger.LogWarning("Invalid PrivateKeyJwt: {@Error}", error); - return null; - - default: - throw new UnexpectedTypeException(nameof(result), result.GetType()); - } - - string? subject; - try - { - subject = token.Payload.Subject; - } - catch (InvalidOperationException ex) - { - _logger.LogWarning("Invalid PrivateKeyJwt: {Message}", ex.Message); + "The authentication method is not allowed for the client {@ClientId}", + clientInfo.ClientId); return null; - } - var issuer = token.Payload.Issuer; - if (issuer == null || subject == null || issuer != subject) - { - _logger.LogWarning( - "Invalid PrivateKeyJwt: iss is \'{Issuer}\', but sub is {Subject}", - issuer, subject); + case (ValidJsonWebToken, clientInfo: null): + _logger.LogWarning("Something went wrong, token cannot be validated without client specified"); + return null; + case (JwtValidationError error, _): + _logger.LogWarning("Invalid PrivateKeyJwt: {@Error}", error); return null; - } - if (token is { Payload: { JwtId: { } jwtId, ExpiresAt: { } expiresAt } }) - { - await _tokenRegistry.SetStatusAsync(jwtId, JsonWebTokenStatus.Used, expiresAt); - } + default: + throw new UnexpectedTypeException(nameof(result), result.GetType()); + } + + string? subject; + try + { + subject = token.Payload.Subject; + } + catch (InvalidOperationException ex) + { + _logger.LogWarning("Invalid PrivateKeyJwt: {Message}", ex.Message); + return null; + } + + var issuer = token.Payload.Issuer; + if (issuer == null || subject == null || issuer != subject) + { + _logger.LogWarning( + "Invalid PrivateKeyJwt: iss is \'{Issuer}\', but sub is {Subject}", + issuer, subject); + + return null; + } - return clientInfo; + if (token is { Payload: { JwtId: { } jwtId, ExpiresAt: { } expiresAt } }) + { + await _tokenRegistry.SetStatusAsync(jwtId, JsonWebTokenStatus.Used, expiresAt); } - return null; + return clientInfo; } } diff --git a/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcherBase.cs b/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcherBase.cs index d9ddfcb0..a3fd3744 100644 --- a/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcherBase.cs +++ b/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcherBase.cs @@ -66,14 +66,14 @@ protected RequestObjectFetcherBase( /// The initial request model to bind the JWT payload to. /// The JWT contained within the request, if any. /// - /// A task representing the asynchronous operation. The task result contains an + /// A task representing the asynchronous operation. The task result contains an /// which either represents a successfully processed request or an error indicating issues with the JWT validation. /// /// /// This method is used to decode and validate the JWT contained in the request. If the JWT is valid, the payload /// is bound to the request model. If the JWT is invalid, an error is returned and logged. /// - protected async Task> FetchAsync(T request, string? requestObject) + protected async Task> FetchAsync(T request, string? requestObject) where T : class { if (!requestObject.HasValue()) @@ -106,7 +106,7 @@ protected async Task> FetchAsync(T request, string? reques throw new UnexpectedTypeException(nameof(result), result.GetType()); } - static OperationResult.Error InvalidRequestObject(string description) + static Result.Error InvalidRequestObject(string description) => new(ErrorCodes.InvalidRequestObject, description); } } diff --git a/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs b/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs index 0d848cd2..91832f2e 100644 --- a/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs +++ b/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs @@ -38,7 +38,7 @@ namespace Abblix.Oidc.Server.Model; public record ClientRegistrationRequest { /// - /// Array of redirection URIs for the OP to redirect the End-User after obtaining authorization. + /// Array of redirection URIs for the OP to redirect the End-User after getting authorization. /// [JsonPropertyName(Parameters.RedirectUris)] [Required] @@ -312,8 +312,9 @@ public record ClientRegistrationRequest /// true if the front-channel logout requires a session identifier; otherwise, false. /// /// - /// This property corresponds to the 'frontchannel_logout_session_required' parameter in the OpenID Connect specification. - /// When set to true, it indicates that the client requires a session identifier to be sent with front-channel logout requests. + /// This property corresponds to the 'frontchannel_logout_session_required' parameter in the OpenID Connect + /// specification. When set to true, it indicates that the client requires a session identifier + /// to be sent with front-channel logout requests. /// This is typically used to facilitate logout across multiple sessions or devices. /// [JsonPropertyName(Parameters.FrontChannelLogoutSessionRequired)] diff --git a/Abblix.Oidc.Server/Model/ConfigurationResponse.cs b/Abblix.Oidc.Server/Model/ConfigurationResponse.cs index b96339f8..8c6f28db 100644 --- a/Abblix.Oidc.Server/Model/ConfigurationResponse.cs +++ b/Abblix.Oidc.Server/Model/ConfigurationResponse.cs @@ -33,7 +33,9 @@ namespace Abblix.Oidc.Server.Model; public record ConfigurationResponse { /// - /// Nested class containing string constants for JSON property names. + /// Nested class containing string constants for JSON property names used in the configuration response. + /// These names map directly to the fields returned by the OpenID Connect discovery document, ensuring + /// proper serialization and deserialization of configuration data. /// public static class Parameters { @@ -68,6 +70,10 @@ public static class Parameters public const string PushedAuthorizationRequestEndpoint = "pushed_authorization_request_endpoint"; public const string RequirePushedAuthorizationRequests = "require_pushed_authorization_requests"; public const string RequireSignedRequestObject = "require_signed_request_object"; + public const string BackchannelTokenDeliveryModesSupported = "backchannel_token_delivery_modes_supported"; + public const string BackchannelAuthenticationEndpoint = "backchannel_authentication_endpoint"; + public const string BackchannelAuthenticationRequestSigningAlgValuesSupported = "backchannel_authentication_request_signing_alg_values_supported"; + public const string BackchannelUserCodeParameterSupported = "backchannel_user_code_parameter_supported"; } /// @@ -133,7 +139,8 @@ public static class Parameters public Uri? RegistrationEndpoint { init; get; } /// - /// The URL for the Pushed Authorization Request endpoint, which allows clients to pre-register authorization requests. + /// The URL for the Pushed Authorization Request endpoint, which allows clients to pre-register authorization + /// requests. /// [JsonPropertyName(Parameters.PushedAuthorizationRequestEndpoint)] public Uri? PushedAuthorizationRequestEndpoint { get; set; } @@ -277,5 +284,30 @@ public static class Parameters /// integrity of request objects received by the provider. /// [JsonPropertyName(Parameters.RequireSignedRequestObject)] - public bool RequireSignedRequestObject { init; get; } //TODO use it! + public bool? RequireSignedRequestObject { init; get; } //TODO use it! + + /// + /// Lists the supported backchannel token delivery modes for client-initiated backchannel authentication. + /// + [JsonPropertyName(Parameters.BackchannelTokenDeliveryModesSupported)] + public IEnumerable? BackchannelTokenDeliveryModesSupported { get; init; } + + /// + /// The backchannel authentication endpoint for initiating CIBA (Client-Initiated Backchannel Authentication) + /// requests. + /// + [JsonPropertyName(Parameters.BackchannelAuthenticationEndpoint)] + public Uri? BackchannelAuthenticationEndpoint { get; init; } + + /// + /// Lists the supported signing algorithms for backchannel authentication requests. + /// + [JsonPropertyName(Parameters.BackchannelAuthenticationRequestSigningAlgValuesSupported)] + public IEnumerable? BackchannelAuthenticationRequestSigningAlgValuesSupported { get; init; } + + /// + /// Indicates whether the OpenID Provider supports the backchannel user code parameter for CIBA. + /// + [JsonPropertyName(Parameters.BackchannelUserCodeParameterSupported)] + public bool? BackchannelUserCodeParameterSupported { get; init; } } diff --git a/Abblix.Oidc.Server/Model/ErrorResponse.cs b/Abblix.Oidc.Server/Model/ErrorResponse.cs index 089da54b..8a42f564 100644 --- a/Abblix.Oidc.Server/Model/ErrorResponse.cs +++ b/Abblix.Oidc.Server/Model/ErrorResponse.cs @@ -27,7 +27,7 @@ namespace Abblix.Oidc.Server.Model; /// /// Represents a standardized error response, commonly used in web APIs and OAuth2/OpenID Connect protocols. /// -public record ErrorResponse(string Error, string ErrorDescription) +public readonly record struct ErrorResponse(string Error, string ErrorDescription) { /// /// The error code representing the specific type of error encountered.