diff --git a/.gitignore b/.gitignore
index 37537f2c..0b8a16f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -364,7 +364,7 @@ FodyWeavers.xsd
/.idea/.idea.Oidc.Server.dir/.idea
.env
-/.idea/.idea.Abblix.Oidc/Docker/
+/.idea/
/local-npm.cmd
/local-rebuild.cmd
/Certificates/myCA/private/Abblix Licensing.pem
diff --git a/.idea/.idea.Abblix.Oidc/.idea/.gitignore b/.idea/.idea.Abblix.Oidc/.idea/.gitignore
deleted file mode 100644
index 53ed85cd..00000000
--- a/.idea/.idea.Abblix.Oidc/.idea/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/modules.xml
-/contentModel.xml
-/projectSettingsUpdater.xml
-/.idea.Abblix.Oidc.iml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.idea.Abblix.Oidc/.idea/.name b/.idea/.idea.Abblix.Oidc/.idea/.name
deleted file mode 100644
index 32fa4ab0..00000000
--- a/.idea/.idea.Abblix.Oidc/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-Abblix.Oidc
\ No newline at end of file
diff --git a/.idea/.idea.Abblix.Oidc/.idea/encodings.xml b/.idea/.idea.Abblix.Oidc/.idea/encodings.xml
deleted file mode 100644
index df87cf95..00000000
--- a/.idea/.idea.Abblix.Oidc/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.Abblix.Oidc/.idea/indexLayout.xml b/.idea/.idea.Abblix.Oidc/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163c..00000000
--- a/.idea/.idea.Abblix.Oidc/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.Abblix.Oidc/.idea/vcs.xml b/.idea/.idea.Abblix.Oidc/.idea/vcs.xml
deleted file mode 100644
index 35eb1ddf..00000000
--- a/.idea/.idea.Abblix.Oidc/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Abblix.DependencyInjection/Abblix.DependencyInjection.csproj b/Abblix.DependencyInjection/Abblix.DependencyInjection.csproj
index 6beffc4c..7ad2e775 100644
--- a/Abblix.DependencyInjection/Abblix.DependencyInjection.csproj
+++ b/Abblix.DependencyInjection/Abblix.DependencyInjection.csproj
@@ -8,7 +8,7 @@
true
Abblix.DependencyInjection
Abblix DependencyInjection
- Enhances .NET applications by extending the standard dependency injection framework. It supports essential patterns such as service aliasing, composite services, and decorators, simplifying and enhancing service registration and resolution processes.
+ Enhances .NET applications by extending the .NET Dependency Injection framework. It supports essential patterns such as service aliasing, composite services and decorators, simplifying and enhancing service registration and resolution processes.
Abblix LLP
https://www.abblix.com/abblix-oidc-server
https://github.com/Abblix/Oidc.Server
@@ -20,8 +20,8 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
- 1.1.0.0
- 1.1.0.0
+ 1.2.0.0
+ 1.2.0.0
diff --git a/Abblix.Jwt.UnitTests/Abblix.Jwt.UnitTests.csproj b/Abblix.Jwt.UnitTests/Abblix.Jwt.UnitTests.csproj
index ff1a425e..9f1b9b6f 100644
--- a/Abblix.Jwt.UnitTests/Abblix.Jwt.UnitTests.csproj
+++ b/Abblix.Jwt.UnitTests/Abblix.Jwt.UnitTests.csproj
@@ -10,8 +10,8 @@
-
-
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
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/Abblix.Jwt.csproj b/Abblix.Jwt/Abblix.Jwt.csproj
index f1fbc345..9f378856 100644
--- a/Abblix.Jwt/Abblix.Jwt.csproj
+++ b/Abblix.Jwt/Abblix.Jwt.csproj
@@ -20,13 +20,13 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
- 1.1.0.0
- 1.1.0.0
+ 1.2.0.0
+ 1.2.0.0
-
+
diff --git a/Abblix.Jwt/IJsonWebTokenCreator.cs b/Abblix.Jwt/IJsonWebTokenCreator.cs
index 92d8bf61..84775fd4 100644
--- a/Abblix.Jwt/IJsonWebTokenCreator.cs
+++ b/Abblix.Jwt/IJsonWebTokenCreator.cs
@@ -30,7 +30,7 @@ public interface IJsonWebTokenCreator
///
/// Lists the all supported signing algorithms for JWT creation.
///
- IEnumerable SigningAlgValuesSupported { get; }
+ IEnumerable SignedResponseAlgorithmsSupported { get; }
///
/// Issues a new JWT based on the specified JsonWebToken object, signing key, and optional encrypting key.
diff --git a/Abblix.Jwt/IJsonWebTokenValidator.cs b/Abblix.Jwt/IJsonWebTokenValidator.cs
index 80430fcc..266dc3ba 100644
--- a/Abblix.Jwt/IJsonWebTokenValidator.cs
+++ b/Abblix.Jwt/IJsonWebTokenValidator.cs
@@ -31,7 +31,7 @@ public interface IJsonWebTokenValidator
/// Indicates which algorithms are accepted by the validator for verifying the signatures of incoming JWTs,
/// ensuring that only tokens signed with recognized and secure algorithms are considered valid.
///
- IEnumerable SigningAlgValuesSupported { get; }
+ IEnumerable SigningAlgorithmsSupported { get; }
///
/// Asynchronously validates a JWT against a set of specified parameters.
diff --git a/Abblix.Jwt/JsonWebKeyExtensions.cs b/Abblix.Jwt/JsonWebKeyExtensions.cs
index c9602195..1d79d0b5 100644
--- a/Abblix.Jwt/JsonWebKeyExtensions.cs
+++ b/Abblix.Jwt/JsonWebKeyExtensions.cs
@@ -41,11 +41,7 @@ public static class JsonWebKeyExtensions
/// Thrown when the algorithm is not supported.
public static SigningCredentials ToSigningCredentials(this JsonWebKey jsonWebKey)
{
- return jsonWebKey.Algorithm switch
- {
- SigningAlgorithms.RS256 => new SigningCredentials(jsonWebKey.ToSecurityKey(), SigningAlgorithms.RS256),
- _ => throw new InvalidOperationException($"Not supported algorithm: {jsonWebKey.Algorithm}"),
- };
+ return new SigningCredentials(jsonWebKey.ToSecurityKey(), jsonWebKey.Algorithm);
}
///
@@ -68,7 +64,7 @@ public static EncryptingCredentials ToEncryptingCredentials(this JsonWebKey json
{
return jsonWebKey.Algorithm switch
{
- SigningAlgorithms.RS256 => new EncryptingCredentials(
+ SecurityAlgorithms.RsaSha256 => new EncryptingCredentials(
jsonWebKey.ToSecurityKey(),
SecurityAlgorithms.RsaOAEP,
SecurityAlgorithms.Aes128CbcHmacSha256),
diff --git a/Abblix.Jwt/JsonWebTokenAlgorithms.cs b/Abblix.Jwt/JsonWebTokenAlgorithms.cs
index e863372c..b3c7ba69 100644
--- a/Abblix.Jwt/JsonWebTokenAlgorithms.cs
+++ b/Abblix.Jwt/JsonWebTokenAlgorithms.cs
@@ -20,8 +20,6 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-using System.IdentityModel.Tokens.Jwt;
-
namespace Abblix.Jwt;
internal static class JsonWebTokenAlgorithms
@@ -33,8 +31,24 @@ internal static class JsonWebTokenAlgorithms
/// for supported algorithms used by both JsonWebTokenCreator and JsonWebTokenValidator classes,
/// leveraging JwtSecurityTokenHandler under the hood.
///
- public static readonly IEnumerable SigningAlgValuesSupported =
- JwtSecurityTokenHandler.DefaultOutboundAlgorithmMap.Values
- .Append(SigningAlgorithms.None)
- .ToArray();
+ public static readonly IEnumerable SigningAlgValuesSupported = new[]
+ {
+ SigningAlgorithms.RS256,
+ SigningAlgorithms.RS384,
+ SigningAlgorithms.RS512,
+
+ SigningAlgorithms.PS256,
+ SigningAlgorithms.PS384,
+ SigningAlgorithms.PS512,
+
+ SigningAlgorithms.ES256,
+ SigningAlgorithms.ES384,
+ SigningAlgorithms.ES512,
+
+ SigningAlgorithms.HS256,
+ SigningAlgorithms.HS384,
+ SigningAlgorithms.HS512,
+
+ SigningAlgorithms.None,
+ };
}
diff --git a/Abblix.Jwt/JsonWebTokenCreator.cs b/Abblix.Jwt/JsonWebTokenCreator.cs
index ab173ab6..362e4897 100644
--- a/Abblix.Jwt/JsonWebTokenCreator.cs
+++ b/Abblix.Jwt/JsonWebTokenCreator.cs
@@ -40,7 +40,7 @@ public sealed class JsonWebTokenCreator : IJsonWebTokenCreator
/// This property reflects the JWT security token handler's default outbound algorithm mapping,
/// indicating the algorithms available for signing the tokens.
///
- public IEnumerable SigningAlgValuesSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported;
+ public IEnumerable SignedResponseAlgorithmsSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported;
///
/// Asynchronously issues a JWT based on the specified JsonWebToken, signing key, and optional encrypting key.
@@ -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.Jwt/JsonWebTokenHeader.cs b/Abblix.Jwt/JsonWebTokenHeader.cs
index 4434c1b3..276978bd 100644
--- a/Abblix.Jwt/JsonWebTokenHeader.cs
+++ b/Abblix.Jwt/JsonWebTokenHeader.cs
@@ -68,7 +68,7 @@ public string? Type
///
///
/// The 'alg' parameter identifies the cryptographic algorithm used to secure the JWT.
- /// Common algorithms include HS256, RS256, and ES256. It is crucial for verifying the JWT's integrity.
+ /// Common algorithms include HS256, RS256, and ES256. It is crucial for verifying the JWT integrity.
///
public string? Algorithm
{
diff --git a/Abblix.Jwt/JsonWebTokenValidator.cs b/Abblix.Jwt/JsonWebTokenValidator.cs
index c6c9b762..ad14f42c 100644
--- a/Abblix.Jwt/JsonWebTokenValidator.cs
+++ b/Abblix.Jwt/JsonWebTokenValidator.cs
@@ -38,7 +38,7 @@ public class JsonWebTokenValidator : IJsonWebTokenValidator
/// by the JwtSecurityTokenHandler for inbound tokens, as well as an option to accept tokens without a signature.
/// This allows for flexibility in validating JWTs with various security requirements.
///
- public IEnumerable SigningAlgValuesSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported;
+ public IEnumerable SigningAlgorithmsSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported;
///
/// Asynchronously validates a JWT string against specified validation parameters.
diff --git a/Abblix.Jwt/SigningAlgorithms.cs b/Abblix.Jwt/SigningAlgorithms.cs
index 04326e6f..ded72565 100644
--- a/Abblix.Jwt/SigningAlgorithms.cs
+++ b/Abblix.Jwt/SigningAlgorithms.cs
@@ -27,6 +27,13 @@ namespace Abblix.Jwt;
///
public static class SigningAlgorithms
{
+ ///
+ /// Represents the "none" signing algorithm.
+ /// This value is used when no digital signature or MAC operation is performed on the JWT.
+ /// It is important to use this algorithm with caution as it implies that the JWT is unprotected.
+ ///
+ public const string None = "none";
+
///
/// Represents the RS256 (RSA Signature with SHA-256) signing algorithm.
/// This algorithm is commonly used for creating JWT signatures using RSA keys with SHA-256 hashing.
@@ -34,9 +41,69 @@ public static class SigningAlgorithms
public const string RS256 = "RS256";
///
- /// Represents the "none" signing algorithm.
- /// This value is used when no digital signature or MAC operation is performed on the JWT.
- /// It is important to use this algorithm with caution as it implies that the JWT is unprotected.
+ /// Represents the RS384 (RSA Signature with SHA-384) signing algorithm.
+ /// This algorithm enhances security by using SHA-384 for the hashing process while signing JWTs.
///
- public const string None = "none";
+ public const string RS384 = "RS384";
+
+ ///
+ /// Represents the RS512 (RSA Signature with SHA-512) signing algorithm.
+ /// This algorithm provides a higher level of security by using SHA-512 for signing JWTs.
+ ///
+ public const string RS512 = "RS512";
+
+ ///
+ /// Represents the PS256 (RSA PSS Signature with SHA-256) signing algorithm.
+ /// This algorithm is similar to RS256 but uses RSA PSS (Probabilistic Signature Scheme) for improved security.
+ ///
+ public const string PS256 = "PS256";
+
+ ///
+ /// Represents the PS384 (RSA PSS Signature with SHA-384) signing algorithm.
+ /// This algorithm enhances security by using SHA-384 in conjunction with RSA PSS for signing.
+ ///
+ public const string PS384 = "PS384";
+
+ ///
+ /// Represents the PS512 (RSA PSS Signature with SHA-512) signing algorithm.
+ /// This algorithm offers a higher level of security by using SHA-512 with RSA PSS for signing.
+ ///
+ public const string PS512 = "PS512";
+
+ ///
+ /// Represents the ES256 (Elliptic Curve Signature with SHA-256) signing algorithm.
+ /// This algorithm uses the ECDSA (Elliptic Curve Digital Signature Algorithm) with SHA-256 hashing,
+ /// offering a compact signature size and high security, making it suitable for JWT signing.
+ ///
+ public const string ES256 = "ES256";
+
+ ///
+ /// Represents the ES384 (Elliptic Curve Signature with SHA-384) signing algorithm.
+ /// This algorithm uses ECDSA with SHA-384, providing enhanced security for signing JWTs.
+ ///
+ public const string ES384 = "ES384";
+
+ ///
+ /// Represents the ES512 (Elliptic Curve Signature with SHA-512) signing algorithm.
+ /// This algorithm provides a very high level of security using SHA-512 in ECDSA for JWT signing.
+ ///
+ public const string ES512 = "ES512";
+
+ ///
+ /// Represents the HS256 (HMAC with SHA-256) signing algorithm.
+ /// This algorithm uses a shared secret key along with SHA-256 hashing to sign JWTs.
+ ///
+ public const string HS256 = "HS256";
+
+ ///
+ /// Represents the HS384 (HMAC with SHA-384) signing algorithm.
+ /// This algorithm enhances security by using SHA-384 for signing JWTs with a shared secret key.
+ ///
+ public const string HS384 = "HS384";
+
+ ///
+ /// Represents the HS512 (HMAC with SHA-512) signing algorithm.
+ /// This algorithm provides a higher level of security using SHA-512 with HMAC for signing JWTs.
+ ///
+ public const string HS512 = "HS512";
}
diff --git a/Abblix.Jwt/ValidJsonWebToken.cs b/Abblix.Jwt/ValidJsonWebToken.cs
index 3aa8660f..ed717095 100644
--- a/Abblix.Jwt/ValidJsonWebToken.cs
+++ b/Abblix.Jwt/ValidJsonWebToken.cs
@@ -29,7 +29,7 @@ namespace Abblix.Jwt;
public record ValidJsonWebToken(JsonWebToken Token) : JwtValidationResult
{
///
- /// Gets the successfully validated JsonWebToken.
+ /// The successfully validated JsonWebToken.
///
public JsonWebToken Token { get; } = Token;
}
diff --git a/Abblix.Oidc.Server.Mvc/Abblix.Oidc.Server.Mvc.csproj b/Abblix.Oidc.Server.Mvc/Abblix.Oidc.Server.Mvc.csproj
index ffc9480b..b5364fc1 100644
--- a/Abblix.Oidc.Server.Mvc/Abblix.Oidc.Server.Mvc.csproj
+++ b/Abblix.Oidc.Server.Mvc/Abblix.Oidc.Server.Mvc.csproj
@@ -22,8 +22,8 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
- 1.1.0.0
- 1.1.0.0
+ 1.2.0.0
+ 1.2.0.0
@@ -35,10 +35,6 @@
-
-
-
-
diff --git a/Abblix.Oidc.Server.Mvc/Controllers/AuthenticationController.cs b/Abblix.Oidc.Server.Mvc/Controllers/AuthenticationController.cs
index 221211f8..99c18290 100644
--- a/Abblix.Oidc.Server.Mvc/Controllers/AuthenticationController.cs
+++ b/Abblix.Oidc.Server.Mvc/Controllers/AuthenticationController.cs
@@ -23,6 +23,7 @@
using System.Net.Mime;
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
using Abblix.Oidc.Server.Endpoints.CheckSession.Interfaces;
using Abblix.Oidc.Server.Endpoints.EndSession;
using Abblix.Oidc.Server.Endpoints.PushedAuthorization.Interfaces;
@@ -55,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)]
@@ -84,14 +88,17 @@ public async Task> PushAuthorizeAsync(
}
///
- /// Handles requests to the authorization endpoint, performing user authentication and obtaining 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.
/// The authorization request details received from the client.
- /// A task that represents the asynchronous operation, resulting in an action result containing the authorization response.
+ /// A task that represents the asynchronous operation, resulting in an action result containing
+ /// the authorization response.
///
- /// This endpoint is a key component of the OpenID Connect flow, initiating user authentication and consent for access to their information.
+ /// This endpoint is a key component of the OpenID Connect flow, initiating user authentication and
+ /// consent for access to their information.
///
/// OpenID Connect Authorization Endpoint Documentation
///
@@ -110,13 +117,15 @@ public async Task> AuthorizeAsync(
}
///
- /// Processes requests to the userinfo endpoint, returning claims about the authenticated user based on the provided access token.
+ /// Processes requests to the userinfo endpoint, returning claims about the authenticated user based on
+ /// the provided access token.
///
/// The handler responsible for processing userinfo requests.
/// The formatter used to generate a response with user claims.
/// The userinfo request containing the access token.
/// Additional request information provided by the client.
- /// A task that represents the asynchronous operation, resulting in an action result containing the userinfo response.
+ /// A task that represents the asynchronous operation, resulting in an action result containing
+ /// the userinfo response.
///
/// This endpoint provides claims about the authenticated user, conforming to the
///
@@ -138,14 +147,17 @@ public async Task> UserInfoAsync(
}
///
- /// Facilitates the logout process by handling requests to the end session endpoint, allowing clients to terminate the user's session.
+ /// Facilitates the logout process by handling requests to the end session endpoint,
+ /// allowing clients to terminate the user's session.
///
/// The handler responsible for processing end session requests.
/// The formatter used to generate a response for the end session request.
/// The end session request details received from the client.
- /// A task that represents the asynchronous operation, resulting in an action result for the end session process.
+ /// A task that represents the asynchronous operation, resulting in an action result for
+ /// the end session process.
///
- /// This endpoint supports the RP-Initiated Logout functionality, enabling clients to initiate logout procedures compliant with OpenID Connect.
+ /// This endpoint supports the RP-Initiated Logout functionality, enabling clients to initiate
+ /// logout procedures compliant with OpenID Connect.
///
/// OpenID Connect EndSession Endpoint Documentation
///
@@ -165,13 +177,17 @@ public async Task EndSessionAsync(
}
///
- /// Monitors the user's session state by handling requests to the check session endpoint, typically used within an iframe for session management.
+ /// Monitors the user's session state by handling requests to the check session endpoint, typically used
+ /// within an iframe for session management.
///
/// The handler responsible for the check session operation.
- /// The formatter used to generate a response suitable for session checking within an iframe.
- /// A task that represents the asynchronous operation, resulting in an action result for the check session response.
+ /// The formatter used to generate a response suitable for session checking
+ /// within an iframe.
+ /// A task that represents the asynchronous operation, resulting in an action result for
+ /// the check session response.
///
- /// This endpoint is part of the OpenID Connect session management specification, enabling clients to monitor the authentication state.
+ /// This endpoint is part of the OpenID Connect session management specification,
+ /// enabling clients to monitor the authentication state.
///
/// OpenID Connect Check Session Documentation
///
@@ -187,40 +203,51 @@ public async Task CheckSessionAsync(
return await formatter.FormatResponseAsync(response);
}
- /*///
- /// 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
///
///
- [HttpPost(Path.BackchannelAuthentication)]
+ ///
+ /// 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(
- [FromServices] IBackChannelAuthenticationRequestValidator validator,
- [FromServices] IBackChannelAuthenticationRequestProcessor processor,
+ [FromServices] IBackChannelAuthenticationHandler handler,
[FromServices] IBackChannelAuthenticationResponseFormatter formatter,
- [FromForm] BackChannelAuthenticationRequest request)
+ [FromForm] BackChannelAuthenticationRequest authenticationRequest,
+ [FromForm] ClientRequest clientRequest)
{
- var backChannelAuthenticationRequest = request.Map();
- var validationResult = await validator.ValidateAsync(backChannelAuthenticationRequest);
-
- var response = validationResult switch
- {
- ValidBackChannelAuthenticationRequest validRequest => await processor.ProcessAsync(validRequest),
-
- BackChannelAuthenticationValidationError { Error: var error, ErrorDescription: var description }
- => new BackChannelAuthenticationError(error, description),
-
- _ => throw new UnexpectedTypeException(nameof(validationResult), validationResult.GetType())
- };
-
- return await formatter.FormatResponseAsync(backChannelAuthenticationRequest, response);
+ var mappedAuthenticationRequest = authenticationRequest.Map();
+ var mappedClientRequest = clientRequest.Map();
+ var response = await handler.HandleAsync(mappedAuthenticationRequest, mappedClientRequest);
+ return await formatter.FormatResponseAsync(mappedAuthenticationRequest, response);
}
+ /*
///
- /// Handles the device authorization endpoint for obtaining user authorization on limited-input devices.
+ /// Handles the device authorization endpoint for getting user authorization on limited-input devices.
///
///
///
diff --git a/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs b/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs
index cb1b5f51..1ae11fd3 100644
--- a/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs
+++ b/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs
@@ -21,8 +21,6 @@
// info@abblix.com
using System.Net.Mime;
-using System.Text.Json;
-using System.Text.Json.Serialization;
using Abblix.Jwt;
using Abblix.Oidc.Server.Common.Configuration;
using Abblix.Oidc.Server.Common.Interfaces;
@@ -119,6 +117,7 @@ public Task> ConfigurationAsync(
ResponseModesSupported = authorizationHandler.Metadata.ResponseModesSupported,
TokenEndpointAuthMethodsSupported = clientAuthenticator.ClientAuthenticationMethodsSupported,
+ TokenEndpointAuthSigningAlgValuesSupported = jwtValidator.SigningAlgorithmsSupported,
SubjectTypesSupported = subjectTypeConverter.SubjectTypesSupported,
PromptValuesSupported = authorizationHandler.Metadata.PromptValuesSupported,
@@ -127,14 +126,22 @@ public Task> ConfigurationAsync(
RequestParameterSupported = authorizationHandler.Metadata.RequestParameterSupported,
RequestObjectSigningAlgValuesSupported = authorizationHandler.Metadata.RequestParameterSupported
- ? jwtValidator.SigningAlgValuesSupported
+ ? jwtValidator.SigningAlgorithmsSupported
: null,
- IdTokenSigningAlgValuesSupported = jwtCreator.SigningAlgValuesSupported,
- UserInfoSigningAlgValuesSupported = jwtCreator.SigningAlgValuesSupported,
+ RequirePushedAuthorizationRequests = options.Value.RequirePushedAuthorizationRequests,
+ RequireSignedRequestObject = options.Value.RequireSignedRequestObject,
+
+ IdTokenSigningAlgValuesSupported = jwtCreator.SignedResponseAlgorithmsSupported,
+ UserInfoSigningAlgValuesSupported = jwtCreator.SignedResponseAlgorithmsSupported,
+
+ BackChannelAuthenticationEndpoint = Resolve(Path.BackChannelAuthentication, OidcEndpoints.BackChannelAuthentication),
+ BackChannelTokenDeliveryModesSupported = options.Value.BackChannelAuthentication.TokenDeliveryModesSupported,
+ BackChannelUserCodeParameterSupported = options.Value.BackChannelAuthentication.UserCodeParameterSupported,
+ BackChannelAuthenticationRequestSigningAlgValuesSupported = jwtValidator.SigningAlgorithmsSupported,
};
- return Task.FromResult>(Json(response));
+ return Task.FromResult>(response);
Uri? Resolve(string contentPath, OidcEndpoints enablingFlag)
=> options.Value.Discovery.AllowEndpointPathsDiscovery && options.Value.EnabledEndpoints.HasFlag(enablingFlag)
@@ -163,14 +170,6 @@ public async Task> KeysAsync(
return NotFound();
var keys = await serviceKeysProvider.GetSigningKeys().ToArrayAsync();
- return Json(new JsonWebKeySet(keys));
+ return new JsonWebKeySet(keys);
}
-
- private static JsonResult Json(object response) => new(
- response,
- new JsonSerializerOptions
- {
- WriteIndented = true,
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- });
}
diff --git a/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationErrorFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationErrorFormatter.cs
index 16d3a5a1..092beb67 100644
--- a/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationErrorFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationErrorFormatter.cs
@@ -54,7 +54,7 @@ public AuthorizationErrorFormatter(
}
private readonly IParametersProvider _parametersProvider;
- protected readonly IIssuerProvider _issuerProvider;
+ private readonly IIssuerProvider _issuerProvider;
///
/// Asynchronously formats an authorization error response into an HTTP action result,
diff --git a/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationResponseFormatter.cs
index dd499c3e..fd0d55ed 100644
--- a/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationResponseFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationResponseFormatter.cs
@@ -74,11 +74,13 @@ public AuthorizationResponseFormatter(IOptions options,
_authorizationRequestStorage = authorizationRequestStorage;
_sessionManagementService = sessionManagementService;
_httpContextAccessor = httpContextAccessor;
+ _issuerProvider = issuerProvider;
}
private readonly IAuthorizationRequestStorage _authorizationRequestStorage;
private readonly ISessionManagementService _sessionManagementService;
private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly IIssuerProvider _issuerProvider;
private readonly IOptions _options;
///
diff --git a/Abblix.Oidc.Server.Mvc/Formatters/BackChannelAuthenticationResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/BackChannelAuthenticationResponseFormatter.cs
index 34e1af2a..0dfd8282 100644
--- a/Abblix.Oidc.Server.Mvc/Formatters/BackChannelAuthenticationResponseFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/Formatters/BackChannelAuthenticationResponseFormatter.cs
@@ -20,28 +20,60 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Common.Exceptions;
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.
- /// A representing the asynchronous operation, with the formatted response as an .
+ /// 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)
{
- throw new NotImplementedException();
+ 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
+ BackChannelAuthenticationUnauthorized { Error: var error, ErrorDescription: var description }
+ => new UnauthorizedObjectResult(new ErrorResponse(error, description)),
+
+ // If access was denied, return a 403 Forbidden response
+ BackChannelAuthenticationForbidden { Error: var error, ErrorDescription: var description }
+ => new ObjectResult(new ErrorResponse(error, 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.Mvc/Formatters/UserInfoResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/UserInfoResponseFormatter.cs
index da441796..04822a74 100644
--- a/Abblix.Oidc.Server.Mvc/Formatters/UserInfoResponseFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/Formatters/UserInfoResponseFormatter.cs
@@ -71,13 +71,19 @@ public async Task> FormatResponseAsync(
{
switch (response)
{
+ case UserInfoFoundResponse {
+ ClientInfo.UserInfoSignedResponseAlgorithm: SigningAlgorithms.None,
+ User: var user,
+ }:
+
+ return new JsonResult(user);
+
case UserInfoFoundResponse
{
ClientInfo: var clientInfo,
User: var user,
Issuer: var issuer,
- }
- when clientInfo.UserInfoSignedResponseAlgorithm != SigningAlgorithms.None:
+ }:
var token = new JsonWebToken
{
@@ -96,9 +102,6 @@ public async Task> FormatResponseAsync(
Content = await _clientJwtFormatter.FormatAsync(token, clientInfo),
};
- case UserInfoFoundResponse { User: var user }:
- return new JsonResult(user);
-
case UserInfoErrorResponse error:
return new BadRequestObjectResult(error);
diff --git a/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs b/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs
index 9fa05202..b45ce0a7 100644
--- a/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs
@@ -170,7 +170,7 @@ public record AuthorizationRequest
/// supporting enhanced security for public clients in the PKCE flow.
///
[BindProperty(SupportsGet = true, Name = Parameters.CodeChallengeMethod)]
- [AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256)]
+ [AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256, CodeChallengeMethods.S512)]
public string? CodeChallengeMethod { get; init; }
///
diff --git a/Abblix.Oidc.Server.Mvc/Model/BackChannelAuthenticationRequest.cs b/Abblix.Oidc.Server.Mvc/Model/BackChannelAuthenticationRequest.cs
index 9c97aa04..c4266d44 100644
--- a/Abblix.Oidc.Server.Mvc/Model/BackChannelAuthenticationRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/BackChannelAuthenticationRequest.cs
@@ -20,30 +20,137 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Model;
+using Abblix.Oidc.Server.Mvc.Binders;
+using Microsoft.AspNetCore.Mvc;
using Core = Abblix.Oidc.Server.Model;
namespace Abblix.Oidc.Server.Mvc.Model;
+
///
-/// Represents a back-channel authentication request in the context of OAuth 2.0 or OpenID Connect.
-/// This record extends from and is used to capture and map the necessary
-/// parameters for initiating a back-channel authentication flow.
+/// Represents a client-initiated backchannel authentication request, typically used in the CIBA (Client-Initiated
+/// Backchannel Authentication) flow as part of OpenID Connect. This request allows a client to request user
+/// authentication through a backchannel communication, which involves the authorization server interacting with
+/// the user asynchronously.
///
-public record BackChannelAuthenticationRequest : ClientRequest
+public record BackChannelAuthenticationRequest
{
+ ///
+ /// A space-separated list of scopes requested by the client. Scopes define the level of access requested by
+ /// the client and the types of information that the client wants to retrieve from the user's account.
+ ///
+ [BindProperty(Name = Parameters.Scope)]
+ [ModelBinder(typeof(SpaceSeparatedValuesBinder))]
+ public string[] Scope { get; init; } = Array.Empty();
+
+ ///
+ /// A token issued by the client that the authorization server uses to notify the client about the result
+ /// of the authentication request. This token allows the authorization server to securely deliver
+ /// the authentication result to the client.
+ ///
+ [BindProperty(Name = Parameters.ClientNotificationToken)]
+ public string? ClientNotificationToken { get; init; }
+
+ ///
+ /// A list of requested Authentication Context Class References (ACRs) that the client wishes to be used
+ /// for authentication. ACR values indicate the level of authentication strength required,
+ /// such as multifactor authentication or biometric verification.
+ ///
+ [BindProperty(Name = Parameters.AcrValues)]
+ public List? AcrValues { get; init; }
+
+ ///
+ /// A token used to pass a hint about the login identifier to the authorization server.
+ /// This token is typically used to identify the user for the authentication process.
+ ///
+ [BindProperty(Name = Parameters.LoginHintToken)]
+ public string? LoginHintToken { get; init; }
+
+ ///
+ /// An ID token previously issued to the client, used as a hint to identify the user for authentication.
+ /// The ID token hint can be used by the authorization server to verify the user's identity without
+ /// requiring re-authentication.
+ ///
+ [BindProperty(Name = Parameters.IdTokenHint)]
+ public string? IdTokenHint { get; init; }
+
+ ///
+ /// A hint about the user's login identifier (such as email or username), used by the authorization server
+ /// to identify the user for authentication. This can help streamline the authentication process
+ /// by pre-filling the user's information.
+ ///
+ [BindProperty(Name = Parameters.LoginHint)]
+ public string? LoginHint { get; init; }
+
+ ///
+ /// A human-readable message intended to be shown to the user, providing context or instructions for
+ /// the authentication process.
+ /// This message is often used to help the user understand the purpose of the authentication request.
+ ///
+ [BindProperty(Name = Parameters.BindingMessage)]
+ public string? BindingMessage { get; init; }
+
+ ///
+ /// A user code provided by the user, typically as a reference for the authentication request.
+ /// This code is often used in scenarios where the user is identified by a code that they provide to the client.
+ ///
+ [BindProperty(Name = Parameters.UserCode)]
+ public string? UserCode { get; init; }
+
+ ///
+ /// An optional parameter that specifies the requested expiry time for the authentication request.
+ /// This defines how long the authentication request remains valid before it expires.
+ ///
+ [BindProperty(Name = Parameters.RequestedExpiry)]
+ [ModelBinder(typeof(SecondsToTimeSpanModelBinder))]
+ public TimeSpan? RequestedExpiry { get; init; }
+
+ ///
+ /// Specifies the resource for which the access token is requested.
+ /// As defined in RFC 8707, this parameter is used to request access tokens with a specific scope for a particular
+ /// resource.
+ ///
+ [BindProperty(SupportsGet = true, Name = Parameters.Resource)]
+ public Uri[]? Resources { get; set; }
+
///
/// Maps the properties of this back-channel authentication request to a corresponding
- /// object. This mapping facilitates
+ /// object. This mapping facilitates
/// the processing of the request in the core layers of the authentication framework.
///
///
- /// A object populated with the relevant
+ /// A object populated with the relevant
/// data from this request.
///
- public new Core.BackChannelAuthenticationRequest Map()
+ public Core.BackChannelAuthenticationRequest Map()
{
return new Core.BackChannelAuthenticationRequest
{
+ Scope = Scope,
+ ClientNotificationToken = ClientNotificationToken,
+ AcrValues = AcrValues,
+ LoginHintToken = LoginHintToken,
+ IdTokenHint = IdTokenHint,
+ LoginHint = LoginHint,
+ BindingMessage = BindingMessage,
+ UserCode = UserCode,
+ RequestedExpiry = RequestedExpiry,
+ Resources = Resources,
};
}
+
+ public static class Parameters
+ {
+ public const string Scope = "scope";
+ public const string ClientNotificationToken = "client_notification_token";
+ public const string AcrValues = "acr_values";
+ public const string LoginHintToken = "login_hint_token";
+ public const string IdTokenHint = "id_token_hint";
+ public const string LoginHint = "login_hint";
+ public const string BindingMessage = "binding_message";
+ public const string UserCode = "user_code";
+ public const string RequestedExpiry = "requested_expiry";
+ public const string Resource = "resource";
+ }
}
diff --git a/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs b/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs
index 351f3b5e..6255cc13 100644
--- a/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs
@@ -23,6 +23,7 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Abblix.Jwt;
+using Abblix.Oidc.Server.Common;
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.DeclarativeValidation;
using Abblix.Oidc.Server.Mvc.Binders;
@@ -43,13 +44,13 @@ namespace Abblix.Oidc.Server.Mvc.Model;
public record ClientRegistrationRequest
{
///
- /// Array of URIs to which the OpenID Provider will redirect the User Agent after obtaining authorization.
- /// These URIs are used in the client's authorization request and are crucial for the redirect flow in OAuth2 and OpenID Connect.
+ /// Array of URIs to which the OpenID Provider will redirect the User Agent after getting authorization.
+ /// These URIs are used in the client's authorization request and are crucial for the redirect flow in OAuth2 and
+ /// OpenID Connect.
///
[JsonPropertyName(Parameters.RedirectUris)]
- [Required]
[ElementsRequired]
- public Uri[] RedirectUris { get; set; } = default!;
+ public Uri[] RedirectUris { get; set; } = Array.Empty();
///
/// JSON array containing a list of the OAuth 2.0 response_type values.
@@ -69,7 +70,8 @@ public record ClientRegistrationRequest
[AllowedValues(
Common.Constants.GrantTypes.AuthorizationCode,
Common.Constants.GrantTypes.Implicit,
- Common.Constants.GrantTypes.RefreshToken)]
+ Common.Constants.GrantTypes.RefreshToken,
+ Common.Constants.GrantTypes.Ciba)]
public string[] GrantTypes { get; init; } = { Common.Constants.GrantTypes.AuthorizationCode };
///
@@ -165,7 +167,6 @@ public record ClientRegistrationRequest
/// Specifies the algorithm that the OpenID Provider should use to sign ID Tokens sent to this client.
///
[JsonPropertyName(Parameters.IdTokenSignedResponseAlg)]
- [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? IdTokenSignedResponseAlg { get; init; }
///
@@ -187,7 +188,6 @@ public record ClientRegistrationRequest
/// Indicates the preferred algorithm for signing UserInfo responses sent to this client.
///
[JsonPropertyName(Parameters.UserInfoSignedResponseAlg)]
- [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? UserInfoSignedResponseAlg { get; init; }
///
@@ -230,12 +230,6 @@ public record ClientRegistrationRequest
/// Specifies how the client will authenticate to the Token Endpoint.
///
[JsonPropertyName(Parameters.TokenEndpointAuthMethod)]
- [AllowedValues(
- ClientAuthenticationMethods.ClientSecretBasic,
- ClientAuthenticationMethods.ClientSecretPost,
- ClientAuthenticationMethods.ClientSecretJwt,
- ClientAuthenticationMethods.PrivateKeyJwt,
- ClientAuthenticationMethods.None)]
public string TokenEndpointAuthMethod { get; init; } = ClientAuthenticationMethods.ClientSecretBasic;
///
@@ -243,7 +237,6 @@ public record ClientRegistrationRequest
/// Endpoint. Specifies the algorithm to be used by the client for signing JWTs used in client authentication.
///
[JsonPropertyName(Parameters.TokenEndpointAuthSigningAlg)]
- [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? TokenEndpointAuthSigningAlg { get; init; }
///
@@ -334,6 +327,36 @@ public record ClientRegistrationRequest
[ElementsRequired]
public Uri[] PostLogoutRedirectUris { get; set; } = Array.Empty();
+ ///
+ /// The backchannel token delivery mode to be used by this client. This determines how tokens are delivered
+ /// during backchannel authentication.
+ ///
+ [JsonPropertyName(Parameters.BackChannelTokenDeliveryMode)]
+ [AllowedValues(
+ BackchannelTokenDeliveryModes.Ping,
+ BackchannelTokenDeliveryModes.Poll,
+ BackchannelTokenDeliveryModes.Push)]
+ public string? BackChannelTokenDeliveryMode { get; set; }
+
+ ///
+ /// The endpoint where backchannel client notifications are sent for this client.
+ ///
+ [JsonPropertyName(Parameters.BackChannelClientNotificationEndpoint)]
+ [AbsoluteUri]
+ public Uri? BackChannelClientNotificationEndpoint { get; set; }
+
+ ///
+ /// The signing algorithm used for backchannel authentication requests sent to this client.
+ ///
+ [JsonPropertyName(Parameters.BackChannelAuthenticationRequestSigningAlg)]
+ public string? BackChannelAuthenticationRequestSigningAlg { get; set; }
+
+ ///
+ /// Indicates whether the backchannel authentication process supports user codes for this client.
+ ///
+ [JsonPropertyName(Parameters.BackChannelUserCodeParameter)]
+ public bool BackChannelUserCodeParameter { get; set; } = false;
+
///
/// Maps the properties of this client registration request to a
/// object. This method is used to translate the request data into a format that can be processed by the core
@@ -372,7 +395,7 @@ public Core.ClientRegistrationRequest Map()
RequestObjectSigningAlg = RequestObjectSigningAlg,
IdTokenEncryptedResponseAlg = IdTokenEncryptedResponseAlg,
- IdTokenEncryptedResponseEnc = RequestObjectEncryptionEnc,
+ IdTokenEncryptedResponseEnc = IdTokenEncryptedResponseEnc,
IdTokenSignedResponseAlg = IdTokenSignedResponseAlg,
TokenEndpointAuthMethod = TokenEndpointAuthMethod,
@@ -389,6 +412,11 @@ public Core.ClientRegistrationRequest Map()
FrontChannelLogoutSessionRequired = FrontChannelLogoutSessionRequired,
PostLogoutRedirectUris = PostLogoutRedirectUris,
+
+ BackChannelTokenDeliveryMode = BackChannelTokenDeliveryMode,
+ BackChannelClientNotificationEndpoint = BackChannelClientNotificationEndpoint,
+ BackChannelAuthenticationRequestSigningAlg = BackChannelAuthenticationRequestSigningAlg,
+ BackChannelUserCodeParameter = BackChannelUserCodeParameter,
};
}
}
diff --git a/Abblix.Oidc.Server.Mvc/Model/IntrospectionRequest.cs b/Abblix.Oidc.Server.Mvc/Model/IntrospectionRequest.cs
index 54497c5a..201282ae 100644
--- a/Abblix.Oidc.Server.Mvc/Model/IntrospectionRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/IntrospectionRequest.cs
@@ -29,10 +29,10 @@
namespace Abblix.Oidc.Server.Mvc.Model;
///
-/// Represents a request for token introspection, extending from .
+/// Represents a request for token introspection.
/// This record is used to determine the active state and meta-information about a token.
///
-public record IntrospectionRequest : ClientRequest
+public record IntrospectionRequest
{
///
/// The token that the client wants to introspect.
@@ -55,7 +55,7 @@ public record IntrospectionRequest : ClientRequest
/// This method is used to translate the request data into a format that can be processed by the core logic of the server.
///
/// A object populated with data from this request.
- public new Core.IntrospectionRequest Map()
+ public Core.IntrospectionRequest Map()
{
return new Core.IntrospectionRequest
{
diff --git a/Abblix.Oidc.Server.Mvc/Model/RevocationRequest.cs b/Abblix.Oidc.Server.Mvc/Model/RevocationRequest.cs
index f04192be..44b90fc8 100644
--- a/Abblix.Oidc.Server.Mvc/Model/RevocationRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/RevocationRequest.cs
@@ -29,10 +29,10 @@
namespace Abblix.Oidc.Server.Mvc.Model;
///
-/// Represents a request for token revocation, extending from .
+/// Represents a request for token revocation.
/// This record is used to invalidate a token, making it no longer usable for authorization purposes.
///
-public record RevocationRequest : ClientRequest
+public record RevocationRequest
{
///
/// The token that the client wants to revoke.
@@ -55,7 +55,7 @@ public record RevocationRequest : ClientRequest
/// This method is used to translate the request data into a format that can be processed by the core logic of the server.
///
/// A object populated with data from this request.
- public new Core.RevocationRequest Map()
+ public Core.RevocationRequest Map()
{
return new Core.RevocationRequest
{
diff --git a/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs b/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs
index d80b1541..e5546675 100644
--- a/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs
@@ -83,12 +83,6 @@ public record TokenRequest
/// Scopes specify the level of access required and the associated permissions.
///
[BindProperty(SupportsGet = true, Name = Parameters.Scope)]
- [AllowedValues(
- Scopes.OpenId,
- Scopes.Profile,
- Scopes.Email,
- Scopes.Phone,
- Scopes.OfflineAccess)]
[ModelBinder(typeof(SpaceSeparatedValuesBinder))]
public string[] Scope { get; set; } = Array.Empty();
diff --git a/Abblix.Oidc.Server.Mvc/Path.cs b/Abblix.Oidc.Server.Mvc/Path.cs
index a2109dcd..020acd46 100644
--- a/Abblix.Oidc.Server.Mvc/Path.cs
+++ b/Abblix.Oidc.Server.Mvc/Path.cs
@@ -73,7 +73,7 @@ public static class Path
///
/// Path for the backchannel authentication endpoint.
///
- public const string BackchannelAuthentication = Base + "/ciba";
+ public const string BackChannelAuthentication = Base + "/bc-authorize";
///
/// Path for the device authorization endpoint.
diff --git a/Abblix.Oidc.Server.Tests/Abblix.Oidc.Server.Tests.csproj b/Abblix.Oidc.Server.Tests/Abblix.Oidc.Server.Tests.csproj
index c8b1b580..daed5fdf 100644
--- a/Abblix.Oidc.Server.Tests/Abblix.Oidc.Server.Tests.csproj
+++ b/Abblix.Oidc.Server.Tests/Abblix.Oidc.Server.Tests.csproj
@@ -5,10 +5,10 @@
-
+
-
-
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
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/Abblix.Oidc.Server.UnitTests.csproj b/Abblix.Oidc.Server.UnitTests/Abblix.Oidc.Server.UnitTests.csproj
index 37e28aad..363f8012 100644
--- a/Abblix.Oidc.Server.UnitTests/Abblix.Oidc.Server.UnitTests.csproj
+++ b/Abblix.Oidc.Server.UnitTests/Abblix.Oidc.Server.UnitTests.csproj
@@ -5,9 +5,9 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
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/Abblix.Oidc.Server.csproj b/Abblix.Oidc.Server/Abblix.Oidc.Server.csproj
index e783e19e..49395dfb 100644
--- a/Abblix.Oidc.Server/Abblix.Oidc.Server.csproj
+++ b/Abblix.Oidc.Server/Abblix.Oidc.Server.csproj
@@ -19,8 +19,8 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
- 1.1.0.0
- 1.1.0.0
+ 1.2.0.0
+ 1.2.0.0
diff --git a/Abblix.Oidc.Server/Common/AuthorizationContext.cs b/Abblix.Oidc.Server/Common/AuthorizationContext.cs
index d83519ff..a34d1c18 100644
--- a/Abblix.Oidc.Server/Common/AuthorizationContext.cs
+++ b/Abblix.Oidc.Server/Common/AuthorizationContext.cs
@@ -20,12 +20,14 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using System.Text.Json.Serialization;
+using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Model;
namespace Abblix.Oidc.Server.Common;
///
-/// Represents the context of an authorization process, encapsulating key parameters necessary for processing
+/// Represents the context of an authorization process, encapsulating the key parameters required for processing
/// authorization requests.
///
///
@@ -38,30 +40,91 @@ namespace Abblix.Oidc.Server.Common;
/// enabling fine-grained access control and personalized identity assertion in accordance with the client's needs
/// and the authorization server's policies.
///
-public record AuthorizationContext(string ClientId, string[] Scope, RequestedClaims? RequestedClaims)
+public record AuthorizationContext
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The unique identifier of the client making the authorization request.
+ ///
+ ///
+ /// An array of scope values requested by the client, representing the access permissions being sought.
+ ///
+ ///
+ /// Optional claims that the client is requesting as part of the authorization process,
+ /// providing additional information about the user's identity.
+ ///
+ [JsonConstructor]
+ public AuthorizationContext(string clientId, string[] scope, RequestedClaims? requestedClaims)
+ {
+ ClientId = clientId;
+ Scope = scope;
+ RequestedClaims = requestedClaims;
+ }
+
+ ///
+ /// Initializes a new instance of the class with a client ID,
+ /// a collection of scopes, and optional requested claims.
+ ///
+ /// The unique identifier of the client making the authorization request.
+ /// An array of scope definitions requested by the client.
+ /// An array of resource definitions associated with the authorization request.
+ /// Optional claims requested by the client for the authorization process.
+ public AuthorizationContext(
+ string clientId,
+ ScopeDefinition[] scopes,
+ ResourceDefinition[] resources,
+ RequestedClaims? requestedClaims)
+ : this(clientId, GetScopeNames(scopes, resources), requestedClaims)
+ {
+ Resources = Array.ConvertAll(resources, resource => resource.Resource);
+ }
+
+ ///
+ /// Extracts unique scope names from the provided scope and resource definitions.
+ /// This method aggregates scopes defined in both scopes and resources to ensure
+ /// there are no duplicates, returning them as an array of strings.
+ ///
+ /// An array of scope definitions to extract names from.
+ /// An array of resource definitions to extract names from.
+ ///
+ /// An array of unique scope names represented as strings, combining those from scopes
+ /// and resources.
+ ///
+ private static string[] GetScopeNames(ScopeDefinition[] scopes, ResourceDefinition[] resources)
+ {
+ return scopes
+ .Concat(resources.SelectMany(rd => rd.Scopes))
+ .Select(sd => sd.Scope)
+ .Distinct(StringComparer.Ordinal)
+ .ToArray();
+ }
+
///
/// The unique identifier for the client making the authorization request, as registered in the authorization server.
- /// This identifier is crucial for linking the authorization request and the issued tokens to a specific client application.
+ /// This identifier is crucial for linking the authorization request and the issued tokens to a specific
+ /// client application.
///
- public string ClientId { get; init; } = ClientId;
+ public string ClientId { get; init; }
///
- /// Defines the scope of access requested by the client. Scopes are used to specify the level of access or permissions
- /// that the client is requesting on the user's behalf. They play a key role in enforcing principle of least privilege.
+ /// Defines the scope of access requested by the client. Scopes are used to specify the level of access or
+ /// permissions that the client is requesting on the user's behalf.
+ /// They play a key role in enforcing the principle of least privilege.
///
- public string[] Scope { get; init; } = Scope;
+ public string[] Scope { get; init; }
///
/// Optional. Specifies the individual Claims requested by the client, providing detailed instructions
/// for the authorization server on the Claims to be returned, either in the ID Token or via the UserInfo endpoint.
- /// This mechanism supports clients in obtaining consented user information in a structured and controlled manner.
///
- public RequestedClaims? RequestedClaims { get; init; } = RequestedClaims;
+ public RequestedClaims? RequestedClaims { get; init; }
///
- /// The URI where the authorization response should be sent. This URI must match one of the registered redirect URIs
- /// for the client application, ensuring that authorization responses are delivered to the correct destination securely.
+ /// The URI where the authorization response should be sent. This URI must match one of the registered redirects URI
+ /// for the client application, ensuring that authorization responses are delivered to the correct destination
+ /// securely.
///
public Uri? RedirectUri { get; init; }
@@ -90,4 +153,11 @@ public record AuthorizationContext(string ClientId, string[] Scope, RequestedCla
/// to access.
///
public Uri[]? Resources { get; init; }
+
+ public void Deconstruct(out string ClientId, out string[] Scope, out RequestedClaims? RequestedClaims)
+ {
+ ClientId = this.ClientId;
+ Scope = this.Scope;
+ RequestedClaims = this.RequestedClaims;
+ }
}
diff --git a/Abblix.Oidc.Server/Common/Configuration/BackChannelAuthenticationOptions.cs b/Abblix.Oidc.Server/Common/Configuration/BackChannelAuthenticationOptions.cs
new file mode 100644
index 00000000..6caeb4a6
--- /dev/null
+++ b/Abblix.Oidc.Server/Common/Configuration/BackChannelAuthenticationOptions.cs
@@ -0,0 +1,69 @@
+// 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
+
+using Abblix.Oidc.Server.Common.Constants;
+
+namespace Abblix.Oidc.Server.Common.Configuration;
+
+///
+/// Provides configuration options for the backchannel authentication process.
+///
+public record BackChannelAuthenticationOptions
+{
+ ///
+ /// Specifies the default expiration time for backchannel authentication requests.
+ /// This value defines how long the authentication request will remain valid if no specific expiration
+ /// time is requested. It is set to 5 minutes by default.
+ ///
+ public TimeSpan DefaultExpiry { get; set; } = TimeSpan.FromMinutes(5);
+
+ ///
+ /// Specifies the maximum allowed expiration time for backchannel authentication requests.
+ /// This value restricts the maximum duration an authentication request can be valid for,
+ /// even if a longer expiration is requested. It is set to 30 minutes by default.
+ ///
+ public TimeSpan MaximumExpiry { get; set; } = TimeSpan.FromMinutes(30);
+
+ ///
+ /// Defines the polling interval used by clients to check the status of a backchannel authentication request.
+ /// It is set to 5 seconds by default, ensuring that clients can frequently check for authentication updates
+ /// without overwhelming the server.
+ ///
+ public TimeSpan PollingInterval { get; set; } = TimeSpan.FromSeconds(5);
+
+ ///
+ /// Indicates whether long polling is used for backchannel authentication status updates.
+ /// When set to true, clients may use long polling techniques to wait for authentication status changes,
+ /// which reduces the need for frequent polling requests.
+ ///
+ public bool UseLongPolling { get; set; } = false;
+
+ ///
+ /// Specifies the length of authentication request identifiers used by the OIDC server.
+ /// This value ensures that each backchannel authentication request is uniquely identified.
+ ///
+ public int RequestIdLength { get; set; } = 64;
+
+ public IEnumerable? TokenDeliveryModesSupported { get; set; } = new[] { BackchannelTokenDeliveryModes.Poll };
+
+ public bool UserCodeParameterSupported { get; set; } = false;
+}
diff --git a/Abblix.Oidc.Server/Common/Configuration/OidcEndpoints.cs b/Abblix.Oidc.Server/Common/Configuration/OidcEndpoints.cs
index 638b1b7d..db67bb67 100644
--- a/Abblix.Oidc.Server/Common/Configuration/OidcEndpoints.cs
+++ b/Abblix.Oidc.Server/Common/Configuration/OidcEndpoints.cs
@@ -23,66 +23,87 @@
namespace Abblix.Oidc.Server.Common.Configuration;
///
-/// Flags to represent various OIDC endpoints.
+/// Flags representing the various OpenID Connect (OIDC) endpoints that the provider can expose.
+/// These flags enable fine-grained control over which endpoints are enabled or disabled.
///
[Flags]
public enum OidcEndpoints
{
///
- /// All OIDC endpoints available.
+ /// All OIDC endpoints are available, covering the full range of OpenID Connect operations.
///
All = Configuration | Keys | Authorize | Token | UserInfo | CheckSession | EndSession | Revocation | Register |
- PushedAuthorizationRequest,
+ PushedAuthorizationRequest | BackChannelAuthentication,
///
- /// Provides OpenID Connect configuration details. Typically used during client setup.
+ /// The configuration endpoint, used by clients to dynamically discover information about the OpenID Provider.
+ /// This typically provides metadata such as available endpoints, supported grant types, and signing algorithms.
///
Configuration = 1 << 0,
///
- /// Provides public keys for token validation. Essential for token validation.
+ /// The keys endpoint, which provides public keys for validating the signatures of issued tokens.
+ /// It is essential for clients to verify the integrity and authenticity of tokens.
///
Keys = 1 << 1,
///
- /// Used to initiate the authorization process. The starting point for user authentication.
+ /// The authorization endpoint, where user authentication and consent is initiated.
+ /// This is the entry point for most OpenID Connect flows, particularly for obtaining authorization codes.
///
Authorize = 1 << 2,
///
- /// Used to exchange authorization codes for tokens. Part of the authentication flow.
+ /// The token endpoint, used to exchange authorization codes for tokens such as access tokens and ID tokens.
+ /// It also supports other grant types like client credentials and refresh tokens.
///
Token = 1 << 3,
///
- /// Retrieves user information after authentication. Often used to fetch user details.
+ /// The user info endpoint, where authenticated user claims are retrieved after a successful authentication process.
+ /// It provides information such as the user's name, email, and other identity claims.
///
UserInfo = 1 << 4,
///
- /// Used for session monitoring in single sign-on scenarios. Helps track user sessions.
+ /// The check session endpoint, typically used in single sign-on (SSO) scenarios to monitor the user's session state.
+ /// It helps in detecting if the user session is still active or if the user has logged out.
///
CheckSession = 1 << 5,
///
- /// Used to end the user's session. Enables logging out the user.
+ /// The end session endpoint, which allows clients to log the user out from the OpenID Provider.
+ /// It is used to terminate the user's session and notify relying parties of the logout event.
///
EndSession = 1 << 6,
///
- /// Used to revoke tokens, enhancing security by invalidating tokens.
+ /// The revocation endpoint, where clients can revoke access or refresh tokens.
+ /// This is a security measure to invalidate tokens that are no longer needed or in cases of token compromise.
///
Revocation = 1 << 7,
///
- /// Used to introspect tokens.
+ /// The introspection endpoint, where clients can check the status of a token (e.g., whether it is active or expired).
+ /// It provides detailed information about the token such as its expiration time and associated scopes.
///
Introspection = 1 << 7,
///
- /// Used to dynamically register clients. Allows clients to register with the OIDC provider.
+ /// The client registration endpoint, which allows dynamic registration of clients.
+ /// Clients can use this endpoint to register themselves with the OpenID Provider, typically during setup.
///
Register = 1 << 8,
+ ///
+ /// The pushed authorization request endpoint, where clients can pre-register authorization requests with the provider.
+ /// It provides an additional layer of security in certain authorization flows.
+ ///
PushedAuthorizationRequest = 1 << 9,
+
+ ///
+ /// The backchannel authentication endpoint, used in CIBA (Client-Initiated Backchannel Authentication) flows.
+ /// It allows clients to initiate out-of-band authentication requests, often via a separate user device.
+ ///
+ BackChannelAuthentication = 1 << 10,
}
diff --git a/Abblix.Oidc.Server/Common/Configuration/OidcOptions.cs b/Abblix.Oidc.Server/Common/Configuration/OidcOptions.cs
index 7922178d..8ac81472 100644
--- a/Abblix.Oidc.Server/Common/Configuration/OidcOptions.cs
+++ b/Abblix.Oidc.Server/Common/Configuration/OidcOptions.cs
@@ -1,22 +1,22 @@
// 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
@@ -176,4 +176,33 @@ public record OidcOptions
/// and permissions based on these definitions.
///
public ResourceDefinition[]? Resources { get; set; }
+
+ ///
+ /// Configuration options for the backchannel authentication flow,
+ /// used in scenarios such as Client-Initiated Backchannel Authentication (CIBA).
+ ///
+ public BackChannelAuthenticationOptions BackChannelAuthentication { get; set; } = new();
+
+ ///
+ /// Specifies the length of session identifiers used by the OIDC server.
+ /// The length determines the uniqueness and security of the session identifiers.
+ ///
+ public int SessionIdLength { get; set; } = 64;
+
+ ///
+ /// Specifies the length of token identifiers used by the OIDC server.
+ /// This value determines the length of the unique ID assigned to tokens.
+ ///
+ public int TokenIdLength { get; set; } = 64;
+
+ ///
+ /// Determines whether the OIDC server requires Pushed Authorization Requests (PAR).
+ ///
+ public bool RequirePushedAuthorizationRequests { get; set; } = false;
+
+ ///
+ /// Determines whether request objects must be signed by the client,
+ /// enhancing security for certain sensitive operations.
+ ///
+ public bool RequireSignedRequestObject { get; set; } = false;
}
diff --git a/Abblix.Oidc.Server/Common/Constants/BackchannelTokenDeliveryModes.cs b/Abblix.Oidc.Server/Common/Constants/BackchannelTokenDeliveryModes.cs
new file mode 100644
index 00000000..d5de045b
--- /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 static 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/CodeChallengeMethods.cs b/Abblix.Oidc.Server/Common/Constants/CodeChallengeMethods.cs
index 77176a0f..96ba7db2 100644
--- a/Abblix.Oidc.Server/Common/Constants/CodeChallengeMethods.cs
+++ b/Abblix.Oidc.Server/Common/Constants/CodeChallengeMethods.cs
@@ -36,4 +36,10 @@ public static class CodeChallengeMethods
/// Represents the "S256" code challenge method where the code verifier is hashed using SHA-256.
///
public const string S256 = "S256";
+
+ ///
+ /// Represents the "S512" code challenge method where the code verifier is hashed using SHA-512.
+ /// This method provides a higher level of security through a stronger hashing algorithm.
+ ///
+ public const string S512 = "S512";
}
diff --git a/Abblix.Oidc.Server/Common/Constants/ErrorCodes.cs b/Abblix.Oidc.Server/Common/Constants/ErrorCodes.cs
index 0765f144..89943234 100644
--- a/Abblix.Oidc.Server/Common/Constants/ErrorCodes.cs
+++ b/Abblix.Oidc.Server/Common/Constants/ErrorCodes.cs
@@ -79,7 +79,7 @@ public static class ErrorCodes
/// The authorization server encountered an unexpected condition that prevented it from fulfilling the request.
///
///
- /// This error code is needed because a 500 Internal Server Error HTTP status code cannot be returned to the client via an HTTP redirect.
+ /// This error code is necessary because a 500 Internal Server Error HTTP status code cannot be returned to the client via an HTTP redirect.
///
public const string ServerError = "server_error";
@@ -87,7 +87,7 @@ public static class ErrorCodes
/// The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
///
///
- /// This error code is needed because a 500 Internal Server Error HTTP status code cannot be returned to the client via an HTTP redirect.
+ /// This error code is necessary because a 500 Internal Server Error HTTP status code cannot be returned to the client via an HTTP redirect.
///
public const string TemporarilyUnavailable = "temporarily_unavailable";
@@ -187,4 +187,46 @@ public static class ErrorCodes
/// The target resource or identifier provided in the request is invalid.
///
public const string InvalidTarget = "invalid_target";
+
+ ///
+ /// The authorization request is still pending as the end-user has not yet been authenticated.
+ ///
+ public const string AuthorizationPending = "authorization_pending";
+
+ ///
+ /// A variant of "authorization_pending", the authorization request is still pending and polling should continue,
+ /// but the interval MUST be increased by at least 5 seconds for this and all further requests.
+ ///
+ public const string SlowDown = "slow_down";
+
+ ///
+ /// The auth_req_id has expired. The Client will need to make a new Authentication Request.
+ ///
+ public const string ExpiredToken = "expired_token";
+
+ ///
+ /// The login_hint_token provided in the authentication request is not valid because it has expired.
+ ///
+ public const string ExpiredLoginHintToken = "expired_login_hint_token";
+
+ ///
+ /// The OpenID Provider is not able to identify which end-user the Client wishes to be authenticated by the hint
+ /// provided in the request (login_hint_token, id_token_hint, or login_hint).
+ ///
+ public const string UnknownUserId = "unknown_user_id";
+
+ ///
+ /// User code is required but was missing from the request.
+ ///
+ public const string MissingUserCode = "missing_user_code";
+
+ ///
+ /// The user code was invalid.
+ ///
+ public const string InvalidUserCode = "invalid_user_code";
+
+ ///
+ /// The binding message is invalid or unacceptable for use in the context of the given request.
+ ///
+ public const string InvalidBindingMessage = "invalid_binding_message";
}
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/Result.cs b/Abblix.Oidc.Server/Common/Result.cs
new file mode 100644
index 00000000..c2a3a6f2
--- /dev/null
+++ b/Abblix.Oidc.Server/Common/Result.cs
@@ -0,0 +1,82 @@
+// 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
+
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Common;
+
+///
+/// Represents the result of an operation that can either be successful, returning a value of type
+/// , 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 Result
+{
+ private Result() { }
+
+ ///
+ /// Represents a successful result containing a value of specific type.
+ ///
+ /// The value returned by the successful operation.
+ public sealed record Success(T Value) : Result
+ {
+ ///
+ /// 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) : Result
+ {
+ ///
+ /// 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 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 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/Common/TimeProvider.cs b/Abblix.Oidc.Server/Common/TimeProvider.cs
deleted file mode 100644
index b3471eea..00000000
--- a/Abblix.Oidc.Server/Common/TimeProvider.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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
-
-#if !NET8_0_OR_GREATER
-
-// ReSharper disable once CheckNamespace
-namespace System;
-
-///
-/// Represents a clock that provides the current UTC time based on the system clock.
-/// This implementation is intended for use in environments or versions of .NET earlier than 8.0,
-/// where access to a more precise or configurable time source is not required. It abstracts the
-/// mechanism for obtaining time, allowing for more flexible testing or future changes to time
-/// acquisition strategies without altering dependent code.
-///
-public abstract class TimeProvider
-{
- ///
- /// Gets the current UTC time directly from the system clock.
- /// This abstract method must be implemented by subclasses to return the current UTC time,
- /// allowing different strategies for time retrieval, such as fixed time for testing or
- /// alternate time sources.
- ///
- /// The current UTC time as a .
- public abstract DateTimeOffset GetUtcNow();
-
- ///
- /// Provides a singleton instance of the that retrieves the current time
- /// using the system's default clock, specifically .
- /// This standard implementation is thread-safe and efficient, suitable for general use across various
- /// application components.
- ///
- public static readonly TimeProvider System = new SystemTimeProvider();
-
- ///
- /// A private nested class that provides the system time. This class implements the
- /// method using the system's clock.
- ///
- private class SystemTimeProvider : TimeProvider
- {
- ///
- /// Returns the current UTC time from the system's clock.
- /// This method directly accesses to provide the current time,
- /// ensuring that time retrievals are fast and reflect the actual system time with no adjustments or modifications.
- ///
- /// The current UTC time as a .
- public override DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow;
- }
-}
-
-#endif
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs
index 15414c9f..925c1ff4 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs
@@ -45,17 +45,17 @@ namespace Abblix.Oidc.Server.Endpoints.Authorization;
public class AuthorizationRequestProcessor : IAuthorizationRequestProcessor
{
///
- /// Initializes a new instance of the class.
- /// This constructor sets up the necessary services for processing authorization requests,
- /// including user authentication, consent handling, authorization code generation, access
- /// and identity token services, and time-related functionality.
+ /// Constructor that initializes the required services for handling authorization requests.
+ /// These services collectively enable the class to handle key parts of the OAuth2/OpenID Connect flow,
+ /// such as authenticating users, managing user consent, issuing tokens, and handling authorization codes.
///
- /// Service for handling user authentication.
- /// Service for managing user consent.
- /// Service for generating and managing authorization codes.
- /// Service for creating access tokens.
- /// Service for generating identity tokens.
- /// Service for managing time-related operations.
+ /// Handles user authentication sessions.
+ /// Manages the storage and retrieval of user consent data.
+ /// Generates and validates authorization codes during
+ /// the authorization process.
+ /// Issues access tokens upon successful authorization.
+ /// Generates ID tokens for user identity verification in OIDC.
+ /// Facilitates time-based logic (e.g., token expiration, session timeouts).
public AuthorizationRequestProcessor(
IAuthSessionService authSessionService,
IUserConsentsProvider consentsProvider,
@@ -80,60 +80,76 @@ public AuthorizationRequestProcessor(
private readonly TimeProvider _clock;
///
- /// Asynchronously processes a valid authorization request.
- /// This method orchestrates the flow of handling an authorization request, including user authentication,
- /// consent validation, and token generation. It determines the appropriate response based on the request's
- /// parameters and the user's session state, which may include prompting for login, consent or directly generating
- /// authorization codes and tokens.
+ /// Orchestrates the flow for handling a valid authorization request, considering the user's session state,
+ /// the need for user consent, and generating appropriate tokens. This method serves as the central logic for
+ /// determining how the system should respond based on the client's request and the user's current state.
///
- /// The valid authorization request to process.
+ /// A validated authorization request containing parameters required for processing.
///
- /// An representing the outcome of the processed authorization request.
- /// This response could be a successful authentication, an error, or a requirement for further user interaction
- /// (like login or consent).
+ /// An authorization response object, which can either represent a successful authentication, an error,
+ /// or a signal that further user interaction is required (e.g., login, consent).
///
public async Task ProcessAsync(ValidAuthorizationRequest request)
{
- request.ClientInfo.CheckClient();
+ // Ensures the client is permitted to make requests by the current license.
+ request.ClientInfo.CheckClientLicense();
+
var model = request.Model;
+ // Retrieves any available user authentication sessions, filtered by the request’s parameters.
var authSessions = await GetAvailableAuthSessionsAsync(model);
- if (authSessions.Count == 0 || model.Prompt == Prompts.Login)
+ AuthSession authSession;
+ switch (authSessions.Count, model.Prompt)
{
- if (model.Prompt == Prompts.None)
- {
+ // If no sessions exist and the prompt forbids user interaction,
+ // respond that login is required without allowing user interaction.
+ case (0, Prompts.None):
return new AuthorizationError(
model,
ErrorCodes.LoginRequired,
"The Authorization Server requires End-User authentication.",
request.ResponseMode,
model.RedirectUri);
- }
-
- return new LoginRequired(model);
- }
- if (authSessions.Count > 1 || model.Prompt == Prompts.SelectAccount)
- {
- if (model.Prompt == Prompts.None)
- {
+ // If multiple sessions exist but the prompt forbids interaction,
+ // respond that account selection is required but user interaction is not allowed.
+ case (> 1, Prompts.None):
return new AuthorizationError(
model,
ErrorCodes.AccountSelectionRequired,
"The End-User is to select a session at the Authorization Server.",
request.ResponseMode,
model.RedirectUri);
- }
- return new AccountSelectionRequired(model, authSessions.ToArray());
- }
+ // If no sessions exist, or the request explicitly asks for a login, prompt the user for login.
+ case (0, _) or (_, Prompts.Login):
+ // Otherwise, prompt the user to log in.
+ return new LoginRequired(model);
+
+ // If multiple sessions exist, or the request requires account selection, prompt the user to select an account.
+ case (> 1, _) or (_, Prompts.SelectAccount):
+ return new AccountSelectionRequired(model, authSessions.ToArray());
- var authSession = authSessions.Single();
+ // If a single session exists, proceed with that session for further processing.
+ case (1, _):
+ authSession = authSessions.Single();
+ break;
+ // Catch any unexpected cases where the session count or prompt state does not match the expected conditions.
+ default:
+ throw new InvalidOperationException(
+ $"Unexpected number of auth sessions: {authSessions.Count} or prompt: {model.Prompt}");
+ }
+
+ // Retrieve user consents (i.e., permissions granted for requested scopes/resources).
+ // The 'prompt=consent' case is not forgotten but processed inside this call.
var userConsents = await _consentsProvider.GetUserConsentsAsync(request, authSession);
+
+ // If consent for required scopes or resources is still pending, handle consent requirements.
if (userConsents.Pending is { Scopes.Length: > 0 } or { Resources.Length: > 0 })
{
+ // If user interaction is disallowed but consent is necessary, return an error.
if (model.Prompt == Prompts.None)
{
return new AuthorizationError(
@@ -144,38 +160,43 @@ public async Task ProcessAsync(ValidAuthorizationRequest
model.RedirectUri);
}
+ // Prompt for consent if necessary permissions are not yet granted.
return new ConsentRequired(model, authSession, userConsents.Pending);
}
var clientId = request.ClientInfo.ClientId;
- var grantedConsents = userConsents.Granted;
- var scopes = grantedConsents.Scopes
- .Concat(grantedConsents.Resources.SelectMany(rd => rd.Scopes))
- .Select(sd => sd.Scope)
- .Distinct()
- .ToArray();
- var resources = Array.ConvertAll(grantedConsents.Resources, resource => resource.Resource);
- var authContext = new AuthorizationContext(clientId, scopes, model.Claims)
+
+ // Build an authorization context containing necessary data like client ID, scopes, and claims.
+ // The authorization context is used to carry the granted scopes, resources and other key details through
+ // the flow.
+ var authContext = new AuthorizationContext(
+ clientId,
+ userConsents.Granted.Scopes,
+ userConsents.Granted.Resources,
+ model.Claims)
{
RedirectUri = model.RedirectUri,
Nonce = model.Nonce,
CodeChallenge = model.CodeChallenge,
CodeChallengeMethod = model.CodeChallengeMethod,
- Resources = resources,
};
+ // Mark the client as affected by this session and update the session's state.
+ // Ensures the client is tied to the current session, updating its state to include the session's client ID.
if (!authSession.AffectedClientIds.Contains(clientId))
{
authSession.AffectedClientIds.Add(clientId);
await _authSessionService.SignInAsync(authSession);
}
+ // Initialize a successful authentication result.
var result = new SuccessfullyAuthenticated(
model,
request.ResponseMode,
authSession.SessionId,
authSession.AffectedClientIds);
+ // Check if the response type requires an authorization code, and generate it if needed.
var codeRequired = request.Model.ResponseType.HasFlag(ResponseTypes.Code);
if (codeRequired)
{
@@ -184,6 +205,7 @@ public async Task ProcessAsync(ValidAuthorizationRequest
request.ClientInfo.AuthorizationCodeExpiresIn);
}
+ // Check if an access token is required, and generate it if needed.
var tokenRequired = request.Model.ResponseType.HasFlag(ResponseTypes.Token);
if (tokenRequired)
{
@@ -194,6 +216,7 @@ public async Task ProcessAsync(ValidAuthorizationRequest
request.ClientInfo);
}
+ // Check if an ID token is required, and generate it if needed.
var idTokenRequired = request.Model.ResponseType.HasFlag(ResponseTypes.IdToken);
if (idTokenRequired)
{
@@ -206,13 +229,21 @@ public async Task ProcessAsync(ValidAuthorizationRequest
result.AccessToken?.EncodedJwt);
}
+ // Return the final authorization result containing codes and tokens as needed.
return result;
}
+ ///
+ /// Retrieves the available authentication sessions based on the request's constraints (e.g., max age, ACR values).
+ /// This function ensures that only sessions meeting the request's criteria (e.g., recency, security level) are used.
+ ///
+ /// The authorization request containing parameters like max age and ACR values.
+ /// A list of valid authentication sessions that match the request's criteria.
private ValueTask> GetAvailableAuthSessionsAsync(AuthorizationRequest model)
{
var authSessions = _authSessionService.GetAvailableAuthSessions();
+ // Filter sessions based on the maximum allowable authentication age, if specified.
if (model.MaxAge.HasValue)
{
// skip all sessions older than max_age value
@@ -221,14 +252,15 @@ private ValueTask> GetAvailableAuthSessionsAsync(Authorization
.Where(session => minAuthenticationTime < session.AuthenticationTime);
}
+ // Filter sessions based on the required ACR (Authentication Context Class Reference) values, if specified.
var acrValues = model.AcrValues;
if (acrValues is { Length: > 0 })
{
- authSessions = authSessions
- .Where(session => session.AuthContextClassRef.HasValue() &&
- acrValues.Contains(session.AuthContextClassRef));
+ authSessions = authSessions.Where(
+ session => session.AuthContextClassRef.HasValue() && acrValues.Contains(session.AuthContextClassRef));
}
+ // Return the filtered list of sessions as an asynchronous task.
return authSessions.ToListAsync();
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/AuthorizationEndpointMetadata.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/AuthorizationEndpointMetadata.cs
index 7e887e35..f97f51a5 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/AuthorizationEndpointMetadata.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/AuthorizationEndpointMetadata.cs
@@ -81,6 +81,7 @@ public record AuthorizationEndpointMetadata
///
public List CodeChallengeMethodsSupported { get; init; } = new()
{
+ CodeChallengeMethods.S512,
CodeChallengeMethods.S256,
CodeChallengeMethods.Plain,
};
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/CompositeRequestFetcher.cs b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/CompositeRequestFetcher.cs
index 0823007b..9608f712 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/CompositeRequestFetcher.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/CompositeRequestFetcher.cs
@@ -25,8 +25,18 @@
namespace Abblix.Oidc.Server.Endpoints.Authorization.RequestFetching;
+///
+/// A composite fetcher that combines multiple instances.
+/// It iterates through each fetcher to process an authorization request, allowing for a flexible and
+/// extensible mechanism to fetch and validate authorization requests from different sources or formats.
+///
public class CompositeRequestFetcher : IAuthorizationRequestFetcher
{
+ ///
+ /// Initializes a new instance of the class with an array of fetchers.
+ ///
+ /// An array of instances that will be used
+ /// to fetch and validate the authorization request.
public CompositeRequestFetcher(IAuthorizationRequestFetcher[] fetchers)
{
_fetchers = fetchers;
@@ -34,6 +44,14 @@ public CompositeRequestFetcher(IAuthorizationRequestFetcher[] fetchers)
private readonly IAuthorizationRequestFetcher[] _fetchers;
+ ///
+ /// Iterates through the configured fetchers to process the authorization request. Each fetcher in the array
+ /// has the opportunity to handle the request. If a fetcher returns a fault, the process stops and
+ /// the fault is returned. If all fetchers succeed, the method returns the final successful result.
+ ///
+ /// The authorization 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(AuthorizationRequest request)
{
foreach (var fetcher in _fetchers)
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/PushedRequestFetcher.cs b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/PushedRequestFetcher.cs
index 47724d00..3934c454 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/PushedRequestFetcher.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/PushedRequestFetcher.cs
@@ -20,10 +20,12 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Common.Configuration;
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.Authorization.Validation;
using Abblix.Oidc.Server.Features.Storages;
using Abblix.Oidc.Server.Model;
+using Microsoft.Extensions.Options;
namespace Abblix.Oidc.Server.Endpoints.Authorization.RequestFetching;
@@ -35,13 +37,19 @@ public class PushedRequestFetcher : IAuthorizationRequestFetcher
///
/// Initializes a new instance of the class.
///
- /// The storage system used to retrieve pushed authorization
- /// request objects.
- public PushedRequestFetcher(IAuthorizationRequestStorage authorizationRequestStorage)
+ ///
+ /// Provides configuration options for the OIDC server, such as whether PAR is required.
+ ///
+ /// The storage system used to retrieve pushed authorization request objects.
+ public PushedRequestFetcher(
+ IOptionsSnapshot options,
+ IAuthorizationRequestStorage authorizationRequestStorage)
{
+ _options = options;
_authorizationRequestStorage = authorizationRequestStorage;
}
+ private readonly IOptionsSnapshot _options;
private readonly IAuthorizationRequestStorage _authorizationRequestStorage;
///
@@ -52,15 +60,18 @@ public PushedRequestFetcher(IAuthorizationRequestStorage authorizationRequestSto
///
///
/// A task representing the asynchronous operation. The task result contains the fetched pushed authorization
- /// request object or an error if not found.
+ /// request object or an error if not found.
+ ///
///
/// This method checks if the provided authorization request contains a URN that references a pushed authorization
/// request stored in the system. If the URN is valid and corresponds to a stored request, the method retrieves
/// and returns the request object. If the request object cannot be found or the URN is invalid,
/// an error is returned.
+ /// Additionally, it checks the server configuration to enforce the Pushed Authorization Request (PAR) requirement.
///
public async Task FetchAsync(AuthorizationRequest request)
{
+ // If the request contains a URN, attempt to retrieve the pushed authorization request from storage
if (request is { RequestUri: { } requestUrn } &&
requestUrn.OriginalString.StartsWith(RequestUrn.Prefix))
{
@@ -72,6 +83,13 @@ public async Task FetchAsync(AuthorizationRequest request)
};
}
+ // If PAR is required by server configuration, return an error if no pushed authorization request is provided
+ if (_options.Value.RequirePushedAuthorizationRequests)
+ {
+ return ErrorFactory.InvalidRequestObject("The Pushed Authorization Request (PAR) is required");
+ }
+
+ // If no URN is provided and PAR is not required, return the original request
return request;
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetchAdapter.cs b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetchAdapter.cs
new file mode 100644
index 00000000..fc44ca9e
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetchAdapter.cs
@@ -0,0 +1,70 @@
+// 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
+
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Common.Exceptions;
+using Abblix.Oidc.Server.Endpoints.Authorization.Validation;
+using Abblix.Oidc.Server.Features.RequestObject;
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Endpoints.Authorization.RequestFetching;
+
+///
+/// Adapter class that implements to delegate the
+/// fetching and processing of request objects to an instance of .
+///
+public class RequestObjectFetchAdapter : IAuthorizationRequestFetcher
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The request object fetcher responsible for fetching and processing
+ /// the JWT request object.
+ public RequestObjectFetchAdapter(IRequestObjectFetcher requestObjectFetcher)
+ {
+ _requestObjectFetcher = requestObjectFetcher;
+ }
+
+ private readonly IRequestObjectFetcher _requestObjectFetcher;
+
+ ///
+ /// Fetches and processes the authorization request by delegating to the request object fetcher.
+ ///
+ /// The authorization request to be processed.
+ ///
+ /// A task that represents the asynchronous operation. The task result contains a
+ /// which either represents a successfully processed request or an error indicating issues with the request object.
+ ///
+ public async Task FetchAsync(AuthorizationRequest request)
+ {
+ var fetchResult = await _requestObjectFetcher.FetchAsync(request, request.Request);
+ return fetchResult switch
+ {
+ Result.Success(var authorizationRequest) => authorizationRequest,
+
+ Result.Error(var error, var description)
+ => ErrorFactory.ValidationError(error, description),
+
+ _ => throw new UnexpectedTypeException(nameof(fetchResult), fetchResult.GetType()),
+ };
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetcher.cs b/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetcher.cs
deleted file mode 100644
index b81ada18..00000000
--- a/Abblix.Oidc.Server/Endpoints/Authorization/RequestFetching/RequestObjectFetcher.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-// 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
-
-using Abblix.Jwt;
-using Abblix.Oidc.Server.Common.Exceptions;
-using Abblix.Oidc.Server.Common.Interfaces;
-using Abblix.Oidc.Server.Endpoints.Authorization.Validation;
-using Abblix.Oidc.Server.Features.ClientInformation;
-using Abblix.Oidc.Server.Features.Licensing;
-using Abblix.Oidc.Server.Model;
-using Microsoft.Extensions.Logging;
-
-namespace Abblix.Oidc.Server.Endpoints.Authorization.RequestFetching;
-
-///
-/// Implements the fetching and processing of authorization request objects, including JWT validation and model binding.
-///
-public class RequestObjectFetcher : IAuthorizationRequestFetcher
-{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger for logging debug and warning messages.
- /// The validator for JSON Web Tokens (JWTs).
- /// The binder for converting JSON payloads into AuthorizationRequest objects.
- /// The provider for client JSON Web Key Sets (JWKS).
- /// The provider for client information.
- public RequestObjectFetcher(
- ILogger logger,
- IJsonWebTokenValidator jwtValidator,
- IJsonObjectBinder jsonObjectBinder,
- IClientKeysProvider clientJwksProvider,
- IClientInfoProvider clientInfoProvider)
- {
- _logger = logger;
- _jwtValidator = jwtValidator;
- _jsonObjectBinder = jsonObjectBinder;
- _clientJwksProvider = clientJwksProvider;
- _clientInfoProvider = clientInfoProvider;
- }
-
- private readonly ILogger _logger;
- private readonly IJsonWebTokenValidator _jwtValidator;
- private readonly IJsonObjectBinder _jsonObjectBinder;
- private readonly IClientKeysProvider _clientJwksProvider;
- private readonly IClientInfoProvider _clientInfoProvider;
-
-
- ///
- /// Fetches and processes the authorization request object, validating its JWT and binding its contents to a new
- /// or updated AuthorizationRequest.
- ///
- ///
- /// The initial authorization request, potentially containing a 'request' parameter with the JWT.
- ///
- /// A task that represents the asynchronous operation. The task result contains the processed authorization
- /// request or an error.
- ///
- /// This method decodes and validates the JWT included in the 'request' parameter of the authorization request.
- /// If valid, it binds the JWT payload to the authorization request model. If the JWT is invalid, it logs
- /// a warning and returns an error.
- ///
- public async Task FetchAsync(AuthorizationRequest request)
- {
- if (request is { Request: { } requestObject })
- {
- _logger.LogDebug("JWT request object was: {RequestObject}", requestObject);
-
- var result = await _jwtValidator.ValidateAsync(
- requestObject,
- new ValidationParameters
- {
- Options = ValidationOptions.ValidateIssuerSigningKey,
- ResolveIssuerSigningKeys = ResolveIssuerSigningKeys,
- });
-
- switch (result)
- {
- case ValidJsonWebToken { Token.Payload.Json: var json }:
- var updatedRequest = await _jsonObjectBinder.BindModelAsync(json, request);
- if (updatedRequest == null)
- return ErrorFactory.InvalidRequestObject($"Unable to bind request object");
-
- return updatedRequest;
-
- case JwtValidationError error:
- _logger.LogWarning("The request object contains invalid token: {@Error}", error);
- return ErrorFactory.InvalidRequestObject($"The request object is invalid.");
-
- default:
- throw new UnexpectedTypeException(nameof(result), result.GetType());
- }
- }
-
- return request;
- }
-
- private async IAsyncEnumerable ResolveIssuerSigningKeys(string clientId)
- {
- var clientInfo = await _clientInfoProvider.TryFindClientAsync(clientId).WithLicenseCheck();
- if (clientInfo == null)
- yield break;
-
- await foreach (var key in _clientJwksProvider.GetSigningKeys(clientInfo))
- yield return key;
- }
-}
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/Authorization/Validation/ClientValidator.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ClientValidator.cs
index 1df8d2d9..c8b39fa8 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ClientValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ClientValidator.cs
@@ -20,6 +20,7 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Features.Licensing;
@@ -74,7 +75,7 @@ public ClientValidator(
if (clientInfo == null)
{
_logger.LogWarning("The client with id {ClientId} was not found", new Sanitized(clientId));
- return context.InvalidRequest("The client is not authorized");
+ return context.Error(ErrorCodes.UnauthorizedClient, "The client is not authorized");
}
context.ClientInfo = clientInfo;
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ErrorFactory.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ErrorFactory.cs
index 865e1a64..3826dd3b 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ErrorFactory.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ErrorFactory.cs
@@ -96,7 +96,7 @@ public static AuthorizationRequestValidationError InvalidRequestObject(string de
/// A human-readable description of the error.
///
/// An instance with the specified error details.
- private static AuthorizationRequestValidationError ValidationError(string error, string description) => new(
+ public static AuthorizationRequestValidationError ValidationError(string error, string description) => new(
error,
description,
null,
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/FlowTypeValidator.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/FlowTypeValidator.cs
index 38e3fdc3..b9d36646 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/FlowTypeValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/FlowTypeValidator.cs
@@ -24,11 +24,8 @@
using Abblix.Oidc.Server.Common;
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
-
using Microsoft.Extensions.Logging;
-
-
namespace Abblix.Oidc.Server.Endpoints.Authorization.Validation;
///
@@ -38,8 +35,10 @@ namespace Abblix.Oidc.Server.Endpoints.Authorization.Validation;
///
public class FlowTypeValidator : SyncAuthorizationContextValidatorBase
{
+ private readonly ILogger _logger;
+
///
- /// Initializes a new instance of the class with a logger.
+ /// Initializes a new instance of the class with a logger.
/// The logger is used for recording the validation activities, aiding in troubleshooting and auditing.
///
/// The logger to be used for logging purposes.
@@ -48,34 +47,70 @@ public FlowTypeValidator(ILogger logger)
_logger = logger;
}
- private readonly ILogger _logger;
-
///
/// Validates the flow type specified in the authorization request.
/// This method checks if the flow type is supported and aligns with the OAuth 2.0 specifications.
///
/// The validation context containing client and request information.
///
- /// An if the flow type is not valid or supported,
+ /// An if the flow type is not valid or supported,
/// or null if the flow type is valid.
///
protected override AuthorizationRequestValidationError? Validate(AuthorizationValidationContext context)
{
var responseType = context.Request.ResponseType;
+
+ if (!ResponseTypeAllowed(context))
+ {
+ _logger.LogWarning("The response type {@ResponseType} is not allowed for the client",
+ new object?[] { responseType });
+ return UnsupportedResponseType("The response type is not allowed for the client");
+ }
+
if (!TryDetectFlowType(responseType, out var flowType, out var responseMode))
{
_logger.LogWarning("The response type {@ResponseType} is not valid", new object?[] { responseType });
+ return UnsupportedResponseType("The response type is not supported");
+ }
+ context.FlowType = flowType;
+ context.ResponseMode = responseMode;
+ return null;
+
+ AuthorizationRequestValidationError UnsupportedResponseType(string message)
+ {
context.ResponseMode = context.Request.ResponseMode ?? ResponseModes.Query;
return context.Error(
ErrorCodes.UnsupportedResponseType,
- "The response type is not supported");
+ message);
}
+ }
- context.FlowType = flowType;
- context.ResponseMode = responseMode;
- return null;
+ ///
+ /// Validates whether the requested response type in an authorization request matches any of the allowed response
+ /// types registered for the client. This ensures the client uses a valid and permitted OAuth/OpenID Connect flow.
+ ///
+ /// The authorization validation context containing the client and request details.
+ ///
+ /// A boolean indicating whether the requested response type is allowed for the client.
+ ///
+ private static bool ResponseTypeAllowed(AuthorizationValidationContext context)
+ {
+ var responseType = context.Request.ResponseType;
+
+ // If the response type is not specified, it means the request is invalid
+ if (responseType == null)
+ return false;
+
+ // Convert the requested response type array into a hashset for faster lookup
+ var responseTypeSet = responseType.ToHashSet(StringComparer.Ordinal);
+
+ // Check if any of the allowed response types matches the requested response type
+ return Array.Exists(
+ context.ClientInfo.AllowedResponseTypes,
+ allowedResponseType => responseTypeSet.Count == allowedResponseType.Length &&
+ Array.TrueForAll(allowedResponseType, responseTypeSet.Contains));
}
///
@@ -85,19 +120,20 @@ public FlowTypeValidator(ILogger logger)
/// The detected flow type, if successful.
/// The default response mode for the detected flow type, if successful.
/// A boolean value indicating whether the detection was successful.
- private static bool TryDetectFlowType([NotNullWhen(true)] string[]? responseType, out FlowTypes flowType, out string responseMode)
- {
- var code = responseType.HasFlag(ResponseTypes.Code);
- var token = responseType.HasFlag(ResponseTypes.Token) || responseType.HasFlag(ResponseTypes.IdToken);
-
- (var result, flowType, responseMode) = (code, token) switch
- {
- (code: true, token: false) => (true, FlowTypes.AuthorizationCode, ResponseModes.Query),
- (code: false, token: true) => (true, FlowTypes.Implicit, ResponseModes.Fragment),
- (code: true, token: true) => (true, FlowTypes.Hybrid, ResponseModes.Fragment),
- _ => (false, default, default!),
- };
-
- return result;
- }
+ private static bool TryDetectFlowType([NotNullWhen(true)] string[]? responseType, out FlowTypes flowType,
+ out string responseMode)
+ {
+ var code = responseType.HasFlag(ResponseTypes.Code);
+ var token = responseType.HasFlag(ResponseTypes.Token) || responseType.HasFlag(ResponseTypes.IdToken);
+
+ (var result, flowType, responseMode) = (code, token) switch
+ {
+ (code: true, token: false) => (true, FlowTypes.AuthorizationCode, ResponseModes.Query),
+ (code: false, token: true) => (true, FlowTypes.Implicit, ResponseModes.Fragment),
+ (code: true, token: true) => (true, FlowTypes.Hybrid, ResponseModes.Fragment),
+ _ => (false, default, default!)
+ };
+
+ return result;
+ }
}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthentication.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthentication.cs
deleted file mode 100644
index 42b1c787..00000000
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthentication.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.Endpoints.BackChannelAuthentication;
-
-public record BackChannelAuthentication;
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationHandler.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationHandler.cs
new file mode 100644
index 00000000..bc0f686c
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationHandler.cs
@@ -0,0 +1,102 @@
+// 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
+
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Common.Exceptions;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.RequestFetching;
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication;
+
+///
+/// Handles the backchannel authentication process for a CIBA (Client-Initiated Backchannel Authentication) request.
+/// This handler coordinates the fetching, validation, and processing of the authentication request.
+///
+public class BackChannelAuthenticationHandler : IBackChannelAuthenticationHandler
+{
+ ///
+ /// Initializes a new instance of the class, with dependencies for
+ /// fetching, validating, and processing backchannel authentication requests.
+ ///
+ /// The service responsible for fetching and validating the initial authentication request.
+ ///
+ /// The service responsible for validating the fetched authentication request.
+ /// The service responsible for processing the validated authentication request and
+ /// generating the response.
+ public BackChannelAuthenticationHandler(
+ IBackChannelAuthenticationRequestFetcher fetcher,
+ IBackChannelAuthenticationRequestValidator validator,
+ IBackChannelAuthenticationRequestProcessor processor)
+ {
+ _fetcher = fetcher;
+ _validator = validator;
+ _processor = processor;
+ }
+
+ private readonly IBackChannelAuthenticationRequestFetcher _fetcher;
+ private readonly IBackChannelAuthenticationRequestValidator _validator;
+ private readonly IBackChannelAuthenticationRequestProcessor _processor;
+
+ ///
+ /// Handles the entire backchannel authentication process by first fetching the request, then validating it,
+ /// and finally processing it to generate an appropriate response.
+ ///
+ /// The initial backchannel authentication request to be processed.
+ /// The client request information associated with the authentication request.
+ /// A task that represents the asynchronous operation,
+ /// resulting in a that indicates the outcome of the process.
+ ///
+ public async Task HandleAsync(
+ BackChannelAuthenticationRequest request,
+ ClientRequest clientRequest)
+ {
+ // Fetch the request if necessary
+ var fetchResult = await _fetcher.FetchAsync(request);
+ switch (fetchResult)
+ {
+ case Result.Success(var requestObject):
+ request = requestObject;
+ break;
+
+ case Result.Error(var error, var description):
+ return new BackChannelAuthenticationError(error, description);
+
+ default:
+ throw new UnexpectedTypeException(nameof(fetchResult), fetchResult.GetType());
+ }
+
+ // Validate the fetched request
+ var validationResult = await _validator.ValidateAsync(request, clientRequest);
+
+ // Process the validated request or handle validation errors
+ return validationResult switch
+ {
+ ValidBackChannelAuthenticationRequest validRequest => await _processor.ProcessAsync(validRequest),
+
+ BackChannelAuthenticationValidationError { Error: var error, ErrorDescription: var description }
+ => new BackChannelAuthenticationError(error, description),
+
+ _ => throw new UnexpectedTypeException(nameof(validationResult), validationResult.GetType()),
+ };
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationRequestProcessor.cs
index f325678b..e17d02c5 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationRequestProcessor.cs
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationRequestProcessor.cs
@@ -20,27 +20,128 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Common.Configuration;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Common.Exceptions;
using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
-
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+using Abblix.Oidc.Server.Features.BackChannelAuthentication;
+using Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Features.Licensing;
+using Abblix.Oidc.Server.Features.UserAuthentication;
+using Abblix.Oidc.Server.Model;
+using Microsoft.Extensions.Options;
+using BackChannelAuthenticationRequest = Abblix.Oidc.Server.Features.BackChannelAuthentication.BackChannelAuthenticationRequest;
namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication;
+///
+/// Handles the processing of backchannel authentication requests in an OAuth 2.0/OpenID Connect context.
+/// This class is responsible for managing the lifecycle of a backchannel authentication request,
+/// from initiating the user's authentication on their device to storing the request for status polling.
+/// It ensures that the client is authorized, user-device authentication is initiated, and the request's status
+/// is properly stored and can be queried during the authentication process.
+/// The class coordinates various services like authentication storage, options configuration and user-device
+/// interaction, ensuring a seamless backchannel authentication flow.
+///
public class BackChannelAuthenticationRequestProcessor : IBackChannelAuthenticationRequestProcessor
{
+ ///
+ /// Initializes the processor with the necessary services required for handling backchannel authentication requests.
+ /// This setup includes storage for persisting the authentication request state, configuration options
+ /// and a handler for initiating user-device authentication.
+ ///
+ /// Service for storing and retrieving backchannel authentication requests.
+ /// Configuration options related to backchannel authentication.
+ /// Handler for initiating authentication on the user's device.
+ ///
+ ///
public BackChannelAuthenticationRequestProcessor(
- IBackChannelAuthenticationStorage storage)
+ IBackChannelAuthenticationStorage storage,
+ IOptionsSnapshot options,
+ IUserDeviceAuthenticationHandler userDeviceAuthenticationHandler,
+ TimeProvider timeProvider)
{
_storage = storage;
+ _options = options;
+ _userDeviceAuthenticationHandler = userDeviceAuthenticationHandler;
+ _timeProvider = timeProvider;
}
private readonly IBackChannelAuthenticationStorage _storage;
+ private readonly IOptionsSnapshot _options;
+ private readonly IUserDeviceAuthenticationHandler _userDeviceAuthenticationHandler;
+ private readonly TimeProvider _timeProvider;
///
+ ///
+ /// Orchestrates the processing of a valid backchannel authentication request.
+ /// This method coordinates between client validation, initiating user-device authentication,
+ /// and persisting the authentication request for further polling.
+ ///
+ ///
+ /// The validated backchannel authentication request containing details such as client info, scope, and resources.
+ ///
+ /// A task that represents the result of processing the backchannel authentication request, returning
+ /// the success response with a polling interval and expiry details.
public async Task ProcessAsync(ValidBackChannelAuthenticationRequest request)
{
- var id = await _storage.StoreAsync(new BackChannelAuthentication());
+ // Validate the client's license or eligibility for making the backchannel authentication request.
+ request.ClientInfo.CheckClientLicense();
+
+ AuthorizedGrant authorizedGrant;
+
+ // Initiate the authentication flow on the user's device to retrieve the associated session
+ var authResult = await _userDeviceAuthenticationHandler.InitiateAuthenticationAsync(request);
+ switch (authResult)
+ {
+ case Result.Success(var authSession):
+ // Create the authorization context with details from the request
+ var authContext = new AuthorizationContext(
+ request.ClientInfo.ClientId,
+ request.Scope,
+ request.Resources,
+ request.Model.Claims);
+
+ authorizedGrant = new AuthorizedGrant(authSession, authContext);
+ break;
+
+ // Client authentication failed (e.g., invalid client credentials, unknown client,
+ // no client authentication included, or unsupported authentication method)
+ case Result.Error(ErrorCodes.UnauthorizedClient, var description):
+ return new BackChannelAuthenticationUnauthorized(ErrorCodes.AccessDenied, description);
+
+ // The resource owner or OpenID Provider denied the request
+ case Result.Error(ErrorCodes.AccessDenied, var description):
+ return new BackChannelAuthenticationForbidden(ErrorCodes.AccessDenied, description);
+
+ // Return a generic error response for other issues
+ case Result.Error(var error, var description):
+ return new BackChannelAuthenticationError(error, description);
+
+ // Treat any unexpected results as exceptions
+ default:
+ throw new UnexpectedTypeException(nameof(authResult), authResult.GetType());
+ }
+
+ var pollingInterval = _options.Value.BackChannelAuthentication.PollingInterval;
+
+ // Persist the backchannel authentication request with an initial pending status
+ var authenticationRequestId = await _storage.StoreAsync(
+ new BackChannelAuthenticationRequest(authorizedGrant)
+ {
+ Status = BackChannelAuthenticationStatus.Pending,
+ NextPollAt = _timeProvider.GetUtcNow() + pollingInterval,
+ },
+ request.ExpiresIn);
- return new BackChannelAuthenticationSuccess();
+ return new BackChannelAuthenticationSuccess
+ {
+ AuthenticationRequestId = authenticationRequestId,
+ ExpiresIn = request.ExpiresIn,
+ Interval = pollingInterval,
+ };
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationRequestValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationRequestValidator.cs
index 7d8af1df..5e257197 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationRequestValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationRequestValidator.cs
@@ -20,36 +20,56 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
-using Abblix.Oidc.Server.Features.ClientAuthentication;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
using Abblix.Oidc.Server.Model;
-
-
namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication;
+///
+/// Validates backchannel authentication requests by delegating the context validation to a context validator.
+/// This class is responsible for ensuring that the request meets all necessary criteria for successful authentication
+/// within the backchannel authentication flow.
+///
public class BackChannelAuthenticationRequestValidator : IBackChannelAuthenticationRequestValidator
{
- public BackChannelAuthenticationRequestValidator(
- IClientAuthenticator clientAuthenticator)
+ ///
+ /// Initializes a new instance of the class,
+ /// using the provided context validator to perform the validation logic.
+ ///
+ ///
+ /// The context validator responsible for performing detailed validation of the request.
+ public BackChannelAuthenticationRequestValidator(IBackChannelAuthenticationContextValidator contextValidator)
{
- _clientAuthenticator = clientAuthenticator;
+ _contextValidator = contextValidator;
}
- private readonly IClientAuthenticator _clientAuthenticator;
+ private readonly IBackChannelAuthenticationContextValidator _contextValidator;
- ///
+ ///
+ /// Validates the specified backchannel authentication request.
+ /// This method creates a validation context from the request and client information,
+ /// then uses the context validator to perform the validation.
+ ///
+ /// If validation succeeds, a is returned;
+ /// otherwise, the corresponding validation error is returned.
+ ///
+ /// The backchannel authentication request to be validated.
+ /// The client request associated with the backchannel authentication request.
+ ///
+ /// A task that represents the asynchronous operation.
+ /// The task result contains a ,
+ /// which can be either a valid request or an error, depending on the outcome of the validation.
+ ///
public async Task ValidateAsync(
BackChannelAuthenticationRequest request,
ClientRequest clientRequest)
{
- var clientInfo = await _clientAuthenticator.TryAuthenticateClientAsync(clientRequest);
- if (clientInfo == null)
- {
- return new BackChannelAuthenticationValidationError(ErrorCodes.InvalidClient, "The client is not authorized");
- }
+ var context = new BackChannelAuthenticationValidationContext(request, clientRequest);
+
+ var result = await _contextValidator.ValidateAsync(context) ??
+ (BackChannelAuthenticationValidationResult)new ValidBackChannelAuthenticationRequest(context);
- return new ValidBackChannelAuthenticationRequest(request, clientInfo);
+ return result;
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationSuccess.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationSuccess.cs
deleted file mode 100644
index 9f1511b0..00000000
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationSuccess.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.Endpoints.BackChannelAuthentication.Interfaces;
-
-public record BackChannelAuthenticationSuccess : BackChannelAuthenticationResponse;
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationValidationError.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationValidationError.cs
index b78cb792..69a8e83b 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationValidationError.cs
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationValidationError.cs
@@ -23,7 +23,12 @@
namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
///
-/// Indicates that a back-channel authentication request is invalid and describes what was wrong and why.
+/// Represents a validation error that occurs during the backchannel authentication request validation process.
+/// This record encapsulates the error details, including an error code and a human-readable description
+/// of the issue that caused the validation to fail.
///
+/// A code representing the specific error that occurred during validation.
+/// A human-readable description providing more details about the validation error.
+///
public record BackChannelAuthenticationValidationError(string Error, string ErrorDescription)
: BackChannelAuthenticationValidationResult;
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationValidationResult.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationValidationResult.cs
index b40f492d..32e37e57 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationValidationResult.cs
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationValidationResult.cs
@@ -22,4 +22,10 @@
namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+///
+/// Serves as the base type for results of backchannel authentication request validation.
+/// This abstract record encapsulates the outcome of the validation process, which can be
+/// further specialized into specific validation results, such as successful validation
+/// or validation failures due to various reasons.
+///
public abstract record BackChannelAuthenticationValidationResult;
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationHandler.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationHandler.cs
new file mode 100644
index 00000000..e1aec194
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationHandler.cs
@@ -0,0 +1,47 @@
+// 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
+
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+
+///
+/// Defines the contract for handling backchannel authentication requests in the context of OpenID Connect.
+/// Implementations of this interface are responsible for processing the incoming authorization requests
+/// and generating appropriate backchannel authentication responses.
+///
+public interface IBackChannelAuthenticationHandler
+{
+ ///
+ /// Handles the processing of a backchannel authentication request asynchronously.
+ /// The method takes an authorization request as input and returns a corresponding
+ /// backchannel authentication response, which could be a success or error response.
+ ///
+ ///
+ /// The authorization request containing the details of the backchannel authentication request.
+ ///
+ ///
+ /// A task that represents the asynchronous operation, containing the backchannel authentication response.
+ ///
+ Task HandleAsync(BackChannelAuthenticationRequest request,
+ ClientRequest clientRequest);
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationRequestProcessor.cs
index 611ff06b..883c733c 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationRequestProcessor.cs
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationRequestProcessor.cs
@@ -20,9 +20,27 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Model;
+
namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+///
+/// Defines the contract for processing validated backchannel authentication requests,
+/// transforming them into a response that includes necessary information for the client
+/// to complete the authentication flow.
+///
public interface IBackChannelAuthenticationRequestProcessor
{
+ ///
+ /// Asynchronously processes a validated backchannel authentication request and generates
+ /// an appropriate response. This method handles the business logic required to respond
+ /// to a backchannel authentication request, including generating tokens, managing
+ /// session state, and any other necessary operations.
+ ///
+ /// The validated backchannel authentication request containing the original request data
+ /// and associated client information.
+ /// A task that represents the asynchronous operation,
+ /// yielding a that contains the result of the processing,
+ /// such as an authentication request ID and the expires_in value.
Task ProcessAsync(ValidBackChannelAuthenticationRequest request);
}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationRequestValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationRequestValidator.cs
index 10b709ef..86cf78b7 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationRequestValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationRequestValidator.cs
@@ -27,27 +27,22 @@
namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
///
-/// The OpenID Provider MUST validate the request received as follows:
-///
-/// Authenticate the Client per the authentication method registered or configured for its client_id.
-/// It is RECOMMENDED that Clients not send shared secrets in the Authentication Request but rather that public-key cryptography be used.
-/// If the authentication request is signed, validate the JWT sent with the request parameter, which includes verifying the signature
-/// and ensuring that the JWT is valid in all other respects per [RFC7519].
-///
-/// Validate all the authentication request parameters. In the event the request contains more than one of the hints specified
-/// in Authentication Request, the OpenID Provider MUST return an "invalid_request" error response.
-///
-/// The OpenID Provider MUST process the hint provided to determine if the hint is valid and if it corresponds to a valid user.
-/// The type, issuer (where applicable) and maximum age (where applicable) of a hint that an OP accepts should be communicated
-/// to Clients.
-///
-/// If the hint is not valid or if the OP is not able to determine the user then an error should be returned to the Client
-/// as per Section Authentication Error Response.
-///
-/// The OpenID Provider MUST verify that all the REQUIRED parameters are present and their usage conforms to this specification.
+/// Defines the contract for validating client-initiated backchannel authentication requests,
+/// ensuring that the requests conform to the necessary security and protocol standards.
///
public interface IBackChannelAuthenticationRequestValidator
{
- Task ValidateAsync(BackChannelAuthenticationRequest request,
+ ///
+ /// Asynchronously validates a backchannel authentication request, checking its conformity
+ /// with the required standards and client information.
+ ///
+ /// The backchannel authentication request to validate.
+ /// The client request containing additional client-related data for validation.
+ ///
+ /// A task that represents the asynchronous operation,
+ /// containing the result of the validation process as a .
+ ///
+ Task ValidateAsync(
+ BackChannelAuthenticationRequest request,
ClientRequest clientRequest);
}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/ValidBackChannelAuthenticationRequest.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/ValidBackChannelAuthenticationRequest.cs
index 4a09f08b..8fba225a 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/ValidBackChannelAuthenticationRequest.cs
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/ValidBackChannelAuthenticationRequest.cs
@@ -20,12 +20,54 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Model;
-
-
namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
-public record ValidBackChannelAuthenticationRequest(BackChannelAuthenticationRequest Model, ClientInfo Client)
- : BackChannelAuthenticationValidationResult;
+///
+/// Represents a validated backchannel authentication request, encapsulating the original request model
+/// and the associated client information.
+///
+/// The original backchannel authentication request that passed validation.
+/// The information about the client associated with the request,
+/// including credentials and other metadata.
+/// The expiry duration for the backchannel authentication request,
+/// defining how long the request remains valid.
+/// The login hint token, if provided,
+/// which can be used to identify the user in the request.
+/// The ID token, if provided, used to validate the user's identity in the request.
+/// The set of scope definitions applicable to the request,
+/// indicating the permissions requested by the client.
+/// The set of resources requested as part of the authorization process,
+/// specifying the accessible resources for the client.
+public record ValidBackChannelAuthenticationRequest(
+ BackChannelAuthenticationRequest Model,
+ ClientInfo ClientInfo,
+ TimeSpan ExpiresIn,
+ JsonWebToken? LoginHintToken,
+ JsonWebToken? IdToken,
+ ScopeDefinition[] Scope,
+ ResourceDefinition[] Resources)
+ : BackChannelAuthenticationValidationResult
+{
+ ///
+ /// Initializes a new instance of the class using
+ /// the specified validation context.
+ ///
+ /// The validation context containing the original request and client information.
+ public ValidBackChannelAuthenticationRequest(BackChannelAuthenticationValidationContext context)
+ :this(
+ context.Request,
+ context.ClientInfo,
+ context.ExpiresIn,
+ context.LoginHintToken,
+ context.IdToken,
+ context.Scope,
+ context.Resources)
+ {
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/CompositeRequestFetcher.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/CompositeRequestFetcher.cs
new file mode 100644
index 00000000..488b2634
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/CompositeRequestFetcher.cs
@@ -0,0 +1,79 @@
+// 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
+
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Common.Exceptions;
+using Abblix.Oidc.Server.Endpoints.Authorization.RequestFetching;
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.RequestFetching;
+
+///
+/// A composite fetcher that combines multiple instances.
+/// It iterates through each fetcher to process a backchannel authentication request, allowing for a flexible and
+/// extensible mechanism to fetch and validate requests from different sources or formats.
+///
+public class CompositeRequestFetcher : IBackChannelAuthenticationRequestFetcher
+{
+ ///
+ /// Initializes a new instance of the class with an array of fetchers.
+ ///
+ /// An array of instances
+ /// that will be used to fetch and validate the backchannel authentication request.
+ public CompositeRequestFetcher(IBackChannelAuthenticationRequestFetcher[] fetchers)
+ {
+ _fetchers = fetchers;
+ }
+
+ private readonly IBackChannelAuthenticationRequestFetcher[] _fetchers;
+
+ ///
+ /// Iterates through the configured fetchers to process the backchannel authentication request.
+ /// Each fetcher in the array has the opportunity to handle the request. If a fetcher returns a fault,
+ /// the process stops and the fault is returned.
+ /// If all fetchers succeed, the method returns the final successful result.
+ ///
+ /// 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)
+ {
+ foreach (var fetcher in _fetchers)
+ {
+ var result = await fetcher.FetchAsync(request);
+ switch (result)
+ {
+ case Result.Success(var success):
+ request = success;
+ continue;
+
+ case Result.Error error:
+ return error;
+
+ default:
+ throw new UnexpectedTypeException(nameof(result), result.GetType());
+ }
+ }
+
+ return request;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/IBackChannelAuthenticationRequestFetcher.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/IBackChannelAuthenticationRequestFetcher.cs
new file mode 100644
index 00000000..79ea4e8d
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/IBackChannelAuthenticationRequestFetcher.cs
@@ -0,0 +1,43 @@
+// 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
+
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Endpoints.Authorization.RequestFetching;
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.RequestFetching;
+
+///
+/// Defines a contract for fetching and validating backchannel authentication requests in the context of
+/// CIBA (Client-Initiated Backchannel Authentication) flows.
+///
+public interface IBackChannelAuthenticationRequestFetcher
+{
+ ///
+ /// Asynchronously fetches and validates a backchannel authentication request. This method handles the retrieval
+ /// and any necessary validation or processing to ensure that the request is ready for further handling.
+ ///
+ /// 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);
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetchAdapter.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetchAdapter.cs
new file mode 100644
index 00000000..9e799a9c
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/RequestFetching/RequestObjectFetchAdapter.cs
@@ -0,0 +1,58 @@
+// 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
+
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Features.RequestObject;
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.RequestFetching;
+
+///
+/// Adapter class that implements to delegate the
+/// fetching and processing of request objects to an instance of .
+///
+public class RequestObjectFetchAdapter : IBackChannelAuthenticationRequestFetcher
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The request object fetcher responsible for fetching and processing
+ /// the JWT request object.
+ public RequestObjectFetchAdapter(IRequestObjectFetcher requestObjectFetcher)
+ {
+ _requestObjectFetcher = requestObjectFetcher;
+ }
+
+ private readonly IRequestObjectFetcher _requestObjectFetcher;
+
+ ///
+ /// Fetches and processes the backchannel authentication request by delegating to the request object fetcher.
+ ///
+ /// The backchannel authentication request to be processed.
+ ///
+ /// A task that represents the asynchronous operation.
+ /// The task result contains a
+ /// that either represents a successfully processed request or an error indicating issues with the JWT validation.
+ ///
+ public Task> FetchAsync(BackChannelAuthenticationRequest request)
+ => _requestObjectFetcher.FetchAsync(request, request.Request);
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/BackChannelAuthenticationValidationContext.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/BackChannelAuthenticationValidationContext.cs
new file mode 100644
index 00000000..b532207d
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/BackChannelAuthenticationValidationContext.cs
@@ -0,0 +1,92 @@
+// 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
+
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Features.ClientInformation;
+using Abblix.Oidc.Server.Model;
+using Abblix.Utils;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Represents the context for validating a backchannel authentication request.
+/// This context encapsulates the details of the authentication request, allowing validators to perform
+/// the necessary checks and validations according to the backchannel authentication flow.
+///
+///
+/// The backchannel authentication request that is being validated.
+/// This request contains all the parameters and data needed for the validation process.
+///
+///
+/// The client request associated with the backchannel authentication request. This contains the details of
+/// the client making the request, such as client credentials and other relevant information.
+///
+public record BackChannelAuthenticationValidationContext(
+ BackChannelAuthenticationRequest Request,
+ ClientRequest ClientRequest)
+{
+ private ClientInfo? _clientInfo;
+
+ ///
+ /// Provides information about the client associated with the backchannel authentication request.
+ /// This includes the client's identity, credentials, and any attributes relevant to the authentication process.
+ ///
+ ///
+ /// Thrown when attempting to access this property before it has been assigned a value.
+ ///
+ public ClientInfo ClientInfo { get => _clientInfo.NotNull(nameof(ClientInfo)); set => _clientInfo = value; }
+
+ ///
+ /// Represents the collection of scope definitions applicable to the authorization request.
+ /// These scopes define the permissions and access levels that the client is requesting from
+ /// the authorization server.
+ ///
+ public ScopeDefinition[] Scope { get; set; } = Array.Empty();
+
+ ///
+ /// A collection of resource definitions requested as part of the authorization process.
+ /// These resources specify the URIs that the client is requesting access to, enhancing the granularity
+ /// of resource-level authorization.
+ ///
+ public ResourceDefinition[] Resources { get; set; } = Array.Empty();
+
+ ///
+ /// Represents the login hint token, which is an optional token used to provide hints about the user's identity
+ /// to streamline the authentication process.
+ /// It may contain pre-validated information, such as a subject identifier.
+ ///
+ public JsonWebToken? LoginHintToken { get; set; }
+
+ ///
+ /// Represents the ID token associated with the request, typically used to validate the identity of the user.
+ /// This token is issued by the authorization server and can be used for user authentication or as a reference
+ /// during token validation.
+ ///
+ public JsonWebToken? IdToken { get; set; }
+
+ ///
+ /// Specifies the expiration time for the backchannel authentication request.
+ /// This value indicates how long the request is valid before it expires.
+ ///
+ public TimeSpan ExpiresIn { get; set; }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/BackChannelAuthenticationValidatorComposite.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/BackChannelAuthenticationValidatorComposite.cs
new file mode 100644
index 00000000..138922fc
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/BackChannelAuthenticationValidatorComposite.cs
@@ -0,0 +1,72 @@
+// 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
+
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Represents a composite validator for backchannel authentication contexts, aggregating multiple
+/// validation steps into a single validation process.
+/// This class implements
+/// and allows for the combination of multiple validators that are executed sequentially.
+///
+public class BackChannelAuthenticationValidatorComposite : IBackChannelAuthenticationContextValidator
+{
+ ///
+ /// Initializes a new instance of the class
+ /// with a set of validation steps.
+ ///
+ /// An array of validators that define the validation process.
+ public BackChannelAuthenticationValidatorComposite(IBackChannelAuthenticationContextValidator[] validators)
+ {
+ _validators = validators;
+ }
+
+ ///
+ /// The array of validators representing the steps in the validation process.
+ ///
+ private readonly IBackChannelAuthenticationContextValidator[] _validators;
+
+ ///
+ /// Asynchronously validates a .
+ /// Iterates through each validation step, returning the first encountered error, if any.
+ ///
+ /// The backchannel authentication validation context to be validated.
+ ///
+ /// A task that represents the asynchronous validation operation.
+ /// The task result contains a
+ /// if a validation error is found, or null if validation succeeds.
+ ///
+ public async Task ValidateAsync(
+ BackChannelAuthenticationValidationContext context)
+ {
+ foreach (var validator in _validators)
+ {
+ var error = await validator.ValidateAsync(context);
+ if (error != null)
+ return error;
+ }
+
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ClientValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ClientValidator.cs
new file mode 100644
index 00000000..28d33619
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ClientValidator.cs
@@ -0,0 +1,77 @@
+// 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
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Features.ClientAuthentication;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Validates the client in a backchannel authentication request, ensuring the client is registered
+/// and authorized to perform the request as part of the authentication validation process.
+///
+public class ClientValidator : IBackChannelAuthenticationContextValidator
+{
+ ///
+ /// Initializes a new instance of the class with the necessary
+ /// dependencies for client authentication.
+ ///
+ /// The service used to authenticate and retrieve client information.
+ public ClientValidator(IClientAuthenticator clientAuthenticator)
+ {
+ _clientAuthenticator = clientAuthenticator;
+ }
+
+ private readonly IClientAuthenticator _clientAuthenticator;
+
+ ///
+ /// Validates the client in the context of a backchannel authentication request.
+ /// Ensures that the client is recognized and authorized to make the request.
+ ///
+ ///
+ /// The validation context containing the backchannel authentication request and client information.
+ ///
+ ///
+ /// A if the client is not valid,
+ /// or null if the client is authorized.
+ ///
+ public async Task ValidateAsync(
+ BackChannelAuthenticationValidationContext context)
+ {
+ var clientInfo = await _clientAuthenticator.TryAuthenticateClientAsync(context.ClientRequest);
+ if (clientInfo == null)
+ {
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.UnauthorizedClient, "The client is not authorized");
+ }
+
+ if (!clientInfo.AllowedGrantTypes.Contains(GrantTypes.Ciba))
+ {
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.UnauthorizedClient, "The Client is not authorized to use this authentication flow");
+ }
+
+ context.ClientInfo = clientInfo;
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/IBackChannelAuthenticationContextValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/IBackChannelAuthenticationContextValidator.cs
new file mode 100644
index 00000000..896b4460
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/IBackChannelAuthenticationContextValidator.cs
@@ -0,0 +1,46 @@
+// 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
+
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Defines a contract for validating the context of a backchannel authentication request.
+/// Implementations of this interface are responsible for ensuring that the backchannel authentication request
+/// meets all necessary validation criteria based on the context, which may include client information,
+/// requested scopes, and other parameters.
+///
+public interface IBackChannelAuthenticationContextValidator
+{
+ ///
+ /// Asynchronously validates the backchannel authentication request context.
+ /// This method checks the context of the request, including client information and requested parameters,
+ /// to ensure compliance with security and protocol requirements.
+ ///
+ /// The context of the backchannel authentication request that needs to be validated.
+ ///
+ /// A task that represents the asynchronous validation operation. The task result contains
+ /// a if validation fails, or null if the context is valid.
+ ///
+ Task ValidateAsync(BackChannelAuthenticationValidationContext context);
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/RequestedExpiryValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/RequestedExpiryValidator.cs
new file mode 100644
index 00000000..064ca026
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/RequestedExpiryValidator.cs
@@ -0,0 +1,85 @@
+// 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
+
+using Abblix.Oidc.Server.Common.Configuration;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Microsoft.Extensions.Options;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Validates the requested expiry time for a backchannel authentication request.
+/// Ensures that the requested expiry is within the allowed range and assigns a valid expiry time to the context.
+///
+public class RequestedExpiryValidator: IBackChannelAuthenticationContextValidator
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The options containing the default and maximum expiry settings for backchannel authentication.
+ public RequestedExpiryValidator(IOptionsSnapshot options)
+ {
+ _options = options;
+ }
+
+ private readonly IOptionsSnapshot _options;
+
+ ///
+ /// Asynchronously validates the expiry time for the backchannel authentication request.
+ /// Ensures that the requested expiry is within the allowed range and assigns an appropriate expiry to the context.
+ ///
+ ///
+ /// The validation context containing the backchannel authentication request and its parameters.
+ /// A task representing the asynchronous operation, returning an error if validation fails,
+ /// or null if validation succeeds.
+ public Task ValidateAsync(BackChannelAuthenticationValidationContext context)
+ => Task.FromResult(Validate(context));
+
+ ///
+ /// Synchronously validates the expiry time for the backchannel authentication request.
+ ///
+ ///
+ /// The validation context containing the backchannel authentication request and its parameters.
+ ///
+ /// An error if the requested expiry exceeds the allowed maximum, or null if validation is successful.
+ private BackChannelAuthenticationValidationError? Validate(BackChannelAuthenticationValidationContext context)
+ {
+ if (!context.Request.RequestedExpiry.HasValue)
+ {
+ context.ExpiresIn = _options.Value.BackChannelAuthentication.DefaultExpiry;
+ }
+ else if (context.Request.RequestedExpiry.Value <= _options.Value.BackChannelAuthentication.MaximumExpiry)
+ {
+ context.ExpiresIn = context.Request.RequestedExpiry.Value;
+ }
+ else
+ {
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.InvalidRequest,
+ "Requested expiry is too long");
+ }
+
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ResourceValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ResourceValidator.cs
new file mode 100644
index 00000000..63dd60be
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ResourceValidator.cs
@@ -0,0 +1,88 @@
+// 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
+
+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.ResourceIndicators;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Validates resources specified in authorization requests to ensure they conform to registered definitions and policies.
+/// This validator checks whether the resources requested in the authorization process are recognized by the system
+/// and permitted for the requesting client, extending the base functionality of resource validation by incorporating
+/// integration with the authorization context.
+///
+public class ResourceValidator: IBackChannelAuthenticationContextValidator
+{
+ ///
+ /// Initializes a new instance of the class, setting up the resource manager
+ /// responsible for maintaining and validating the definitions of resources.
+ ///
+ /// The manager responsible for retrieving and validating resource information.
+ ///
+ public ResourceValidator(IResourceManager resourceManager)
+ {
+ _resourceManager = resourceManager;
+ }
+
+ private readonly IResourceManager _resourceManager;
+
+ ///
+ /// Performs the validation of resource identifiers specified in the authorization request against the allowed
+ /// resource definitions managed by the . This method ensures that the resources
+ /// requested are known to the system and align with security and access policies.
+ ///
+ /// The context containing the authorization request, which includes the resources to be
+ /// validated.
+ ///
+ /// An containing error details if validation fails,
+ /// or null if the validation is successful, indicating that all requested resources are recognized and permissible.
+ ///
+ public Task ValidateAsync(BackChannelAuthenticationValidationContext context)
+ => Task.FromResult(Validate(context));
+
+ private BackChannelAuthenticationValidationError? Validate(BackChannelAuthenticationValidationContext context)
+ {
+ var request = context.Request;
+
+ // Proceed with validation only if there are resources specified in the request.
+ if (request.Resources is { Length: > 0 })
+ {
+ // Validate the requested resources using the resource manager.
+ if (!_resourceManager.Validate(
+ request.Resources,
+ request.Scope,
+ out var resources,
+ out var errorDescription))
+ {
+ return new BackChannelAuthenticationValidationError(ErrorCodes.InvalidTarget, errorDescription);
+ }
+
+ context.Resources = resources;
+ }
+
+ // Return null indicating successful validation if there are no errors.
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ScopeValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ScopeValidator.cs
new file mode 100644
index 00000000..f82e26dc
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/ScopeValidator.cs
@@ -0,0 +1,95 @@
+// 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
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Features.ScopeManagement;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Validates the scopes in OAuth 2.0 authorization requests for backchannel authentication.
+/// This validator ensures that the requested scopes are allowed based on the client’s configuration
+/// and the type of OAuth flow being used. It checks for scope compatibility and prevents unauthorized
+/// or excessive scope requests, reinforcing the security policies and minimizing scope-related vulnerabilities.
+///
+public class ScopeValidator : IBackChannelAuthenticationContextValidator
+{
+ public ScopeValidator(IScopeManager scopeManager)
+ {
+ _scopeManager = scopeManager;
+ }
+
+ private readonly IScopeManager _scopeManager;
+
+ ///
+ /// Validates the scopes in the context of the backchannel authentication request, checking if
+ /// they align with the client's permissions and the OAuth flow. This method prevents the client
+ /// from requesting unauthorized scopes, such as offline access,
+ /// unless explicitly allowed by the client's configuration.
+ ///
+ /// The validation context that includes details about the request and the client.
+ ///
+ /// A if the scope validation fails,
+ /// or null if the scopes in the request are valid.
+ ///
+ public Task ValidateAsync(
+ BackChannelAuthenticationValidationContext context)
+ {
+ return Task.FromResult(Validate(context));
+ }
+
+ ///
+ /// Performs the actual scope validation, ensuring the requested scopes are permitted for the client.
+ /// It checks for issues like unauthorized offline access requests and verifies the compatibility of
+ /// the requested scopes with the client’s registered permissions and the resources requested.
+ ///
+ ///
+ /// Contains the authorization request and the client information necessary for validation.
+ ///
+ /// A if the requested scopes are not valid or not allowed,
+ /// or null if the validation passes.
+ ///
+ private BackChannelAuthenticationValidationError? Validate(BackChannelAuthenticationValidationContext context)
+ {
+ if (context.Request.Scope.Contains(Scopes.OfflineAccess) &&
+ context.ClientInfo.OfflineAccessAllowed != true)
+ {
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.InvalidScope,
+ "This client is not allowed to request for offline access");
+ }
+
+ if (!_scopeManager.Validate(
+ context.Request.Scope,
+ context.Resources,
+ out var scopeDefinitions,
+ out var errorDescription))
+ {
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.InvalidScope, errorDescription);
+ }
+
+ context.Scope = scopeDefinitions;
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserCodeValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserCodeValidator.cs
new file mode 100644
index 00000000..28e189a0
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserCodeValidator.cs
@@ -0,0 +1,88 @@
+// 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
+
+using Abblix.Oidc.Server.Common.Configuration;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Microsoft.Extensions.Options;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Validates the presence of a UserCode in backchannel authentication requests, based on the client
+/// and provider configuration. This validator ensures that if the client or provider requires the
+/// UserCode parameter for backchannel authentication, it is included in the request.
+///
+public class UserCodeValidator : IBackChannelAuthenticationContextValidator
+{
+ ///
+ /// Initializes a new instance of the class.
+ /// The constructor accepts OIDC options, allowing the validator to access the configuration
+ /// settings that determine if the UserCode parameter is required.
+ ///
+ ///
+ /// The OIDC options used to configure the behavior of the backchannel authentication process.
+ public UserCodeValidator(IOptions options)
+ {
+ _options = options;
+ }
+
+ private readonly IOptions _options;
+
+ ///
+ /// Asynchronously validates the UserCode parameter in the context of a backchannel authentication request.
+ /// If the UserCode is required but not present, the method returns an error. Otherwise, it returns null.
+ ///
+ ///
+ /// The validation context containing the authentication request and client information.
+ ///
+ /// A task that represents the asynchronous operation, returning an error if validation fails,
+ /// or null if successful.
+ public Task ValidateAsync(BackChannelAuthenticationValidationContext context)
+ => Task.FromResult(Validate(context));
+
+ ///
+ /// Performs the actual validation of the UserCode parameter. Checks whether the provider and client require
+ /// the UserCode parameter for the current request and ensures that it is present in the request.
+ ///
+ /// The validation context containing the backchannel authentication request details.
+ ///
+ /// A if the UserCode is missing when required,
+ /// or null otherwise.
+ private BackChannelAuthenticationValidationError? Validate(BackChannelAuthenticationValidationContext context)
+ {
+ // Check if the provider and client both require the UserCode parameter.
+ var requireUserCode = _options.Value.BackChannelAuthentication.UserCodeParameterSupported &&
+ context.ClientInfo.BackChannelUserCodeParameter;
+
+ // Return an error if UserCode is required but missing from the request.
+ if (requireUserCode && string.IsNullOrEmpty(context.Request.UserCode))
+ {
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.MissingUserCode,
+ "The UserCode parameter is missing.");
+ }
+
+ // If no errors, return null (indicating a successful validation).
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserIdentityValidator.cs b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserIdentityValidator.cs
new file mode 100644
index 00000000..5e55e6a4
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Validation/UserIdentityValidator.cs
@@ -0,0 +1,190 @@
+// 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
+
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Common.Exceptions;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Features.Tokens.Validation;
+using Abblix.Oidc.Server.Model;
+using Abblix.Utils;
+
+namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
+
+///
+/// Validates the user's identity in a backchannel authentication request, ensuring that valid identity hints
+/// (e.g., login hints, tokens) are provided and correctly processed.
+///
+public class UserIdentityValidator: IBackChannelAuthenticationContextValidator
+{
+ public UserIdentityValidator(
+ IAuthServiceJwtValidator idTokenValidator,
+ IClientJwtValidator clientJwtValidator)
+ {
+ _idTokenValidator = idTokenValidator;
+ _clientJwtValidator = clientJwtValidator;
+ }
+
+ private readonly IAuthServiceJwtValidator _idTokenValidator;
+ private readonly IClientJwtValidator _clientJwtValidator;
+
+ ///
+ /// Validates the user's identity based on the provided identity hints, such as login hint, login hint token,
+ /// or ID token hint. It ensures that only one identity hint is present and attempts to process the hint
+ /// to confirm the user's identity.
+ ///
+ /// Contains the backchannel authentication request and client information.
+ ///
+ /// Returns a if the identity validation fails,
+ /// or null if the identity is successfully validated.
+ ///
+ public async Task ValidateAsync(
+ BackChannelAuthenticationValidationContext context)
+ {
+ var request = context.Request;
+
+ // Count the number of identity hints (LoginHint, LoginHintToken, IdTokenHint) provided in the request
+ var userIdentityCount = new[]
+ {
+ request.LoginHint, // Regular login hint
+ request.LoginHintToken, // JWT-based login hint token
+ request.IdTokenHint // ID token hint provided by the client
+ }
+ .Count(id => id.HasValue());
+
+ switch (userIdentityCount)
+ {
+ case 1:
+ break; // Valid scenario: exactly one hint is provided
+
+ case 0:
+ // No identity hint is present; return an error indicating the user's identity is unknown
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.InvalidRequest, "The user's identity is unknown.");
+
+ default:
+ // Multiple identity hints provided; return an error indicating ambiguity
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.InvalidRequest,
+ "User identity is not determined due to conflicting hints.");
+ }
+
+ // Validate the LoginHintToken if it is provided and the client is configured to parse it as a JWT
+ if (request.LoginHintToken.HasValue() && context.ClientInfo.ParseLoginHintTokenAsJwt)
+ {
+ var (loginHintTokenResult, clientInfo) = await _clientJwtValidator.ValidateAsync(request.LoginHintToken);
+ switch (loginHintTokenResult, clientInfo)
+ {
+ // The token was issued for another client
+ case (ValidJsonWebToken, { ClientId: var clientId})
+ when clientId != context.ClientInfo.ClientId:
+
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.InvalidRequest,
+ "LoginHintToken issued by another client.");
+
+ // If the token is valid and issued for the correct client, store it in the validation context
+ case (ValidJsonWebToken { Token: var loginHintToken }, _):
+ context.LoginHintToken = loginHintToken;
+ break;
+
+ case (JwtValidationError { Error: JwtError.InvalidToken }, _):
+ break;
+
+ // If JWT validation fails, return an error
+ case (JwtValidationError, _):
+ return new BackChannelAuthenticationValidationError(
+ ErrorCodes.InvalidRequest,
+ "LoginHintToken validation failed.");
+
+ // Unexpected cases should result in an exception
+ default:
+ throw new InvalidOperationException("Something went wrong.");
+ }
+ }
+
+ // Validate the IdTokenHint if present
+ if (request.IdTokenHint.HasValue())
+ {
+ var idTokenResult = await ValidateIdTokenHint(context, request.IdTokenHint);
+ switch (idTokenResult)
+ {
+ // If successful, store the validated token in the context
+ case Result.Success(var idToken):
+ context.IdToken = idToken;
+ break;
+
+ // If validation fails, return the error with the appropriate message
+ case Result.Error(var error, var description):
+ return new BackChannelAuthenticationValidationError(error, description);
+ }
+ }
+
+ return null; // Identity validation successful
+ }
+
+ ///
+ /// Validates the ID token hint to ensure it is properly issued and valid.
+ ///
+ /// The validation context containing the client information.
+ /// The ID token hint string to be validated.
+ ///
+ /// An representing the validation result,
+ /// which can either be a successful token or an error.
+ ///
+ private async Task> ValidateIdTokenHint(
+ BackChannelAuthenticationValidationContext context,
+ string idTokenHint)
+ {
+ // Validate the ID token hint, ensuring that it is a well-formed token except validation of its lifetime.
+ var result = await _idTokenValidator.ValidateAsync(
+ idTokenHint,
+ ValidationOptions.Default & ~ValidationOptions.ValidateLifetime);
+
+ // Analyze the validation result, checking if the token was issued for the correct client
+ switch (result)
+ {
+ // If the token's audience doesn't match the client specified in the validation context, return an error.
+ case ValidJsonWebToken { Token.Payload.Audiences: var audiences }
+ when !audiences.Contains(context.ClientInfo.ClientId, StringComparer.Ordinal):
+
+ return new ErrorResponse(
+ ErrorCodes.InvalidRequest,
+ "The id token hint contains token issued for the client other than specified");
+
+ // If the token validation resulted in an error, return an invalid request error response.
+ case JwtValidationError:
+ return new ErrorResponse(
+ ErrorCodes.InvalidRequest,
+ "The id token hint contains invalid token");
+
+ // If the token is valid, return it as the successful result.
+ case ValidJsonWebToken { Token: var idToken }:
+ return idToken;
+
+ // If none of the above cases match, an unexpected result occurred, so throw an exception.
+ default:
+ throw new UnexpectedTypeException(nameof(result), result.GetType());
+ }
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/RegisterClientRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/RegisterClientRequestProcessor.cs
index 6228834f..76602392 100644
--- a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/RegisterClientRequestProcessor.cs
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/RegisterClientRequestProcessor.cs
@@ -130,7 +130,8 @@ public async Task ProcessAsync(ValidClientRegistrati
return (clientSecret, expiresAt);
default:
- // It is not needed for Clients selecting a token_endpoint_auth_method of private_key_jwt unless symmetric encryption will be used.
+ // It is unnecessary for Clients selecting a token_endpoint_auth_method of private_key_jwt
+ // unless symmetric encryption will be used
return (clientSecret: null, expiresAt: null);
}
}
@@ -170,6 +171,10 @@ private ClientInfo ToClientInfo(
SubjectType = model.SubjectType,
SectorIdentifier = sectorIdentifier,
PostLogoutRedirectUris = model.PostLogoutRedirectUris,
+ BackChannelTokenDeliveryMode = model.BackChannelTokenDeliveryMode,
+ BackChannelClientNotificationEndpoint = model.BackChannelClientNotificationEndpoint,
+ BackChannelAuthenticationRequestSigningAlg = model.BackChannelAuthenticationRequestSigningAlg,
+ BackChannelUserCodeParameter = model.BackChannelUserCodeParameter,
};
if (model.UserInfoSignedResponseAlg.HasValue())
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/BackChannelAuthenticationValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/BackChannelAuthenticationValidator.cs
new file mode 100644
index 00000000..532a8050
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/BackChannelAuthenticationValidator.cs
@@ -0,0 +1,115 @@
+// 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
+
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Interfaces;
+using Abblix.Utils;
+
+namespace Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation;
+
+///
+/// Validates backchannel authentication configurations during client registration.
+/// This class ensures that the backchannel token delivery mode and notification endpoint
+/// meet the requirements of the CIBA (Client-Initiated Backchannel Authentication) protocol.
+/// Additionally, it checks for the presence of supported signing algorithms.
+///
+public class BackChannelAuthenticationValidator: IClientRegistrationContextValidator
+{
+ ///
+ /// Initializes the validator with the necessary components, including a JWT validator
+ /// to verify supported signing algorithms.
+ ///
+ /// The service responsible for validating JWT signing algorithms.
+ public BackChannelAuthenticationValidator(IJsonWebTokenValidator jwtValidator)
+ {
+ _jwtValidator = jwtValidator;
+ }
+
+ private readonly IJsonWebTokenValidator _jwtValidator;
+
+ ///
+ /// Asynchronously validates the client registration context, returning any errors found during validation.
+ ///
+ /// The context containing the client registration request.
+ /// A task that represents the result of the validation, either an error or null if valid.
+ public Task ValidateAsync(ClientRegistrationValidationContext context)
+ => Task.FromResult(Validate(context));
+
+ ///
+ /// Validates the backchannel token delivery mode, notification endpoints, and signing algorithms specified
+ /// in the client registration request.
+ ///
+ /// The context containing the client registration request.
+ /// A validation error if the request is invalid, or null if the request is valid.
+ private ClientRegistrationValidationError? Validate(ClientRegistrationValidationContext context)
+ {
+ switch (context.Request)
+ {
+ // If the backchannel token delivery mode is not set, assume CIBA is not enabled for the client
+ case { BackChannelTokenDeliveryMode: null }:
+ return null;
+
+ // If delivery mode is set to "poll" but a notification endpoint is provided, return an error
+ case {
+ BackChannelTokenDeliveryMode: BackchannelTokenDeliveryModes.Poll,
+ BackChannelClientNotificationEndpoint: not null,
+ }:
+ return new ClientRegistrationValidationError(
+ ErrorCodes.InvalidRequest,
+ "Notification endpoint is invalid if the token delivery mode is set to poll");
+
+ // If delivery mode is set to "ping" or "push" but no notification endpoint is provided, return an error
+ case {
+ BackChannelTokenDeliveryMode: BackchannelTokenDeliveryModes.Ping or BackchannelTokenDeliveryModes.Push,
+ BackChannelClientNotificationEndpoint: null,
+ }:
+ return new ClientRegistrationValidationError(
+ ErrorCodes.InvalidRequest,
+ "Notification endpoint is required if the token delivery mode is set to ping or push");
+
+ // Valid configurations for poll, ping, and push modes
+ case { BackChannelTokenDeliveryMode: BackchannelTokenDeliveryModes.Poll }:
+ //case { BackChannelTokenDeliveryMode: BackchannelTokenDeliveryModes.Ping or BackchannelTokenDeliveryModes.Push }:
+ break;
+
+ // If the token delivery mode is not supported, return an error
+ default:
+ return new ClientRegistrationValidationError(
+ ErrorCodes.InvalidRequest,
+ "The specified token delivery mode is not supported");
+ }
+
+ // Check if the signing algorithm specified in the request is supported
+ var signingAlgorithm = context.Request.BackChannelAuthenticationRequestSigningAlg;
+ if (signingAlgorithm.HasValue() &&
+ !_jwtValidator.SigningAlgorithmsSupported.Contains(signingAlgorithm, StringComparer.Ordinal))
+ {
+ return new ClientRegistrationValidationError(
+ ErrorCodes.InvalidRequest,
+ "The specified signing algorithm is not supported");
+ }
+
+ // If all validations pass, return null indicating the request is valid
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/RedirectUrisValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/RedirectUrisValidator.cs
index 982f76b6..51a2a2a1 100644
--- a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/RedirectUrisValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/RedirectUrisValidator.cs
@@ -29,70 +29,85 @@
namespace Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation;
///
-/// This internal class validates Redirect URIs in a client registration request. It checks if the URIs are absolute,
-/// do not contain fragments, and comply with security requirements based on the application type.
+/// Validates Redirect URIs in a client registration request.
+/// This internal class checks if the URIs are absolute, do not contain fragments,
+/// and comply with security requirements based on the application type.
/// If any validation fails, it returns a ClientRegistrationValidationError.
///
internal class RedirectUrisValidator : SyncClientRegistrationContextValidator
{
+ private static readonly string[] RequiringRedirectUri = {
+ GrantTypes.AuthorizationCode,
+ GrantTypes.Implicit,
+ GrantTypes.RefreshToken,
+ };
+
///
/// Validates Redirect URIs in the client registration request.
+ /// This method ensures that the registered Redirect URIs meet the necessary criteria
+ /// required by the OAuth 2.0 and OpenID Connect specifications.
///
/// The validation context containing client registration data.
///
- /// A ClientRegistrationValidationError if any validation fails, or null if the request is valid.
+ /// A if any validation fails,
+ /// or null if the request is valid.
///
protected override ClientRegistrationValidationError? Validate(ClientRegistrationValidationContext context)
{
var request = context.Request;
- if (request.RedirectUris.Length == 0)
- return ErrorFactory.InvalidRedirectUri($"{Parameters.RedirectUris} is required");
-
- foreach (var uri in request.RedirectUris)
+ if (request.GrantTypes.Intersect(RequiringRedirectUri, StringComparer.Ordinal).Any())
{
- if (uri is not { IsAbsoluteUri : true })
- return ErrorFactory.InvalidRedirectUri($"{Parameters.RedirectUris} must contain only absolute URIs");
-
- if (uri.Fragment.HasValue())
- return ErrorFactory.InvalidRedirectUri($"{Parameters.RedirectUris} must not contain fragment");
+ if (request.RedirectUris.Length == 0)
+ return ErrorFactory.InvalidRedirectUri($"{Parameters.RedirectUris} is required");
- var applicationType = context.Request.ApplicationType;
- switch (applicationType)
+ foreach (var uri in request.RedirectUris)
{
- case ApplicationTypes.Web when uri.Scheme != Uri.UriSchemeHttps:
- // Web Clients using the OAuth Implicit Grant Type MUST only register URLs using the https scheme as redirect_uris
+ if (uri is not { IsAbsoluteUri: true })
return ErrorFactory.InvalidRedirectUri(
- $"{Parameters.RedirectUris} must be secure ({Uri.UriSchemeHttps})");
+ $"{Parameters.RedirectUris} must contain only absolute URIs");
- case ApplicationTypes.Web when IsLocalhost(uri):
- // they MUST NOT use localhost as the hostname
- return ErrorFactory.InvalidRedirectUri(
- $"{Parameters.RedirectUris} must not use host name {uri.Host}");
+ if (uri.Fragment.HasValue())
+ return ErrorFactory.InvalidRedirectUri($"{Parameters.RedirectUris} must not contain fragment");
- case ApplicationTypes.Web:
- break;
+ var applicationType = context.Request.ApplicationType;
+ switch (applicationType)
+ {
+ // Web Clients using the OAuth Implicit Grant Type MUST only register URLs using the https scheme as redirect_uris
+ case ApplicationTypes.Web when uri.Scheme != Uri.UriSchemeHttps:
+ return ErrorFactory.InvalidRedirectUri(
+ $"{Parameters.RedirectUris} must be secure ({Uri.UriSchemeHttps})");
+
+ // Web Clients MUST NOT use localhost as the hostname
+ case ApplicationTypes.Web when IsLocalhost(uri):
+ return ErrorFactory.InvalidRedirectUri(
+ $"{Parameters.RedirectUris} must not use host name {uri.Host}");
- case ApplicationTypes.Native when uri.Scheme == Uri.UriSchemeHttp && !IsLocalhost(uri):
// Native Clients MUST only register redirect_uris using the http: scheme with localhost as the hostname
- return ErrorFactory.InvalidRedirectUri(
- $"Native Clients MUST only register {Parameters.RedirectUris} using the http: scheme with localhost as the hostname");
+ case ApplicationTypes.Native when uri.Scheme == Uri.UriSchemeHttp && !IsLocalhost(uri):
+ return ErrorFactory.InvalidRedirectUri(
+ $"Native Clients MUST only register {Parameters.RedirectUris} using the http: scheme with localhost as the hostname");
- case ApplicationTypes.Native when uri.Scheme == Uri.UriSchemeHttps:
// Native Clients MUST only register redirect_uris using custom URI schemes
- return ErrorFactory.InvalidRedirectUri(
- $"Native Clients MUST only register {Parameters.RedirectUris} using custom URI schemes");
+ case ApplicationTypes.Native when uri.Scheme == Uri.UriSchemeHttps:
+ return ErrorFactory.InvalidRedirectUri(
+ $"Native Clients MUST only register {Parameters.RedirectUris} using custom URI schemes");
- case ApplicationTypes.Native:
- break;
+ // Any other case of Web or Native application types is correct
+ case ApplicationTypes.Web:
+ case ApplicationTypes.Native:
+ break;
- default:
- throw new UnexpectedTypeException(nameof(applicationType), applicationType.GetType());
+ default:
+ throw new UnexpectedTypeException(nameof(applicationType), applicationType.GetType());
+ }
}
}
- return null;
+ return null; // All validations passed successfully
}
- private static bool IsLocalhost(Uri uri) => uri.IsLoopback || uri.Host == "localhost";
+ // Helper method to determine if the provided URI uses localhost or loopback address
+ private static bool IsLocalhost(Uri uri)
+ => uri.IsLoopback || string.Equals(uri.Host, "localhost", StringComparison.Ordinal);
}
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SignedResponseAlgorithmsValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SignedResponseAlgorithmsValidator.cs
new file mode 100644
index 00000000..76ca1da1
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SignedResponseAlgorithmsValidator.cs
@@ -0,0 +1,86 @@
+// 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
+
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Interfaces;
+using static Abblix.Oidc.Server.Model.ClientRegistrationRequest;
+
+namespace Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation;
+
+///
+/// Validates the signing algorithms specified for ID tokens and user info responses in a client registration request.
+/// This class checks if the requested signing algorithms are supported by the JWT creator, ensuring compliance
+/// with security standards.
+///
+public class SignedResponseAlgorithmsValidator: SyncClientRegistrationContextValidator
+{
+ ///
+ /// Initializes a new instance of the class.
+ /// The constructor takes a JWT creator to access the supported signing algorithms for validation.
+ ///
+ /// The service responsible for creating signed JWTs.
+ public SignedResponseAlgorithmsValidator(IJsonWebTokenCreator jwtCreator)
+ {
+ _jwtCreator = jwtCreator;
+ }
+
+ private readonly IJsonWebTokenCreator _jwtCreator;
+
+ ///
+ /// Validates the signing algorithms specified for ID tokens and user info responses.
+ /// This method ensures that the JWT creator supports the requested algorithms.
+ ///
+ /// The validation context containing the client registration data.
+ ///
+ /// A if any signing algorithm is not supported;
+ /// otherwise, null if all validations are successful.
+ ///
+ protected override ClientRegistrationValidationError? Validate(ClientRegistrationValidationContext context)
+ {
+ var request = context.Request;
+ return Validate( request.IdTokenSignedResponseAlg, Parameters.IdTokenSignedResponseAlg) ??
+ Validate(request.UserInfoSignedResponseAlg, Parameters.UserInfoSignedResponseAlg);
+ }
+
+ ///
+ /// Validates that the JWT creator supports the specified signing algorithm.
+ /// If the algorithm is not supported, it returns a validation error.
+ ///
+ /// The signing algorithm to validate.
+ ///
+ /// A description used in the error message to identify which signing algorithm is invalid.
+ ///
+ /// A if the algorithm is not supported; otherwise, null.
+ ///
+ private ClientRegistrationValidationError? Validate(string? alg, string description)
+ {
+ if (alg is not null && !_jwtCreator.SignedResponseAlgorithmsSupported.Contains(alg, StringComparer.Ordinal))
+ {
+ return new ClientRegistrationValidationError(
+ ErrorCodes.InvalidRequest,
+ $"The signing algorithm for {description} is not supported");
+ }
+
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SigningAlgorithmsValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SigningAlgorithmsValidator.cs
new file mode 100644
index 00000000..ce0ebdd5
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SigningAlgorithmsValidator.cs
@@ -0,0 +1,89 @@
+// 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
+
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Interfaces;
+using static Abblix.Oidc.Server.Model.ClientRegistrationRequest;
+
+namespace Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation;
+
+///
+/// Validates the signing algorithms specified for request objects, backchannel authentication requests,
+/// and token endpoints in a client registration request.
+/// This class ensures that the requested signing algorithms are supported by the JWT validator,
+/// maintaining compliance with security standards.
+///
+public class SigningAlgorithmsValidator: SyncClientRegistrationContextValidator
+{
+ ///
+ /// Initializes a new instance of the class.
+ /// The constructor takes a JWT validator to access the supported signing algorithms for validation.
+ ///
+ /// The service responsible for validating signing algorithms used in JWTs.
+ public SigningAlgorithmsValidator(IJsonWebTokenValidator jwtValidator)
+ {
+ _jwtValidator = jwtValidator;
+ }
+
+ private readonly IJsonWebTokenValidator _jwtValidator;
+
+ ///
+ /// Validates the signing algorithms specified in the client registration request.
+ /// This method checks if the requested algorithms are supported by the JWT validator for various purposes.
+ ///
+ /// The validation context containing the client registration data.
+ ///
+ /// A if any signing algorithm is not supported;
+ /// otherwise, null if all validations are successful.
+ ///
+ protected override ClientRegistrationValidationError? Validate(ClientRegistrationValidationContext context)
+ {
+ var request = context.Request;
+ return Validate(request.RequestObjectSigningAlg, Parameters.RequestObjectSigningAlg)
+ ?? Validate(request.BackChannelAuthenticationRequestSigningAlg, Parameters.BackChannelAuthenticationRequestSigningAlg)
+ ?? Validate(request.TokenEndpointAuthSigningAlg, Parameters.TokenEndpointAuthSigningAlg)
+ ;
+ }
+
+ ///
+ /// Validates that the JWT validator supports the specified signing algorithm.
+ /// If the algorithm is not supported, it returns a validation error.
+ ///
+ /// The signing algorithm to validate.
+ ///
+ /// A description used in the error message to identify which signing algorithm is invalid.
+ ///
+ /// A if the algorithm is not supported; otherwise, null.
+ ///
+ private ClientRegistrationValidationError? Validate(string? alg, string description)
+ {
+ if (alg is not null && !_jwtValidator.SigningAlgorithmsSupported.Contains(alg, StringComparer.Ordinal))
+ {
+ return new ClientRegistrationValidationError(
+ ErrorCodes.InvalidRequest,
+ $"The signing algorithm for {description} is not supported");
+ }
+
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/TokenEndpointAuthMethodValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/TokenEndpointAuthMethodValidator.cs
new file mode 100644
index 00000000..54db8f20
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/TokenEndpointAuthMethodValidator.cs
@@ -0,0 +1,73 @@
+// 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
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Interfaces;
+using Abblix.Oidc.Server.Features.ClientAuthentication;
+using Abblix.Utils;
+
+namespace Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation;
+
+///
+/// Validates the authentication method specified for the token endpoint in a client registration request.
+/// This validator ensures that the provided authentication method is supported by the OpenID provider.
+///
+public class TokenEndpointAuthMethodValidator: SyncClientRegistrationContextValidator
+{
+ ///
+ /// Initializes a new instance of the class.
+ /// This constructor injects the client authenticator service used for verifying supported authentication methods.
+ ///
+ /// The client authenticator used to validate authentication methods.
+ public TokenEndpointAuthMethodValidator(IClientAuthenticator clientAuthenticator)
+ {
+ _clientAuthenticator = clientAuthenticator;
+ }
+
+ private readonly IClientAuthenticator _clientAuthenticator;
+
+ ///
+ /// Validates the token endpoint authentication method specified in the client registration request.
+ /// This method checks if the provided authentication method is among those supported by the OpenID provider.
+ ///
+ /// The validation context containing client registration data.
+ ///
+ /// A if the authentication method is not valid or supported,
+ /// or null if the request is valid.
+ ///
+ protected override ClientRegistrationValidationError? Validate(ClientRegistrationValidationContext context)
+ {
+ var request = context.Request;
+
+ // Check if the authentication method is specified and supported
+ if (request.TokenEndpointAuthMethod.HasValue() &&
+ !_clientAuthenticator.ClientAuthenticationMethodsSupported.Contains(
+ request.TokenEndpointAuthMethod, StringComparer.Ordinal))
+ {
+ return new ClientRegistrationValidationError(
+ ErrorCodes.InvalidRequest,
+ $"The specified token endpoint authentication method '{request.TokenEndpointAuthMethod}' is not supported");
+ }
+
+ return null; // No errors; the request is valid
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/EndSession/IEndSessionHandler.cs b/Abblix.Oidc.Server/Endpoints/EndSession/IEndSessionHandler.cs
index c7af5d79..69e82784 100644
--- a/Abblix.Oidc.Server/Endpoints/EndSession/IEndSessionHandler.cs
+++ b/Abblix.Oidc.Server/Endpoints/EndSession/IEndSessionHandler.cs
@@ -26,5 +26,5 @@ namespace Abblix.Oidc.Server.Endpoints.EndSession;
public interface IEndSessionHandler
{
- Task HandleAsync(Server.Model.EndSessionRequest endSessionRequest);
-}
\ No newline at end of file
+ Task HandleAsync(Model.EndSessionRequest endSessionRequest);
+}
diff --git a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/EndSessionValidationContext.cs b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/EndSessionValidationContext.cs
index 59d532b4..b315cd16 100644
--- a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/EndSessionValidationContext.cs
+++ b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/EndSessionValidationContext.cs
@@ -20,6 +20,7 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Jwt;
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Model;
@@ -45,4 +46,10 @@ public record EndSessionValidationContext(EndSessionRequest Request)
///
/// Thrown when attempting to get a null value.
public ClientInfo? ClientInfo { get; set; }
+
+ ///
+ /// The ID token associated with the end-session request.
+ /// This token is typically used to validate the identity of the user who initiated the end-session process.
+ ///
+ public JsonWebToken? IdToken { get; set; }
}
diff --git a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/IdTokenHintValidator.cs b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/IdTokenHintValidator.cs
index abd102f7..edf5ab7b 100644
--- a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/IdTokenHintValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/IdTokenHintValidator.cs
@@ -55,32 +55,45 @@ public IdTokenHintValidator(IAuthServiceJwtValidator jwtValidator)
{
var request = context.Request;
- if (!request.IdTokenHint.HasValue())
- return null;
-
- var result = await _jwtValidator.ValidateAsync(request.IdTokenHint);
-
- switch (result)
+ if (request.IdTokenHint.HasValue())
{
- case ValidJsonWebToken { Token.Payload.Audiences: var audiences }:
- if (!request.ClientId.HasValue())
- {
- context.ClientId = audiences.Single(); // TODO find what to do if there are multiple audiences???
- }
- else if (!audiences.Contains(request.ClientId, StringComparer.Ordinal))
- {
- return new EndSessionRequestValidationError(ErrorCodes.InvalidRequest,
- "The id token hint contains token issued for the client other than specified");
- }
+ var result = await _jwtValidator.ValidateAsync(
+ request.IdTokenHint,
+ ValidationOptions.Default & ~ValidationOptions.ValidateLifetime);
- break;
+ switch (result)
+ {
+ case ValidJsonWebToken { Token: var idToken, Token.Payload.Audiences: var audiences }:
+ if (!request.ClientId.HasValue())
+ {
+ 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,
+ "The id token hint contains token issued for the client other than specified");
+ }
- case JwtValidationError:
- return new EndSessionRequestValidationError(ErrorCodes.InvalidRequest,
- "The id token hint contains invalid token");
+ context.IdToken = idToken;
+ break;
+
+ case JwtValidationError:
+ return new EndSessionRequestValidationError(ErrorCodes.InvalidRequest,
+ "The id token hint contains invalid token");
- default:
- throw new UnexpectedTypeException(nameof(result), result.GetType());
+ default:
+ throw new UnexpectedTypeException(nameof(result), result.GetType());
+ }
}
return null;
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..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;
@@ -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,16 +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.
///
- /// 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.
+ /// Provides logging capabilities to record validation outcomes and errors.
+ ///
+ /// 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. 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;
@@ -61,40 +72,51 @@ 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
- /// .
- /// 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)
{
- 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 }:
+ // 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);
- 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);
- }
+ // 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:
+ _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 error: //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/Endpoints/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs
index 87acaa64..f07f2821 100644
--- a/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs
+++ b/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs
@@ -21,6 +21,7 @@
// info@abblix.com
using Abblix.DependencyInjection;
+using Abblix.Jwt;
using Abblix.Oidc.Server.Common.Configuration;
using Abblix.Oidc.Server.Common.Implementation;
using Abblix.Oidc.Server.Common.Interfaces;
@@ -30,6 +31,8 @@
using Abblix.Oidc.Server.Endpoints.Authorization.Validation;
using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication;
using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.RequestFetching;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Validation;
using Abblix.Oidc.Server.Endpoints.CheckSession;
using Abblix.Oidc.Server.Endpoints.CheckSession.Interfaces;
using Abblix.Oidc.Server.Endpoints.DynamicClientManagement;
@@ -94,13 +97,12 @@ public static IServiceCollection AddAuthorizationRequestFetchers(this IServiceCo
// Add individual authorization request fetchers as singletons
.AddSingleton()
.AddSingleton()
- .AddSingleton()
+ .AddSingleton()
// Compose the individual fetchers into a composite fetcher
- .Compose();
+ .Compose();
}
-
///
/// Adds a series of validators for authorization context as a composite service to ensure comprehensive validation
/// of authorization requests.
@@ -140,7 +142,7 @@ public static IServiceCollection AddPushedAuthorizationEndpoint(this IServiceCol
return services
.AddScoped()
.AddScoped(
- Dependency.Override())
+ Dependency.Override())
.AddScoped();
}
@@ -218,6 +220,7 @@ public static IServiceCollection AddAuthorizationGrants(this IServiceCollection
return services
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
.Compose();
}
@@ -335,6 +338,10 @@ private static IServiceCollection AddClientRegistrationContextValidators(this IS
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
.Compose();
}
@@ -373,8 +380,24 @@ public static IServiceCollection AddEndSessionContextValidators(this IServiceCol
public static IServiceCollection AddBackChannelAuthenticationEndpoint(this IServiceCollection services)
{
return services
+ .AddBackChannelAuthenticationContextValidators()
+
+ .AddScoped()
+ .AddScoped()
.AddScoped()
- .AddScoped()
- .AddScoped();
+ .AddScoped();
+ }
+
+ public static IServiceCollection AddBackChannelAuthenticationContextValidators(this IServiceCollection services)
+ {
+ return services
+ // compose BackChannelAuthenticationValidationContext validation as a pipeline of several IBackChannelAuthenticationContextValidator
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .Compose();
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Grants/AuthorizationCodeGrantHandler.cs b/Abblix.Oidc.Server/Endpoints/Token/Grants/AuthorizationCodeGrantHandler.cs
index b519ce9b..94a3c6a0 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Grants/AuthorizationCodeGrantHandler.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Grants/AuthorizationCodeGrantHandler.cs
@@ -34,16 +34,22 @@ namespace Abblix.Oidc.Server.Endpoints.Token.Grants;
///
/// Handles the authorization code grant type for OAuth 2.0.
-/// This class validates the provided authorization code against stored codes,
-/// checks the client details, and implements PKCE verification when necessary.
+/// This class validates the provided authorization code, verifies client details, and checks for PKCE compliance.
+/// PKCE is a security mechanism primarily used in public clients, and its enforcement helps prevent code injection
+/// attacks.
///
public class AuthorizationCodeGrantHandler : IAuthorizationGrantHandler
{
///
/// Initializes a new instance of the class.
+ /// The constructor sets up the services necessary for validating the parameters of a token request and managing
+ /// authorization codes. It centralizes these services to streamline the validation process and ensure secure
+ /// handling of authorization codes.
///
- /// The service to validate request parameters.
- /// The service to manage authorization codes.
+ ///
+ /// Service for validating request parameters, ensuring required fields are provided.
+ ///
+ /// Service responsible for generating, validating, and managing authorization codes.
public AuthorizationCodeGrantHandler(
IParameterValidator parameterValidator,
IAuthorizationCodeService authorizationCodeService)
@@ -56,7 +62,8 @@ public AuthorizationCodeGrantHandler(
private readonly IAuthorizationCodeService _authorizationCodeService;
///
- /// Gets the grant type this handler supports, which is the authorization code grant type.
+ /// Provides the grant type this handler supports, which is the OAuth 2.0 'authorization_code' grant type.
+ /// This information is useful for identifying the handler's capabilities in a broader authorization framework.
///
public IEnumerable GrantTypesSupported
{
@@ -64,46 +71,77 @@ public IEnumerable GrantTypesSupported
}
///
- /// Authorizes the token request asynchronously using the authorization code grant type.
- /// Validates the authorization code, verifies the client information, and ensures compliance with PKCE if used.
+ /// Authorizes a token request asynchronously using the authorization code grant type.
+ /// This method validates the authorization code submitted by the client, ensures the client making the request
+ /// is the same as the one to whom the code was originally issued, and performs any necessary PKCE checks.
+ /// It ensures that all security requirements, including client verification and PKCE validation, are enforced
+ /// before tokens are issued.
///
- /// The token request containing the authorization code.
- /// The client information associated with the request.
- /// A task representing the result of the authorization process,
- /// containing a .
+ ///
+ /// The token request containing the authorization code and other necessary parameters.
+ ///
+ /// Information about the client, used to verify that the request is valid for this client.
+ /// A task that represents the asynchronous authorization operation.
+ /// The result is either an authorized grant or an error indicating why the request failed.
public async Task AuthorizeAsync(TokenRequest request, ClientInfo clientInfo)
{
+ // Ensures the authorization code is provided in the request.
_parameterValidator.Required(request.Code, nameof(request.Code));
- var grantResult = await _authorizationCodeService.AuthorizeByCodeAsync(request.Code);
+ // Validates the authorization code and retrieves the authorization context associated with the code.
+ var result = await _authorizationCodeService.AuthorizeByCodeAsync(request.Code);
- return grantResult switch
+ // Verifies that the authorization code was issued for the requesting client.
+ return result switch
{
+ // If the client making the request is not the same as the one that initiated the authentication,
+ // return an unauthorized error.
AuthorizedGrant { Context.ClientId: var clientId } when clientId != clientInfo.ClientId
- => new InvalidGrantResult(ErrorCodes.UnauthorizedClient, "Code was issued for another client"),
+ => new InvalidGrantResult(
+ ErrorCodes.UnauthorizedClient,
+ "Code was issued for another client"),
+ // Checks if PKCE is required but the code verifier is missing from the request.
AuthorizedGrant { Context.CodeChallenge: not null } when string.IsNullOrEmpty(request.CodeVerifier)
=> new InvalidGrantResult(ErrorCodes.InvalidGrant, "Code verifier is required"),
+ // Validates the code verifier against the stored code challenge using the appropriate method (plain or S256).
AuthorizedGrant { Context: { CodeChallenge: { } challenge, CodeChallengeMethod: { } method } }
- when !string.Equals(challenge, CalculateChallenge(method, request.CodeVerifier), StringComparison.OrdinalIgnoreCase)
+ when !string.Equals(
+ challenge,
+ CalculateChallenge(method, request.CodeVerifier),
+ StringComparison.OrdinalIgnoreCase)
+
=> new InvalidGrantResult(ErrorCodes.InvalidGrant, "Code verifier is not valid"),
- _ => grantResult,
+ // Returns the authorized grant if all checks pass.
+ _ => result,
};
}
///
- /// Calculates the code challenge based on the provided method and code verifier.
- /// Supports 'plain' and 'S256' challenge methods.
+ /// Calculates the code challenge from the provided code verifier and method.
+ /// PKCE involves transforming the code verifier into a code challenge, which the authorization server verifies when
+ /// exchanging the authorization code for a token. This method ensures that the correct transformation is applied.
+ /// It supports both 'plain' and 'S256' methods, with 'S256' being the recommended approach for stronger security.
///
- /// The code challenge method used during authorization request.
- /// The code verifier submitted by the client.
- /// The calculated code challenge string.
+ /// The PKCE challenge method, either 'plain', 'S256' or 'S512'.
+ /// The code verifier submitted by the client during the token request.
+ /// The transformed code challenge based on the specified method.
private static string CalculateChallenge(string method, string codeVerifier) => method switch
{
- CodeChallengeMethods.S256 => HttpServerUtility.UrlTokenEncode(SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier))),
+ // Encodes the code verifier using SHA256 and URL-safe base64 encoding for 'S256' method.
+ CodeChallengeMethods.S256 => HttpServerUtility.UrlTokenEncode(
+ SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier))),
+
+ // Encodes the code verifier using SHA512 and URL-safe base64 encoding for 'S512' method.
+ CodeChallengeMethods.S512 => HttpServerUtility.UrlTokenEncode(
+ SHA512.HashData(Encoding.ASCII.GetBytes(codeVerifier))),
+
+ // Returns the code verifier as-is for the 'plain' method.
CodeChallengeMethods.Plain => codeVerifier,
+
+ // Throws an exception if an unsupported method is encountered.
_ => throw new ArgumentOutOfRangeException(nameof(method), $"Unknown code challenge method: {method}"),
};
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Grants/BackChannelAuthenticationGrantHandler.cs b/Abblix.Oidc.Server/Endpoints/Token/Grants/BackChannelAuthenticationGrantHandler.cs
new file mode 100644
index 00000000..26223f64
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Grants/BackChannelAuthenticationGrantHandler.cs
@@ -0,0 +1,143 @@
+// 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
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Common.Interfaces;
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+using Abblix.Oidc.Server.Features.BackChannelAuthentication;
+using Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Features.ClientInformation;
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Grants;
+
+///
+/// Handles the authorization process for backchannel authentication requests under the Client-Initiated Backchannel
+/// Authentication (CIBA) grant type.
+/// This handler validates the token request based on the backchannel authentication flow, ensuring
+/// that the client is authorized and that the user has been authenticated before tokens are issued.
+///
+public class BackChannelAuthenticationGrantHandler : IAuthorizationGrantHandler
+{
+ ///
+ /// Initializes a new instance of the class.
+ /// The storage service is injected to manage the lifecycle and retrieval of backchannel authentication requests.
+ ///
+ /// Service for storing and retrieving backchannel authentication requests.
+ /// The service to validate request parameters.
+ /// Provides access to the current time.
+ public BackChannelAuthenticationGrantHandler(
+ IBackChannelAuthenticationStorage storage,
+ IParameterValidator parameterValidator,
+ TimeProvider timeProvider)
+ {
+ _storage = storage;
+ _parameterValidator = parameterValidator;
+ _timeProvider = timeProvider;
+ }
+
+ private readonly IBackChannelAuthenticationStorage _storage;
+ private readonly IParameterValidator _parameterValidator;
+ private readonly TimeProvider _timeProvider;
+
+ ///
+ /// Specifies the grant types supported by this handler, specifically the "CIBA" (Client-Initiated Backchannel
+ /// Authentication) grant type.
+ /// This property ensures that the handler is only invoked for the specific grant type it supports.
+ ///
+ public IEnumerable GrantTypesSupported
+ {
+ get { yield return GrantTypes.Ciba; }
+ }
+
+ ///
+ /// Processes the authorization request by verifying the authentication request ID and checking the status of the
+ /// associated backchannel authentication request. This method retrieves the authentication request from storage
+ /// and determines if the request is authorized, still pending, denied or expired. Based on the status, it returns
+ /// either a success result with the authorized grant or an error result indicating why the request can't be
+ /// processed.
+ ///
+ /// The token request containing the authentication request ID and other parameters.
+ /// Information about the client making the request, used to validate client identity.
+ ///
+ ///
+ /// A indicating the outcome of the authorization process.
+ /// This could be a valid grant if the user has been authenticated, or an error if the request is pending, denied
+ /// or invalid.
+ ///
+ public async Task AuthorizeAsync(TokenRequest request, ClientInfo clientInfo)
+ {
+ // Check if the request contains a valid authentication request ID
+ _parameterValidator.Required(request.AuthenticationRequestId, nameof(request.AuthenticationRequestId));
+
+ // Try to retrieve the corresponding backchannel authentication request from storage
+ var authenticationRequest = await _storage.TryGetAsync(request.AuthenticationRequestId);
+
+ // Determine the outcome of the authorization based on the state of the backchannel authentication request
+ switch (authenticationRequest)
+ {
+ // If the user has been authenticated, remove the request and return the authorized grant
+ case { Status: BackChannelAuthenticationStatus.Authenticated, AuthorizedGrant: { } authorizedGrant }:
+ await _storage.RemoveAsync(request.AuthenticationRequestId);
+ return authorizedGrant;
+
+ // If the request is not found or has expired, return an error indicating token expiration
+ case null:
+ return new InvalidGrantResult(
+ ErrorCodes.ExpiredToken,
+ "The authentication request has expired");
+
+ // If the client making the request is not the same as the one that initiated the authentication
+ case { AuthorizedGrant.Context.ClientId: var clientId } when clientId != clientInfo.ClientId:
+ return new InvalidGrantResult(
+ ErrorCodes.UnauthorizedClient,
+ "The authentication request was started by another client");
+
+ // If the request is still pending and not yet time to poll again
+ case { Status: BackChannelAuthenticationStatus.Pending, NextPollAt: {} nextPollAt }
+ when _timeProvider.GetUtcNow() < nextPollAt:
+
+ return new InvalidGrantResult(
+ ErrorCodes.SlowDown,
+ "The authorization request is still pending as the user hasn't been authenticated");
+
+ // If the user has not yet been authenticated and the request is still pending,
+ // return an error indicating that authorization is pending
+ case { Status: BackChannelAuthenticationStatus.Pending }:
+ return new InvalidGrantResult(
+ ErrorCodes.AuthorizationPending,
+ "The authorization request is still pending. " +
+ "The polling interval must be increased by at least 5 seconds for all subsequent requests.");
+
+ // If the user denied the authentication request, return an error indicating access is denied
+ case { Status: BackChannelAuthenticationStatus.Denied }:
+ return new InvalidGrantResult(
+ ErrorCodes.AccessDenied,
+ "The authorization request is denied by the user.");
+
+ // Capture any unexpected case as an exception
+ default:
+ throw new InvalidOperationException(
+ $"The authentication request status is unexpected: {authenticationRequest.Status}.");
+ }
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Grants/CompositeAuthorizationGrantHandler.cs b/Abblix.Oidc.Server/Endpoints/Token/Grants/CompositeAuthorizationGrantHandler.cs
index 3af6fe9d..7e6c01ac 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Grants/CompositeAuthorizationGrantHandler.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Grants/CompositeAuthorizationGrantHandler.cs
@@ -28,15 +28,26 @@
namespace Abblix.Oidc.Server.Endpoints.Token.Grants;
///
-/// Represents a composite handler that manages multiple authorization grant handlers. This class allows for the delegation
-/// of authorization requests to specific handlers based on the grant type specified in the request. It supports a dynamic
-/// registration of grant handlers, facilitating the extension and customization of the authorization process to accommodate
-/// various grant types defined by the OAuth 2.0 specification.
+/// A composite handler that coordinates multiple authorization grant handlers for processing OAuth 2.0 token requests.
+/// This class allows for flexible and extensible handling of various grant types by delegating specific grant processing
+/// tasks to individual handlers. It dynamically aggregates all available grant handlers, facilitating the addition
+/// of new handlers without modifying the core authorization flow.
///
public class CompositeAuthorizationGrantHandler: IAuthorizationGrantHandler
{
+ ///
+ /// Initializes a new instance of the class.
+ /// The constructor aggregates the collection of grant handlers and organizes them by grant type for efficient
+ /// delegation. Each handler can support one or more grant types, and these are mapped to their respective handlers.
+ /// This design simplifies the extension of grant type handling, as new grant types can be added without changing
+ /// existing logic.
+ ///
+ ///
+ /// A collection of grant handlers, each responsible for a specific set of grant types.
public CompositeAuthorizationGrantHandler(IEnumerable grantHandlers)
{
+ // Create a dictionary where each grant type is mapped to its corresponding handler.
+ // This allows for fast lookup of handlers based on the requested grant type.
_grantHandlers = new Dictionary(
from handler in grantHandlers
from grantType in handler.GrantTypesSupported
@@ -47,21 +58,30 @@ from grantType in handler.GrantTypesSupported
private readonly Dictionary _grantHandlers;
///
- /// The collection of grant types supported by the composite handler, aggregating the grant types
- /// from all registered individual grant handlers.
+ /// Provides a list of all the supported grant types across the registered grant handlers.
+ /// This allows the composite handler to advertise the full set of supported grant types, which
+ /// can be used for validation and discovery of capabilities by client applications.
///
public IEnumerable GrantTypesSupported => _grantHandlers.Keys;
///
- /// Asynchronously authorizes a token request based on its grant type. Delegates the authorization
- /// process to the appropriate grant handler that supports the specified grant type in the request.
+ /// Processes a token request asynchronously by delegating the request to the appropriate handler based on
+ /// the grant type. If a handler for the requested grant type is found, it delegates the request to that handler
+ /// for processing. Otherwise, it returns an error indicating that the grant type is not supported.
+ /// This method abstracts away the complexity of identifying and invoking the correct handler, simplifying the main
+ /// authorization flow.
///
- /// The token request containing the grant type and other relevant data.
- /// Client information associated with the request, used for validation and processing.
- /// A task that resolves to a ,
- /// indicating the outcome of the authorization attempt.
+ ///
+ /// The token request, which includes the grant type and relevant parameters for processing the request.
+ ///
+ /// The client information used to validate and process the request, ensuring the request is authorized.
+ /// A task that resolves to the result of the authorization process.
+ /// If successful, it contains the granted authorization;
+ /// otherwise, it contains an error explaining why the authorization failed.
public async Task AuthorizeAsync(TokenRequest request, ClientInfo clientInfo)
{
+ // Check if there is a handler for the requested grant type.
+ // If no handler exists, return an error indicating that the grant type is unsupported.
if (!_grantHandlers.TryGetValue(request.GrantType, out var grantHandler))
{
return new InvalidGrantResult(
@@ -69,13 +89,7 @@ public async Task AuthorizeAsync(TokenRequest request,
"The grant type is not supported");
}
- if (!clientInfo.AllowedGrantTypes.Contains(request.GrantType))
- {
- return new InvalidGrantResult(
- ErrorCodes.UnauthorizedClient,
- "The grant type is not allowed for this client");
- }
-
+ // Delegate the authorization request to the handler that supports the specified grant type.
return await grantHandler.AuthorizeAsync(request, clientInfo);
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Grants/IAuthorizationGrantHandler.cs b/Abblix.Oidc.Server/Endpoints/Token/Grants/IAuthorizationGrantHandler.cs
index a04e0630..d252ec80 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Grants/IAuthorizationGrantHandler.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Grants/IAuthorizationGrantHandler.cs
@@ -35,18 +35,18 @@ namespace Abblix.Oidc.Server.Endpoints.Token.Grants;
public interface IAuthorizationGrantHandler
{
///
- /// The grant types this handler is responsible for.
+ /// Allows the authorization server to determine which grant types are supported by this handler.
///
IEnumerable GrantTypesSupported { get; }
///
- /// Processes an authorization request asynchronously, validates the request against the supported grant type
+ /// Processes an authorization request asynchronously, validates the request against the supported grant type,
/// and generates a response indicating the authorization result.
///
- /// The authorization request containing required parameters for the grant type.
- /// Client information associated with the request, used for validation and generating
- /// the response.
- /// A task that when completed successfully, returns a GrantAuthorizationResult indicating the outcome
- /// of the authorization process.
+ /// The authorization request containing the required parameters for the grant type.
+ /// Client information associated with the request, used for validation and
+ /// to generate the authorization response.
+ /// A task that, when completed successfully, returns a indicating
+ /// the outcome of the authorization process, including any tokens or error messages.
Task AuthorizeAsync(TokenRequest request, ClientInfo clientInfo);
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Grants/PasswordGrantHandler.cs b/Abblix.Oidc.Server/Endpoints/Token/Grants/PasswordGrantHandler.cs
index 17909e6c..fc0fb5c6 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Grants/PasswordGrantHandler.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Grants/PasswordGrantHandler.cs
@@ -31,14 +31,21 @@
namespace Abblix.Oidc.Server.Endpoints.Token.Grants;
///
-/// This class is responsible for handling the password grant type
-/// as part of the IAuthorizationGrantHandler process.
+/// Handles the authorization process for the password grant type within the OAuth 2.0 framework.
+/// This handler validates the user's credentials and processes token requests based on the password grant type.
+/// The password grant type allows clients to directly exchange a user's credentials (username and password)
+/// for an access token, typically for trusted clients.
///
public class PasswordGrantHandler : IAuthorizationGrantHandler
{
///
- /// Initializes a new instance of the PasswordGrantHandler class.
+ /// Initializes a new instance of the class.
+ /// The constructor sets up the required services for validating user credentials and parameters.
+ /// The parameter validator ensures the required parameters for this grant type are present,
+ /// while the user credentials authenticator is responsible for verifying the username and password.
///
+ /// A service for validating required request parameters.
+ /// A service for authenticating the user's credentials.
public PasswordGrantHandler(
IParameterValidator parameterValidator,
IUserCredentialsAuthenticator userCredentialsAuthenticator)
@@ -51,29 +58,40 @@ public PasswordGrantHandler(
private readonly IUserCredentialsAuthenticator _userCredentialsAuthenticator;
///
- /// Gets the grant type this handler supports.
+ /// Specifies the grant type that this handler supports, which is the "password" grant type.
+ /// This ensures that this handler is only invoked when processing requests with the password grant type.
///
public IEnumerable GrantTypesSupported
{
get { yield return GrantTypes.Password; }
}
+
///
- /// Authorizes the token request asynchronously using the password grant type.
+ /// Asynchronously processes the token request using the password grant type.
+ /// The handler ensures the request contains the necessary parameters, validates the user's credentials,
+ /// and then proceeds to authorize the request if the credentials are valid.
+ /// It delegates credential validation to the user credentials authenticator, which handles the security
+ /// checks related to user authentication.
///
- /// The token request to authorize.
- /// The client information associated with the request.
- /// A task representing the result of the authorization process, containing a GrantAuthorizationResult object.
+ /// The token request containing the user's credentials and other parameters.
+ /// Information about the client making the request, used for validation and context.
+ ///
+ /// A task that completes with the authorization result, which could be an error or successful grant.
+ ///
public Task AuthorizeAsync(TokenRequest request, ClientInfo clientInfo)
{
+ // Ensure that the request contains the required username and password parameters.
_parameterValidator.Required(request.UserName, nameof(request.UserName));
_parameterValidator.Required(request.Password, nameof(request.Password));
+ // Extract relevant details from the request and prepare the authorization context.
var userName = request.UserName;
var password = request.Password;
var scope = request.Scope;
var context = new AuthorizationContext(clientInfo.ClientId, scope, null);
+ // Delegate the actual user credential validation and authentication to the custom authenticator.
return _userCredentialsAuthenticator.ValidateAsync(userName, password, context);
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Grants/RefreshTokenGrantHandler.cs b/Abblix.Oidc.Server/Endpoints/Token/Grants/RefreshTokenGrantHandler.cs
index 3fa12fa8..11cd2de6 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Grants/RefreshTokenGrantHandler.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Grants/RefreshTokenGrantHandler.cs
@@ -26,43 +26,48 @@
using Abblix.Oidc.Server.Common.Interfaces;
using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
using Abblix.Oidc.Server.Features.ClientInformation;
-using Abblix.Oidc.Server.Features.Storages;
using Abblix.Oidc.Server.Features.Tokens;
using Abblix.Oidc.Server.Features.Tokens.Validation;
using Abblix.Oidc.Server.Model;
-
-
namespace Abblix.Oidc.Server.Endpoints.Token.Grants;
///
-/// This class is responsible for handling the refresh token grant type
-/// as part of the IAuthorizationGrantHandler process.
+/// Responsible for processing requests that use the refresh token grant type within the OAuth 2.0 framework.
+/// This handler validates the provided refresh token and issues a new access token when valid.
+/// It ensures that clients can obtain fresh access tokens without re-authenticating the user,
+/// enhancing user experience while maintaining security.
///
public class RefreshTokenGrantHandler : IAuthorizationGrantHandler
{
///
- /// Initializes a new instance of the RefreshTokenGrantHandler class.
+ /// Initializes a new instance of the class.
+ /// The constructor sets up services responsible for validating refresh tokens, handling JWT validation,
+ /// and interacting with the refresh token storage service.
///
+ ///
+ /// Validates the presence and format of required parameters in the request.
+ ///
+ /// Validates the JWT structure of the refresh token to ensure its authenticity and integrity.
+ ///
+ /// Handles the logic of authorizing clients and issuing new tokens based on refresh tokens.
public RefreshTokenGrantHandler(
IParameterValidator parameterValidator,
IAuthServiceJwtValidator jwtValidator,
- IRefreshTokenService refreshTokenService,
- ITokenRegistry tokenRegistry)
+ IRefreshTokenService refreshTokenService)
{
_parameterValidator = parameterValidator;
_jwtValidator = jwtValidator;
_refreshTokenService = refreshTokenService;
- _tokenRegistry = tokenRegistry;
}
private readonly IParameterValidator _parameterValidator;
private readonly IAuthServiceJwtValidator _jwtValidator;
private readonly IRefreshTokenService _refreshTokenService;
- private readonly ITokenRegistry _tokenRegistry;
///
- /// Gets the grant type this handler supports.
+ /// Indicates that this handler is responsible for processing the 'refresh_token' grant type.
+ /// The framework uses this information to ensure that this handler is only invoked for the refresh token flow.
///
public IEnumerable GrantTypesSupported
{
@@ -70,33 +75,55 @@ public IEnumerable GrantTypesSupported
}
///
- /// Authorizes the token request asynchronously using the refresh token grant type.
+ /// Processes a token request using the refresh token grant type.
+ /// This method validates the refresh token, ensures that the token is associated with the correct client,
+ /// and generates new tokens if the request is valid.
///
- /// The token request to authorize.
- /// The client information associated with the request.
- /// A task representing the result of the authorization process,
- /// containing a GrantAuthorizationResult object.
+ /// The token request, containing the refresh token and other required parameters.
+ ///
+ /// The client information, used to verify the request is coming from an authorized client.
+ ///
+ /// A task representing the outcome of the authorization process, either returning a successful grant with a new
+ /// access token or an error if the request is invalid or the refresh token is unauthorized.
+ ///
public async Task AuthorizeAsync(TokenRequest request, ClientInfo clientInfo)
{
+ // Validate that the refresh token parameter is present in the request, throwing an error if missing.
_parameterValidator.Required(request.RefreshToken, nameof(request.RefreshToken));
+ // Validate the refresh token's JWT structure and authenticity using the JWT validator service.
var jwtValidationResult = await _jwtValidator.ValidateAsync(request.RefreshToken);
switch (jwtValidationResult)
{
- case ValidJsonWebToken { Token: var token, Token.Header.Type: var tokenType }:
- if (tokenType != JwtTypes.RefreshToken)
- return new InvalidGrantResult(ErrorCodes.InvalidGrant, $"Invalid token type: {tokenType}");
+ // If the token type is invalid, return an error indicating the issue.
+ case ValidJsonWebToken { Token.Header.Type: var tokenType } when tokenType != JwtTypes.RefreshToken:
+ return new InvalidGrantResult(
+ ErrorCodes.InvalidGrant,
+ $"Invalid token type: {tokenType}");
+
+ // If the token is valid and of the correct type (refresh token), proceed with authorization.
+ case ValidJsonWebToken { Token: {} token }:
+ // Authorize the request based on the refresh token and check if the token belongs to the correct client.
var result = await _refreshTokenService.AuthorizeByRefreshTokenAsync(token);
- if (result is AuthorizedGrant { Context.ClientId: var clientId } && clientId != clientInfo.ClientId)
- return new InvalidGrantResult(ErrorCodes.InvalidGrant, "The specified grant belongs to another client");
+ if (result is AuthorizedGrant { Context.ClientId: var clientId } &&
+ clientId != clientInfo.ClientId)
+ {
+ // If the client information in the token doesn't match the request, return an error.
+ return new InvalidGrantResult(
+ ErrorCodes.InvalidGrant,
+ "The specified grant belongs to another client");
+ }
+ // If everything is valid, return the authorized result.
return result;
+ // If there was a validation error, return it as an invalid grant result.
case JwtValidationError error:
return new InvalidGrantResult(ErrorCodes.InvalidGrant, error.ErrorDescription);
+ // If an unexpected result type is encountered, throw an exception.
default:
throw new UnexpectedTypeException(nameof(jwtValidationResult), jwtValidationResult.GetType());
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/AuthorizedGrant.cs b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/AuthorizedGrant.cs
index 427c3bce..ffdbdfaa 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/AuthorizedGrant.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/AuthorizedGrant.cs
@@ -38,9 +38,8 @@ public record AuthorizedGrant(AuthSession AuthSession, AuthorizationContext Cont
: GrantAuthorizationResult
{
///
- /// An array of tokens that have been issued as part of this grant.
- /// This may include access tokens, refresh tokens, or other types of tokens
- /// depending on the authorization flow and client request.
+ /// An array of tokens that have been issued as part of this grant. This may include access tokens, refresh tokens
+ /// or other types of tokens depending on the authorization flow and client request.
///
public TokenInfo[]? IssuedTokens { get; init; }
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/TokenRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/Token/TokenRequestProcessor.cs
index 0ce7d8b6..cec15ab7 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/TokenRequestProcessor.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/TokenRequestProcessor.cs
@@ -79,7 +79,7 @@ public TokenRequestProcessor(
public async Task ProcessAsync(ValidTokenRequest request)
{
var clientInfo = request.ClientInfo;
- clientInfo.CheckClient();
+ clientInfo.CheckClientLicense();
var authContext = _tokenContextEvaluator.EvaluateAuthorizationContext(request);
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/AuthorizationGrantValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/AuthorizationGrantValidator.cs
index 78710c25..38a3594e 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Validation/AuthorizationGrantValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/AuthorizationGrantValidator.cs
@@ -27,8 +27,22 @@
namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+///
+/// Validates the authorization grant in the context of a token request, ensuring that the request is authorized
+/// and that the associated redirect URI matches the one used during the initial authorization request
+///
+///
+/// This validator interacts with the to perform the necessary checks
+/// on the authorization grant. It ensures that the token request is made for an authorized grant and verifies
+/// the consistency of the redirect URI. If the grant is valid and authorized, it updates the validation context
+///
public class AuthorizationGrantValidator: ITokenContextValidator
{
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified grant handler.
+ ///
+ /// The handler responsible for authorizing the grant.
public AuthorizationGrantValidator(IAuthorizationGrantHandler grantHandler)
{
_grantHandler = grantHandler;
@@ -36,24 +50,49 @@ public AuthorizationGrantValidator(IAuthorizationGrantHandler grantHandler)
private readonly IAuthorizationGrantHandler _grantHandler;
+ ///
+ /// Asynchronously validates the authorization grant in the token request context. This method checks if the grant
+ /// is valid and authorized for the client making the request. It also ensures that the redirect URI used in the
+ /// token request matches the one used during the initial authorization request.
+ ///
+ /// The validation context containing the token request and client information.
+ ///
+ /// A if the authorization grant is invalid,
+ /// including an error code and description;
+ /// otherwise, null indicating that the grant is valid and the context has been updated.
+ ///
public async Task ValidateAsync(TokenValidationContext context)
{
+ // Ensure the client is authorized to use the requested grant type
+ if (!context.ClientInfo.AllowedGrantTypes.Contains(context.Request.GrantType))
+ {
+ return new TokenRequestError(
+ ErrorCodes.UnauthorizedClient,
+ "The grant type is not allowed for this client");
+ }
+
var result = await _grantHandler.AuthorizeAsync(context.Request, context.ClientInfo);
switch (result)
{
+ // Deny the request if the grant is invalid, providing the specific reason for rejection
case InvalidGrantResult { Error: var error, ErrorDescription: var description }:
return new TokenRequestError(error, description);
+ // Ensure that the redirect URI used matches the original authorization request
+ // to prevent redirection attacks
case AuthorizedGrant { Context.RedirectUri: var redirectUri }
when redirectUri != context.Request.RedirectUri:
return new TokenRequestError(
ErrorCodes.InvalidGrant,
"The redirect Uri value does not match to the value used before");
+ // Accept the request if the grant is valid, and securely store it in the validation context
+ // for further processing
case AuthorizedGrant grant:
context.AuthorizedGrant = grant;
return null;
+ // Handle any unexpected results in a way that ensures predictability in the flow
default:
throw new UnexpectedTypeException(nameof(result), result.GetType());
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/ClientValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/ClientValidator.cs
index 4ae3f43d..57699825 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Validation/ClientValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/ClientValidator.cs
@@ -26,8 +26,20 @@
namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+///
+/// Validates the client information in the context of a token request, ensuring that the client is properly authenticated.
+///
+///
+/// This validator is responsible for authenticating the client making the token request. It leverages the
+/// to perform the authentication, and if successful, attaches the client information
+/// to the validation context. If the authentication fails, it returns an error indicating that the client is not authorized.
+///
public class ClientValidator: ITokenContextValidator
{
+ ///
+ /// Initializes a new instance of the class with the specified client authenticator.
+ ///
+ /// The client authenticator used to authenticate the client.
public ClientValidator(IClientAuthenticator clientAuthenticator)
{
_clientAuthenticator = clientAuthenticator;
@@ -35,6 +47,16 @@ public ClientValidator(IClientAuthenticator clientAuthenticator)
private readonly IClientAuthenticator _clientAuthenticator;
+ ///
+ /// Asynchronously validates the client in the token request context. This method checks if the client
+ /// can be authenticated using the provided client request information. If the client is successfully authenticated,
+ /// the client information is added to the context; otherwise, an error is returned.
+ ///
+ /// The validation context containing the token request and client information.
+ ///
+ /// A if the client cannot be authenticated,
+ /// otherwise null indicating successful validation.
+ ///
public async Task ValidateAsync(TokenValidationContext context)
{
var clientRequest = context.ClientRequest;
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/ITokenContextValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/ITokenContextValidator.cs
index c12032cd..41a6322c 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Validation/ITokenContextValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/ITokenContextValidator.cs
@@ -24,7 +24,21 @@
namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+///
+/// Defines the contract for a token context validator, responsible for validating different aspects of a token request
+/// within a given context. Implementations of this interface ensure that the token request adheres to
+/// the expected security and business rules.
+///
public interface ITokenContextValidator
{
+ ///
+ /// Asynchronously validates the token request within the provided context, checking for compliance with
+ /// the necessary validation rules such as client authentication, scope validation, grant validation, etc.
+ ///
+ /// The context containing the token request and related information that needs to be validated.
+ ///
+ /// A containing error details if the validation fails;
+ /// otherwise, returns null indicating that the validation was successful.
+ ///
Task ValidateAsync(TokenValidationContext context);
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/SyncTokenContextValidatorBase.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/SyncTokenContextValidatorBase.cs
index 1ac60369..43dbc280 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Validation/SyncTokenContextValidatorBase.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/SyncTokenContextValidatorBase.cs
@@ -24,10 +24,36 @@
namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+///
+/// Provides a base class for implementing synchronous token context validators.
+/// This class simplifies the creation of token context validators by offering a synchronous validation method
+/// that is automatically wrapped in an asynchronous call.
+///
public abstract class SyncTokenContextValidatorBase : ITokenContextValidator
{
+ ///
+ /// Asynchronously validates the token request within the provided context by invoking the synchronous
+ /// method.
+ ///
+ ///
+ /// The context containing the token request and related information that needs to be validated.
+ ///
+ /// A that resolves to a containing error details
+ /// if the validation fails;
+ /// otherwise, resolves to null indicating that the validation was successful.
+ ///
public Task ValidateAsync(TokenValidationContext context)
=> Task.FromResult(Validate(context));
+ ///
+ /// Validates the token request within the provided context. This method must be implemented by derived classes
+ /// to perform the specific validation logic.
+ ///
+ ///
+ /// The context containing the token request and related information that needs to be validated.
+ ///
+ /// A containing error details if the validation fails;
+ /// otherwise, returns null indicating that the validation was successful.
+ ///
protected abstract TokenRequestError? Validate(TokenValidationContext context);
-}
\ No newline at end of file
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenContextValidatorComposite.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenContextValidatorComposite.cs
index bacd252e..1c84b178 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenContextValidatorComposite.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenContextValidatorComposite.cs
@@ -24,18 +24,39 @@
namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+///
+/// Represents a composite validator for token context validation, executing a sequence of individual validators.
+/// This class allows multiple validators to be combined, each responsible for a specific validation step,
+/// and short-circuits the validation process if any step fails.
+///
public class TokenContextValidatorComposite : ITokenContextValidator
{
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified array of validators.
+ ///
+ /// An array of validators representing the steps in the validation process.
public TokenContextValidatorComposite(ITokenContextValidator[] validators)
{
_validators = validators;
}
///
- /// The array of validators representing the steps in the validation process.
+ /// The array of validators that will be executed in sequence during the validation process.
///
private readonly ITokenContextValidator[] _validators;
+ ///
+ /// Asynchronously validates the token request by executing each validator in the sequence.
+ /// The validation process stops at the first encountered error and returns it.
+ /// If all validators succeed, the method returns null, indicating successful validation.
+ ///
+ /// The context containing the token request and related information
+ /// that needs to be validated.
+ ///
+ /// A containing error details if any validation step fails;
+ /// otherwise, returns null indicating that all validation steps were successful.
+ ///
public async Task ValidateAsync(TokenValidationContext context)
{
foreach (var validator in _validators)
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenValidationContext.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenValidationContext.cs
index a488a71f..47ff30c1 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenValidationContext.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenValidationContext.cs
@@ -24,6 +24,7 @@
using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Model;
+using Abblix.Utils;
namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
@@ -32,17 +33,22 @@ namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
///
public record TokenValidationContext(TokenRequest Request, ClientRequest ClientRequest)
{
+ private ClientInfo? _clientInfo;
+
///
/// Information about the client making the request, derived from the client authentication process.
///
- public ClientInfo ClientInfo { get; set; }
+ ///
+ /// Thrown when trying to access this property before it is set.
+ ///
+ public ClientInfo ClientInfo { get => _clientInfo.NotNull(nameof(ClientInfo)); set => _clientInfo = value; }
///
/// Represents the result of an authorized grant, containing both the session and context of the authorization.
/// This object is essential for ensuring that the grant is valid and for extracting any additional information
/// needed for token generation.
///
- public AuthorizedGrant AuthorizedGrant { get; set; }
+ public AuthorizedGrant AuthorizedGrant { get; set; } = default!;
///
/// Defines the scope of access requested or authorized. This array of scope definitions helps in determining
diff --git a/Abblix.Oidc.Server/Features/BackChannelAuthentication/AuthenticationRequestIdGenerator.cs b/Abblix.Oidc.Server/Features/BackChannelAuthentication/AuthenticationRequestIdGenerator.cs
new file mode 100644
index 00000000..0887c5ba
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/BackChannelAuthentication/AuthenticationRequestIdGenerator.cs
@@ -0,0 +1,58 @@
+// 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
+
+using Abblix.Oidc.Server.Common.Configuration;
+using Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces;
+using Abblix.Utils;
+using Microsoft.Extensions.Options;
+
+namespace Abblix.Oidc.Server.Features.BackChannelAuthentication;
+
+///
+/// Generates a unique authentication request ID using a cryptographically secure random number generator.
+/// This ID is encoded for safe use in URLs and is typically used in backchannel authentication flows.
+///
+public class AuthenticationRequestIdGenerator : IAuthenticationRequestIdGenerator
+{
+ ///
+ /// Initializes a new instance of the class,
+ /// using the provided OIDC options for configuring the request ID length.
+ ///
+ /// The configuration options for OIDC, including settings for backchannel authentication.
+ ///
+ public AuthenticationRequestIdGenerator(IOptions options)
+ {
+ _options = options;
+ }
+
+ private readonly IOptions _options;
+
+ ///
+ /// Generates a unique authentication request ID by creating a cryptographically secure random byte array
+ /// and encoding it for safe use in URLs.
+ ///
+ /// A URL-safe, base64-encoded authentication request ID.
+ public string GenerateAuthenticationRequestId()
+ => HttpServerUtility.UrlTokenEncode(
+ CryptoRandom.GetRandomBytes(
+ _options.Value.BackChannelAuthentication.RequestIdLength));
+}
diff --git a/Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationRequest.cs b/Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationRequest.cs
new file mode 100644
index 00000000..1d168c00
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationRequest.cs
@@ -0,0 +1,50 @@
+// 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
+
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+
+namespace Abblix.Oidc.Server.Features.BackChannelAuthentication;
+
+///
+/// Represents a backchannel authentication request as part of the Client-Initiated Backchannel Authentication (CIBA)
+/// protocol.
+/// This request facilitates the authentication of users without requiring immediate interaction with their devices,
+/// allowing for a more flexible and user-friendly authentication experience.
+///
+///
+/// The authorized grant associated with this authentication request,
+/// containing details about the user's authorization context.
+///
+public record BackChannelAuthenticationRequest(AuthorizedGrant AuthorizedGrant)
+{
+ ///
+ /// Specifies the next time the client should poll for updates regarding the authentication request.
+ /// This helps manage the timing of polling requests efficiently.
+ ///
+ public DateTimeOffset? NextPollAt { get; set; }
+
+ ///
+ /// Indicates the current status of the backchannel authentication request.
+ /// Defaults to Pending, reflecting that the request has not yet been resolved.
+ ///
+ public BackChannelAuthenticationStatus Status { get; set; } = BackChannelAuthenticationStatus.Pending;
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/IBackChannelAuthenticationStorage.cs b/Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationStatus.cs
similarity index 51%
rename from Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/IBackChannelAuthenticationStorage.cs
rename to Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationStatus.cs
index f7227de9..5d9e4926 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/IBackChannelAuthenticationStorage.cs
+++ b/Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationStatus.cs
@@ -20,9 +20,27 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication;
+namespace Abblix.Oidc.Server.Features.BackChannelAuthentication;
-public interface IBackChannelAuthenticationStorage
+///
+/// Represents the various states of a backchannel authentication request.
+/// This enumeration defines the possible statuses that an authentication request can have,
+/// facilitating the management of the authentication process in Client-Initiated Backchannel Authentication (CIBA).
+///
+public enum BackChannelAuthenticationStatus
{
- Task StoreAsync(BackChannelAuthentication backChannelAuthentication);
+ ///
+ /// Indicates that the authentication request is pending and has not yet been processed.
+ ///
+ Pending,
+
+ ///
+ /// Indicates that the authentication request has been denied, either by the user or the system.
+ ///
+ Denied,
+
+ ///
+ /// Indicates that the authentication request has been successfully authenticated.
+ ///
+ Authenticated,
}
diff --git a/Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationStorage.cs b/Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationStorage.cs
new file mode 100644
index 00000000..bc464112
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/BackChannelAuthentication/BackChannelAuthenticationStorage.cs
@@ -0,0 +1,100 @@
+// 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
+
+using Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Features.Storages;
+
+namespace Abblix.Oidc.Server.Features.BackChannelAuthentication;
+
+///
+/// Implements the storage of backchannel authentication requests, allowing for persistence
+/// and retrieval of authentication request data in the context of Client-Initiated Backchannel Authentication (CIBA).
+///
+public class BackChannelAuthenticationStorage : IBackChannelAuthenticationStorage
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The storage system used for persisting authentication requests.
+ /// Generator for creating unique authentication request IDs.
+ public BackChannelAuthenticationStorage(
+ IEntityStorage storage,
+ IAuthenticationRequestIdGenerator authenticationRequestIdGenerator)
+ {
+ _storage = storage;
+ _authenticationRequestIdGenerator = authenticationRequestIdGenerator;
+ }
+
+ private readonly IEntityStorage _storage;
+ private readonly IAuthenticationRequestIdGenerator _authenticationRequestIdGenerator;
+
+ ///
+ /// Asynchronously stores a backchannel authentication request and generates a unique identifier for it.
+ /// This method also sets an expiration duration for the stored request.
+ ///
+ /// The backchannel authentication request to store.
+ /// The duration after which the stored request will expire.
+ ///
+ /// A task that represents the asynchronous operation, containing the unique ID of the stored authentication request.
+ ///
+ public async Task StoreAsync(BackChannelAuthenticationRequest authenticationRequest, TimeSpan expiresIn)
+ {
+ var authenticationRequestId = _authenticationRequestIdGenerator.GenerateAuthenticationRequestId();
+
+ await _storage.SetAsync(
+ ToKeyString(authenticationRequestId),
+ authenticationRequest,
+ new StorageOptions { AbsoluteExpirationRelativeToNow = expiresIn });
+
+ return authenticationRequestId;
+ }
+
+ ///
+ /// Tries to retrieve a backchannel authentication request by its unique identifier.
+ ///
+ /// The unique identifier of the authentication request to retrieve.
+ ///
+ /// A task that represents the asynchronous operation, containing the authentication request if found;
+ /// otherwise, null.
+ ///
+ public Task TryGetAsync(string authenticationRequestId)
+ => _storage.GetAsync(ToKeyString(authenticationRequestId), true);
+
+ ///
+ /// Removes a backchannel authentication request from storage using its unique identifier.
+ /// This method allows for cleaning up expired or completed authentication requests.
+ ///
+ /// The unique identifier of the authentication request to remove.
+ ///
+ /// A task that represents the asynchronous operation of removing the request from storage.
+ ///
+ public Task RemoveAsync(string authenticationRequestId)
+ => _storage.RemoveAsync(ToKeyString(authenticationRequestId));
+
+ ///
+ /// Converts the authentication request ID into a key string format for storage purposes.
+ ///
+ /// The unique identifier of the authentication request.
+ /// A formatted key string for storing the request in the storage system.
+ private static string ToKeyString(string authenticationRequestId)
+ => $"{nameof(authenticationRequestId)}:{authenticationRequestId}";
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationStorage.cs b/Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IAuthenticationRequestIdGenerator.cs
similarity index 54%
rename from Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationStorage.cs
rename to Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IAuthenticationRequestIdGenerator.cs
index 92e397fb..324e412e 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/BackChannelAuthenticationStorage.cs
+++ b/Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IAuthenticationRequestIdGenerator.cs
@@ -1,32 +1,37 @@
// 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.Endpoints.BackChannelAuthentication;
+namespace Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces;
-public class BackChannelAuthenticationStorage : IBackChannelAuthenticationStorage
+///
+/// Defines the contract for generating unique authentication request identifiers in the context of a backchannel
+/// or other authentication flows. This identifier is used to track and reference individual authentication requests.
+///
+public interface IAuthenticationRequestIdGenerator
{
- ///
- public Task StoreAsync(BackChannelAuthentication backChannelAuthentication)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Generates a unique authentication request ID, which is used to identify a specific
+ /// authentication request during the backchannel authentication flow or similar processes.
+ ///
+ /// The generated authentication request ID as a string.
+ string GenerateAuthenticationRequestId();
}
diff --git a/Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationStorage.cs b/Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationStorage.cs
new file mode 100644
index 00000000..7e8d0f37
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IBackChannelAuthenticationStorage.cs
@@ -0,0 +1,62 @@
+// 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.Features.BackChannelAuthentication.Interfaces;
+
+///
+/// Defines the contract for a storage system responsible for persisting and retrieving
+/// backchannel authentication requests in the context of Client-Initiated Backchannel Authentication (CIBA).
+///
+public interface IBackChannelAuthenticationStorage
+{
+ ///
+ /// Asynchronously stores a backchannel authentication request in the storage system.
+ /// This method saves the provided authentication request and sets its expiration based on the specified duration.
+ ///
+ /// The backchannel authentication request to store.
+ /// The duration after which the stored request will expire.
+ ///
+ /// A task that represents the asynchronous operation, containing the ID of the stored authentication request.
+ ///
+ Task StoreAsync(BackChannelAuthenticationRequest authenticationRequest, TimeSpan expiresIn);
+
+ ///
+ /// Tries to retrieve a backchannel authentication request by its unique identifier.
+ /// This method checks if a request exists for the specified ID and returns it if found.
+ ///
+ /// The unique identifier of the authentication request to retrieve.
+ ///
+ /// A task that represents the asynchronous operation, containing the authentication request if found;
+ /// otherwise, null.
+ ///
+ Task TryGetAsync(string authenticationRequestId);
+
+ ///
+ /// Removes a backchannel authentication request from the storage system using its unique identifier.
+ /// This method allows for cleanup of expired or completed authentication requests.
+ ///
+ /// The unique identifier of the authentication request to remove.
+ ///
+ /// A task that represents the asynchronous operation of removing the request from storage.
+ ///
+ Task RemoveAsync(string authenticationRequestId);
+}
diff --git a/Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IUserDeviceAuthenticationHandler.cs b/Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IUserDeviceAuthenticationHandler.cs
new file mode 100644
index 00000000..22c0b49a
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/BackChannelAuthentication/Interfaces/IUserDeviceAuthenticationHandler.cs
@@ -0,0 +1,48 @@
+// 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
+
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+using Abblix.Oidc.Server.Features.UserAuthentication;
+
+namespace Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces;
+
+///
+/// Defines the contract for initiating user authentication on a device in the context of a backchannel authentication
+/// flow. This interface is responsible for handling the initiation of the authentication process for the end-user
+/// on their device, based on a validated backchannel authentication request.
+///
+public interface IUserDeviceAuthenticationHandler
+{
+ ///
+ /// Initiates the authentication process for the user on their device, based on a validated backchannel
+ /// authentication request.
+ /// This may involve sending a notification to the user’s device, starting an out-of-band
+ /// authentication process, or performing other steps required to authenticate the user asynchronously.
+ ///
+ /// The validated backchannel authentication request containing user and client information
+ /// required to initiate the authentication process.
+ ///
+ /// A representing the asynchronous operation to initiate the authentication process.
+ ///
+ Task> InitiateAuthenticationAsync(ValidBackChannelAuthenticationRequest request);
+}
diff --git a/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs b/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs
index 930db230..4d6b551e 100644
--- a/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs
+++ b/Abblix.Oidc.Server/Features/ClientAuthentication/PrivateKeyJwtAuthenticator.cs
@@ -23,64 +23,48 @@
using Abblix.Jwt;
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Common.Exceptions;
-using Abblix.Oidc.Server.Common.Interfaces;
using Abblix.Oidc.Server.Features.ClientInformation;
-using Abblix.Oidc.Server.Features.Licensing;
using Abblix.Oidc.Server.Features.Storages;
using Abblix.Oidc.Server.Features.Tokens.Revocation;
+using Abblix.Oidc.Server.Features.Tokens.Validation;
using Abblix.Oidc.Server.Model;
using Abblix.Utils;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using static Abblix.Oidc.Server.Model.ClientRequest.Parameters;
-using JsonWebKey = Abblix.Jwt.JsonWebKey;
namespace Abblix.Oidc.Server.Features.ClientAuthentication;
///
-/// Authenticates client requests using the Private Key JWT authentication method.
-/// This method is typically used in scenarios where clients can securely hold a private key and
-/// authenticate by providing a JWT signed with that key.
+/// Authenticates clients using the Private Key JWT method, verifying the client's identity through a signed JWT
+/// that the client provides. This method is suitable for clients that can securely store and use private keys.
///
public class PrivateKeyJwtAuthenticator : IClientAuthenticator
{
///
/// Initializes a new instance of the class.
///
- /// Logger for logging authentication process information and warnings.
- /// Validator for JSON Web Tokens (JWT) used in client assertions.
- /// Provider for retrieving client information,
- /// essential for validating client identity.
- /// Provider for retrieving information about the current request,
- /// used in validating JWT claims like audience.
- /// Registry for managing the status of tokens, such as marking them used.
- /// Provider for retrieving client JSON Web Key Sets (JWKS),
- /// necessary for validating JWT signatures.
+ /// Logger for recording the authentication process and any issues encountered.
+ /// Registry for managing the status of JWTs, such as marking them as used or invalid.
+ ///
+ /// Service provider used to resolve scoped dependencies.
public PrivateKeyJwtAuthenticator(
ILogger logger,
- IJsonWebTokenValidator tokenValidator,
- IClientInfoProvider clientInfoProvider,
- IRequestInfoProvider requestInfoProvider,
ITokenRegistry tokenRegistry,
- IClientKeysProvider clientJwksProvider)
+ IServiceProvider serviceProvider)
{
_logger = logger;
- _tokenValidator = tokenValidator;
- _clientInfoProvider = clientInfoProvider;
- _requestInfoProvider = requestInfoProvider;
_tokenRegistry = tokenRegistry;
- _clientJwksProvider = clientJwksProvider;
+ _serviceProvider = serviceProvider;
}
private readonly ILogger _logger;
- private readonly IJsonWebTokenValidator _tokenValidator;
- private readonly IClientInfoProvider _clientInfoProvider;
- private readonly IRequestInfoProvider _requestInfoProvider;
private readonly ITokenRegistry _tokenRegistry;
- private readonly IClientKeysProvider _clientJwksProvider;
+ private readonly IServiceProvider _serviceProvider;
///
/// Indicates the client authentication method supported by this authenticator.
- /// This method utilizes private keys and JSON Web Tokens (JWT) for client authentication,
+ /// This method uses private keys and JSON Web Tokens (JWT) for client authentication,
/// allowing clients to assert their identity through the use of asymmetric key cryptography.
/// It is designed for environments where the client can securely hold a private key.
///
@@ -90,44 +74,53 @@ public IEnumerable ClientAuthenticationMethodsSupported
}
///
- /// Asynchronously tries to authenticate a client request using the Private Key JWT method.
- /// Validates the JWT and checks if the client is authorized to use this authentication method.
+ /// Attempts to authenticate a client using the Private Key JWT method by validating the JWT provided in
+ /// the client request.
///
- /// The client request to authenticate.
- ///
- /// A representing the asynchronous operation, which upon completion will yield the
- /// authenticated , or null if the authentication is unsuccessful.
- ///
+ /// 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)
+ {
+ _logger.LogWarning(
+ $"{ClientAssertionType} is not '{ClientAssertionTypes.JwtBearer}'");
return null;
+ }
if (!request.ClientAssertion.HasValue())
{
- _logger.LogWarning($"{ClientAssertionType} is '{ClientAssertionTypes.JwtBearer}', but {ClientAssertion} is empty");
+ _logger.LogWarning(
+ $"{ClientAssertionType} is '{ClientAssertionTypes.JwtBearer}', but {ClientAssertion} is empty");
return null;
}
- var issuerValidator = new IssuerValidator(_clientInfoProvider, _clientJwksProvider);
-
- var result = await _tokenValidator.ValidateAsync(
- request.ClientAssertion,
- new ValidationParameters
- {
- ValidateAudience = ValidateAudience,
- ValidateIssuer = issuerValidator.ValidateIssuer,
- ResolveIssuerSigningKeys = issuerValidator.ResolveIssuerSigningKeys,
- });
+ 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)
+ switch (result, clientInfo)
{
- case ValidJsonWebToken validToken:
+ case (ValidJsonWebToken validToken, { TokenEndpointAuthMethod: ClientAuthenticationMethods.PrivateKeyJwt }):
token = validToken.Token;
break;
- case JwtValidationError error:
+ 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;
@@ -156,105 +149,11 @@ public IEnumerable ClientAuthenticationMethodsSupported
return null;
}
- if (token is { Payload: { JwtId: { } jwtId, ExpiresAt: {} expiresAt } })
+ if (token is { Payload: { JwtId: { } jwtId, ExpiresAt: { } expiresAt } })
{
await _tokenRegistry.SetStatusAsync(jwtId, JsonWebTokenStatus.Used, expiresAt);
}
- return issuerValidator.ClientInfo;
- }
-
- private Task ValidateAudience(IEnumerable audiences)
- {
- var requestUri = _requestInfoProvider.RequestUri;
- var result = audiences.Contains(requestUri);
- if (!result)
- {
- _logger.LogWarning(
- "Audience validation failed, token audiences: {@Audiences}, actual requestUri: {RequestUri}",
- audiences, requestUri);
- }
-
- return Task.FromResult(result);
- }
-
- ///
- /// Validates issuers and resolves their signing keys to ensure secure token validation and authentication processes.
- ///
- ///
- /// This class plays a critical role in the security infrastructure by validating token issuers against known client
- /// information and resolving the issuer's signing keys for JWT validation. It supports the secure and reliable
- /// verification of JWTs, which are central to authentication and authorization mechanisms.
- ///
- private class IssuerValidator
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// Provides access to client information.
- /// Provides access to the client's JSON Web Keys (JWKs).
- public IssuerValidator(IClientInfoProvider clientInfoProvider, IClientKeysProvider clientJwksProvider)
- {
- _clientInfoProvider = clientInfoProvider;
- _clientJwksProvider = clientJwksProvider;
- }
-
- private readonly IClientInfoProvider _clientInfoProvider;
- private readonly IClientKeysProvider _clientJwksProvider;
-
- ///
- /// The client information if the issuer has been successfully validated.
- ///
- public ClientInfo? ClientInfo { get; private set; }
-
- ///
- /// Validates the issuer by attempting to match it with known client information. If the issuer has already been
- /// validated and stored in , the method checks if the provided issuer matches
- /// the stored client ID. Throws an exception if a mismatch is detected, ensuring that
- /// the validator is not misused to validate issuers against different client information.
- ///
- /// The issuer value to validate.
- /// A task that represents the asynchronous operation. The task result contains a boolean
- /// indicating whether the issuer has been successfully validated. If the issuer is already validated and matches
- /// the stored client information, the validation is considered successful without further checks.
- /// Thrown if attempting to validate a different issuer than the one
- /// associated with already stored client information.
- public async Task ValidateIssuer(string issuer)
- {
- switch (ClientInfo)
- {
- case { ClientId: var clientId } when issuer != clientId:
- throw new InvalidOperationException(
- $"Trying to validate issuer {issuer}, but already has info about client {clientId}");
-
- case not null:
- return true;
-
- default:
- var clientInfo = await _clientInfoProvider.TryFindClientAsync(issuer).WithLicenseCheck();
- if (clientInfo is not { TokenEndpointAuthMethod: ClientAuthenticationMethods.PrivateKeyJwt })
- {
- return false;
- }
-
- ClientInfo = clientInfo;
- return true;
- }
- }
-
- ///
- /// Asynchronously resolves the signing keys for a validated issuer's JWTs.
- ///
- /// The issuer URL whose signing keys are to be resolved.
- /// An asynchronous stream of objects representing the issuer's
- /// signing keys.
- public async IAsyncEnumerable ResolveIssuerSigningKeys(string issuer)
- {
- if (!await ValidateIssuer(issuer))
- yield break;
-
- await foreach (var key in _clientJwksProvider.GetSigningKeys(ClientInfo.NotNull(nameof(ClientInfo))))
- yield return key;
- }
+ return clientInfo;
}
}
diff --git a/Abblix.Oidc.Server/Features/ClientInformation/ClientInfo.cs b/Abblix.Oidc.Server/Features/ClientInformation/ClientInfo.cs
index 525f84a9..9658e5db 100644
--- a/Abblix.Oidc.Server/Features/ClientInformation/ClientInfo.cs
+++ b/Abblix.Oidc.Server/Features/ClientInformation/ClientInfo.cs
@@ -46,7 +46,7 @@ public record ClientInfo(string ClientId)
///
/// Classifies the client based on its ability to securely maintain a client secret.
/// This classification influences the authorization flow and token endpoint authentication method that
- /// the client can utilize. Public clients, such as mobile or desktop applications, cannot securely store secrets,
+ /// the client can utilize. Public clients, such as mobile or desktop applications, can’t securely store secrets,
/// while confidential clients, like server-side web applications, can.
///
public ClientType ClientType { get; set; } = ClientType.Public;
@@ -129,7 +129,7 @@ public record ClientInfo(string ClientId)
public string[] AllowedGrantTypes { get; set; } = { GrantTypes.AuthorizationCode };
///
- /// Allows the client to request tokens that enable access to the user's resources while they are offline.
+ /// Allows the client to request tokens that enable access to the user's resources while they’re offline.
///
public bool? OfflineAccessAllowed { get; set; } = false;
@@ -200,9 +200,40 @@ public record ClientInfo(string ClientId)
///
/// Used in conjunction with pairwise subject identifiers to calculate the subject value returned to the client.
- /// This field is particularly relevant for ensuring user privacy by providing a different subject identifier
+ /// This field is particularly relevant to ensuring user privacy by providing a different subject identifier
/// to each client, even if it's the same end-user. It typically contains a URL or a unique identifier
/// representing the client's sector.
///
public string? SectorIdentifier { get; set; }
+
+ ///
+ /// Indicates whether the login hint token should be parsed and validated as a JSON Web Token (JWT).
+ ///
+ ///
+ /// If this property is set to false, it means the login hint token is not in JWT format.
+ /// In this case, the client is responsible for parsing and validating the token as part of the validation flow,
+ /// as the authorization server will not handle its validation automatically.
+ ///
+ public bool ParseLoginHintTokenAsJwt { get; set; } = true;
+
+ ///
+ /// The backchannel token delivery mode to be used by this client. This determines how tokens are delivered
+ /// during backchannel authentication.
+ ///
+ public string? BackChannelTokenDeliveryMode { get; set; }
+
+ ///
+ /// The endpoint where backchannel client notifications are sent for this client.
+ ///
+ public Uri? BackChannelClientNotificationEndpoint { get; set; }
+
+ ///
+ /// The signing algorithm used for backchannel authentication requests sent to this client.
+ ///
+ public string? BackChannelAuthenticationRequestSigningAlg { get; set; }
+
+ ///
+ /// Indicates whether the backchannel authentication process supports user codes for this client.
+ ///
+ public bool BackChannelUserCodeParameter { get; set; } = false;
}
diff --git a/Abblix.Oidc.Server/Features/Consents/NullConsentService.cs b/Abblix.Oidc.Server/Features/Consents/NullConsentService.cs
index 383fbc70..0150b762 100644
--- a/Abblix.Oidc.Server/Features/Consents/NullConsentService.cs
+++ b/Abblix.Oidc.Server/Features/Consents/NullConsentService.cs
@@ -33,7 +33,7 @@ namespace Abblix.Oidc.Server.Features.Consents;
/// consent-related checks and workflows. If your application requires user consent for accessing specific scopes
/// or resources, replace this service with a custom implementation that appropriately handles such requirements.
///
-public class NullConsentService : IUserConsentsProvider, IConsentService
+public class NullConsentService : IUserConsentsProvider
{
///
/// Retrieves the user consents for a given authorization request and authentication session.
diff --git a/Abblix.Oidc.Server/Features/Consents/PromptConsentDecorator.cs b/Abblix.Oidc.Server/Features/Consents/PromptConsentDecorator.cs
index 5ba04959..1b3ad88b 100644
--- a/Abblix.Oidc.Server/Features/Consents/PromptConsentDecorator.cs
+++ b/Abblix.Oidc.Server/Features/Consents/PromptConsentDecorator.cs
@@ -38,7 +38,7 @@ public class PromptConsentDecorator: IUserConsentsProvider
/// provider.
///
/// The inner-to - delegate calls to when no explicit
- /// prompting is needed.
+ /// prompting is necessary.
public PromptConsentDecorator(IUserConsentsProvider inner)
{
_inner = inner;
diff --git a/Abblix.Oidc.Server/Features/Licensing/AggregationExtensions.cs b/Abblix.Oidc.Server/Features/Licensing/AggregationExtensions.cs
index 5b3ba64a..5f33595c 100644
--- a/Abblix.Oidc.Server/Features/Licensing/AggregationExtensions.cs
+++ b/Abblix.Oidc.Server/Features/Licensing/AggregationExtensions.cs
@@ -31,7 +31,7 @@ public static class AggregationExtensions
/// Determines the greater of two nullable values, treating null as positive infinity.
///
/// The type of the values being compared, constrained to value types that implement
- /// .
+ /// .
/// The first nullable value to compare.
/// The second nullable value to compare.
/// The greater of the two values if at least one is non-null; otherwise, null. If both values are
diff --git a/Abblix.Oidc.Server/Features/Licensing/LicenseChecker.cs b/Abblix.Oidc.Server/Features/Licensing/LicenseChecker.cs
index 8207cdf9..8395a1e4 100644
--- a/Abblix.Oidc.Server/Features/Licensing/LicenseChecker.cs
+++ b/Abblix.Oidc.Server/Features/Licensing/LicenseChecker.cs
@@ -61,7 +61,7 @@ public static class LicenseChecker
/// A task that, upon completion, returns the client information if it complies with the licensing
/// constraints; otherwise, logs an error.
public static async Task WithLicenseCheck(this Task clientInfo)
- => (await clientInfo).CheckClient();
+ => (await clientInfo).CheckClientLicense();
///
/// Applies licensing checks to client information.
@@ -69,7 +69,7 @@ public static class LicenseChecker
/// The client information to check against licensing constraints.
/// The client information if it complies with the licensing constraints; otherwise, logs an error.
///
- public static ClientInfo? CheckClient(this ClientInfo? clientInfo)
+ public static ClientInfo? CheckClientLicense(this ClientInfo? clientInfo)
{
if (clientInfo != null)
{
diff --git a/Abblix.Oidc.Server/Features/RandomGenerators/SessionIdGenerator.cs b/Abblix.Oidc.Server/Features/RandomGenerators/SessionIdGenerator.cs
index 7e793fb5..b5cfabc5 100644
--- a/Abblix.Oidc.Server/Features/RandomGenerators/SessionIdGenerator.cs
+++ b/Abblix.Oidc.Server/Features/RandomGenerators/SessionIdGenerator.cs
@@ -20,7 +20,9 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Common.Configuration;
using Abblix.Utils;
+using Microsoft.Extensions.Options;
namespace Abblix.Oidc.Server.Features.RandomGenerators;
@@ -31,6 +33,19 @@ namespace Abblix.Oidc.Server.Features.RandomGenerators;
///
public class SessionIdGenerator : ISessionIdGenerator
{
+ ///
+ /// Initializes a new instance of the class with configuration options
+ /// for session ID generation, such as the length of the session ID.
+ ///
+ /// The configuration options for OIDC, providing settings such as the session ID length.
+ ///
+ public SessionIdGenerator(IOptions options)
+ {
+ _options = options;
+ }
+
+ private readonly IOptions _options;
+
///
/// Generates a new session identifier. The method employs a cryptographically strong random number generator
/// to produce a sequence of bytes, which are then URL-encoded to ensure they can be safely used within HTTP URLs.
@@ -39,5 +54,6 @@ public class SessionIdGenerator : ISessionIdGenerator
///
/// A string representing a URL-safe, cryptographically strong random session identifier. The identifier
/// is encoded in a way that makes it suitable for use in HTTP URLs, cookies, or any other URL-based contexts.
- public string GenerateSessionId() => HttpServerUtility.UrlTokenEncode(CryptoRandom.GetRandomBytes(32));
+ public string GenerateSessionId()
+ => HttpServerUtility.UrlTokenEncode(CryptoRandom.GetRandomBytes(_options.Value.SessionIdLength));
}
diff --git a/Abblix.Oidc.Server/Features/RandomGenerators/TokenIdGenerator.cs b/Abblix.Oidc.Server/Features/RandomGenerators/TokenIdGenerator.cs
index c9635a35..d3d32242 100644
--- a/Abblix.Oidc.Server/Features/RandomGenerators/TokenIdGenerator.cs
+++ b/Abblix.Oidc.Server/Features/RandomGenerators/TokenIdGenerator.cs
@@ -20,21 +20,37 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Common.Configuration;
using Abblix.Utils;
+using Microsoft.Extensions.Options;
namespace Abblix.Oidc.Server.Features.RandomGenerators;
///
-/// Generates a new identifier for a JSON Web Token (JWT). This class uses a cryptographic-strength random number generator
-/// to create a unique and secure identifier for each token. The generated identifier is then encoded using HTTP URL encoding
-/// to ensure it is safe to transmit in URL contexts.
+/// Generates a new identifier for a JSON Web Token (JWT). This class uses a cryptographic-strength random number
+/// generator to create a unique and secure identifier for each token. The generated identifier is then encoded using
+/// HTTP URL encoding to ensure it is safe to transmit in URL contexts.
///
public class TokenIdGenerator : ITokenIdGenerator
{
+ ///
+ /// Initializes a new instance of the class with configuration options for
+ /// token ID generation, such as the length of the token ID.
+ ///
+ /// The configuration options for OIDC, providing settings such as the token ID length.
+ ///
+ public TokenIdGenerator(IOptions options)
+ {
+ _options = options;
+ }
+
+ private readonly IOptions _options;
+
///
/// Creates a new unique identifier for a JWT. This method generates a 32-byte random number and encodes it using
/// HTTP URL-safe Base64 encoding, resulting in a string suitable for use as a JWT ID.
///
/// A URL-safe, randomly generated unique identifier for a JWT.
- public string GenerateTokenId() => HttpServerUtility.UrlTokenEncode(CryptoRandom.GetRandomBytes(32));
+ public string GenerateTokenId()
+ => HttpServerUtility.UrlTokenEncode(CryptoRandom.GetRandomBytes(_options.Value.TokenIdLength));
}
diff --git a/Abblix.Oidc.Server/Features/RequestObject/IRequestObjectFetcher.cs b/Abblix.Oidc.Server/Features/RequestObject/IRequestObjectFetcher.cs
new file mode 100644
index 00000000..07da2317
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/RequestObject/IRequestObjectFetcher.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
+
+using Abblix.Oidc.Server.Common;
+
+namespace Abblix.Oidc.Server.Features.RequestObject;
+
+///
+/// Defines the interface for fetching and processing JWT request objects, validating their content
+/// and binding their payloads to a request model.
+/// This is typically used in OpenID Connect flows where request parameters are passed as JWTs.
+///
+public interface IRequestObjectFetcher
+{
+ ///
+ /// Fetches and processes the request object by validating its JWT and binding the payload to the request model.
+ ///
+ /// The type of the request model.
+ /// 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 a object,
+ /// which either represents a successfully processed request or an error indicating issues with the JWT validation.
+ ///
+ ///
+ /// This method is responsible for decoding and validating the JWT contained in the request. If the JWT is valid,
+ /// the payload is bound to the request model.
+ /// If the JWT is invalid or not present, an appropriate error result is returned.
+ ///
+ Task> FetchAsync(T request, string? requestObject)
+ where T : class;
+}
diff --git a/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcher.cs b/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcher.cs
new file mode 100644
index 00000000..cc98d163
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/RequestObject/RequestObjectFetcher.cs
@@ -0,0 +1,156 @@
+// 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
+
+using System.Text.Json.Nodes;
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Common.Configuration;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Common.Exceptions;
+using Abblix.Oidc.Server.Common.Interfaces;
+using Abblix.Oidc.Server.Features.Tokens.Validation;
+using Abblix.Utils;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Abblix.Oidc.Server.Features.RequestObject;
+
+///
+/// Provides functionality to validate and process JWT request objects, binding their payloads to a request model.
+/// This class is typically used in OpenID Connect flows where request parameters are passed as JWTs.
+///
+public class RequestObjectFetcher : IRequestObjectFetcher
+{
+ ///
+ /// Initializes a new instance of the class, providing necessary services
+ /// for logging, JSON binding, and client key retrieval.
+ ///
+ /// The logger for recording debug information and warnings.
+ /// The binder for converting JSON payloads into request objects.
+ /// The service provider used for resolving dependencies at runtime.
+ /// Options that define how request object validation is handled, including whether
+ /// request objects must be signed.
+ public RequestObjectFetcher(
+ ILogger logger,
+ IJsonObjectBinder jsonObjectBinder,
+ IServiceProvider serviceProvider,
+ IOptionsSnapshot options)
+ {
+ _logger = logger;
+ _jsonObjectBinder = jsonObjectBinder;
+ _serviceProvider = serviceProvider;
+ _options = options;
+ }
+
+ private readonly ILogger _logger;
+ private readonly IJsonObjectBinder _jsonObjectBinder;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IOptionsSnapshot _options;
+
+ ///
+ /// Fetches and processes the request object by validating its JWT and binding the payload to the request model.
+ ///
+ /// The type of the request model.
+ /// 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
+ /// 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.
+ ///
+ public async Task> FetchAsync(T request, string? requestObject)
+ where T : class
+ {
+ // Return original request if no request object is provided
+ if (!requestObject.HasValue())
+ return request;
+
+ _logger.LogDebug("JWT request object was: {RequestObject}", requestObject);
+
+ var result = await ValidateAsync(requestObject);
+ switch (result)
+ {
+ // If the JWT is valid and contains a JSON payload, bind it to the request
+ case Result.Success(var payload):
+ var updatedRequest = await _jsonObjectBinder.BindModelAsync(payload, request);
+ if (updatedRequest == null)
+ return InvalidRequestObject("Unable to bind request object");
+ return updatedRequest;
+
+ // Handle validation errors
+ case Result.Error(var error, var description):
+ return new Result.Error(error, description);
+
+ // Handle unexpected result
+ default:
+ throw new UnexpectedTypeException(nameof(result), result.GetType());
+ }
+ }
+
+ ///
+ /// Validates the JWT request object to ensure it complies with the required signing algorithm
+ /// and structure, based on the OIDC options.
+ ///
+ /// The JWT request object to be validated.
+ ///
+ /// A task representing the asynchronous operation. The task result contains a
+ /// indicating whether the JWT is valid or contains errors.
+ ///
+ ///
+ /// This method uses the configured OIDC options to determine whether the JWT must be signed and validates
+ /// it accordingly. It retrieves a validator service from the DI container to perform the validation.
+ ///
+ private async Task> ValidateAsync(string requestObject)
+ {
+ // Set validation options, requiring the token to be signed if specified in the OIDC options
+ var options = ValidationOptions.ValidateIssuerSigningKey;
+ if (_options.Value.RequireSignedRequestObject)
+ options |= ValidationOptions.RequireSignedTokens;
+
+ // Use dependency injection to get a JWT validator and perform the validation
+ using var scope = _serviceProvider.CreateScope();
+ var tokenValidator = scope.ServiceProvider.GetRequiredService();
+ var (result, _) = await tokenValidator.ValidateAsync(requestObject, options);
+
+ switch (result)
+ {
+ // If the JWT is valid and contains a JSON payload, bind it to the request
+ case ValidJsonWebToken { Token.Payload.Json: var payload }:
+ return payload;
+
+ // Log warning and return error result if JWT validation failed
+ 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());
+ }
+ }
+
+ private static Result.Error InvalidRequestObject(string description)
+ => new(ErrorCodes.InvalidRequestObject, description);
+}
diff --git a/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs
index 08a1a791..f305dfec 100644
--- a/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs
+++ b/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs
@@ -26,6 +26,8 @@
using Abblix.Oidc.Server.Common.Implementation;
using Abblix.Oidc.Server.Common.Interfaces;
using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
+using Abblix.Oidc.Server.Features.BackChannelAuthentication;
+using Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces;
using Abblix.Oidc.Server.Features.ClientAuthentication;
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Features.Consents;
@@ -34,6 +36,7 @@
using Abblix.Oidc.Server.Features.Licensing;
using Abblix.Oidc.Server.Features.LogoutNotification;
using Abblix.Oidc.Server.Features.RandomGenerators;
+using Abblix.Oidc.Server.Features.RequestObject;
using Abblix.Oidc.Server.Features.ResourceIndicators;
using Abblix.Oidc.Server.Features.ScopeManagement;
using Abblix.Oidc.Server.Features.SessionManagement;
@@ -90,14 +93,12 @@ public static IServiceCollection AddClientInformation(this IServiceCollection se
}
///
- /// Registers common services required by the application, like system clock, hashing services etc.
+ /// Registers common services required by the application, like system clock, hashing services, etc.
///
/// The to add the services to.
/// The with the common services registered.
public static IServiceCollection AddCommonServices(this IServiceCollection services)
{
- services.TryAddSingleton();
-
services.TryAddSingleton();
services.Decorate();
@@ -279,18 +280,22 @@ public static IServiceCollection AddAuthServiceJwt(this IServiceCollection servi
}
///
- /// Registers a service for formatting JWTs specific to client authentication within the specified .
+ /// Registers services for validating and formatting JWTs used in client authentication scenarios within
+ /// the specified .
///
///
- /// Adds a service which provides functionality for formatting JWTs used in client authentication scenarios.
- /// This service ensures that JWTs generated for clients adhere to the required format and contain all necessary
- /// claims for identifying and authenticating users in the client application.
+ /// This method adds services to the that are responsible for validating and
+ /// formatting JWTs used specifically in client authentication.
+ /// These services ensure that JWTs conform to the required standards,
+ /// include all necessary claims, and are properly validated for client authentication processes.
///
- /// The to add the client JWT formatting service to.
+ /// The to add the client JWT validation and
+ /// formatting services to.
/// The for chaining further service registrations.
public static IServiceCollection AddClientJwt(this IServiceCollection services)
{
return services
+ .AddSingleton()
.AddSingleton();
}
@@ -386,4 +391,33 @@ public static IServiceCollection AddUserInfo(this IServiceCollection services)
services.TryAddSingleton();
return services;
}
+
+ ///
+ /// Adds request object fetching capabilities to the dependency injection container.
+ /// Registers services required for processing JWT request objects, including their validation
+ /// and binding to the appropriate request properties.
+ ///
+ /// The to which the user claims provider services will be
+ /// added. This collection is a mechanism for adding and retrieving dependencies in .NET applications, often used
+ /// to configure dependency injection in ASP.NET Core applications.
+ /// The updated after adding the services, allowing for further
+ /// modifications and additions to be chained.
+ public static IServiceCollection AddRequestObject(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ return services;
+ }
+
+ ///
+ /// Configures services for handling back-channel authentication requests, enabling secure server-to-server
+ /// authentication flows.
+ ///
+ /// The to configure.
+ /// The configured .
+ public static IServiceCollection AddBackChannelAuthentication(this IServiceCollection services)
+ {
+ return services
+ .AddSingleton()
+ .AddSingleton();
+ }
}
diff --git a/Abblix.Oidc.Server/Features/Storages/AuthorizationCodeService.cs b/Abblix.Oidc.Server/Features/Storages/AuthorizationCodeService.cs
index 6231bef9..fb15c762 100644
--- a/Abblix.Oidc.Server/Features/Storages/AuthorizationCodeService.cs
+++ b/Abblix.Oidc.Server/Features/Storages/AuthorizationCodeService.cs
@@ -34,7 +34,7 @@ public class AuthorizationCodeService : IAuthorizationCodeService
{
///
/// Initializes a new instance of the class,
- /// configuring it with the necessary components for authorization code generation and storage.
+ /// configuring it with the necessary elements for authorization code generation and storage.
///
/// The generator that creates unique authorization codes.
/// The storage mechanism for persisting and retrieving authorization codes and their
@@ -52,7 +52,7 @@ public AuthorizationCodeService(
///
/// Generates a unique authorization code for a given authorization grant result and client information.
- /// This code is subsequently used by the client to request an access token.
+ /// The client subsequently uses this code to request an access token.
///
/// An object encapsulating the result of the authorization grant, including
/// user authentication session and authorization context details.
@@ -123,5 +123,5 @@ public Task UpdateAuthorizationGrantAsync(
///
/// The authorization code to convert.
/// A string that represents the standardized key for the authorization code.
- private string ToKeyString(string authorizationCode) => $"{nameof(authorizationCode)}:{authorizationCode}";
+ private static string ToKeyString(string authorizationCode) => $"{nameof(authorizationCode)}:{authorizationCode}";
}
diff --git a/Abblix.Oidc.Server/Features/Storages/DistributedCacheStorage.cs b/Abblix.Oidc.Server/Features/Storages/DistributedCacheStorage.cs
index f82f7ac2..b57a987d 100644
--- a/Abblix.Oidc.Server/Features/Storages/DistributedCacheStorage.cs
+++ b/Abblix.Oidc.Server/Features/Storages/DistributedCacheStorage.cs
@@ -27,7 +27,7 @@ namespace Abblix.Oidc.Server.Features.Storages;
///
/// Provides a general-purpose distributed caching mechanism with serialization support,
-/// enabling the storage and retrieval of any type of serialized objects.
+/// enabling the storage and retrieval of serialized objects.
///
public sealed class DistributedCacheStorage : IEntityStorage
{
diff --git a/Abblix.Oidc.Server/Features/Storages/IEntityStorage.cs b/Abblix.Oidc.Server/Features/Storages/IEntityStorage.cs
index 03932480..9d5a57c8 100644
--- a/Abblix.Oidc.Server/Features/Storages/IEntityStorage.cs
+++ b/Abblix.Oidc.Server/Features/Storages/IEntityStorage.cs
@@ -37,7 +37,8 @@ public interface IEntityStorage
/// The entity to be stored in the cache.
/// Configuration options for the cache entry, such as expiration times.
/// An optional cancellation token that can be used to cancel the storage operation.
- /// A task that represents the asynchronous operation, providing awareness of completion or faults.
+ /// A task that represents the asynchronous operation, providing awareness of completion or faults.
+ ///
Task SetAsync(string key, T value, StorageOptions options, CancellationToken? token = null);
///
@@ -45,7 +46,8 @@ public interface IEntityStorage
///
/// The type of the entity to retrieve.
/// The unique cache key associated with the entity to retrieve.
- /// Indicates whether the entity should be removed from the storage after retrieval.
+ /// Indicates whether the entity should be removed from the storage after retrieval.
+ ///
/// An optional cancellation token that can be used to cancel the retrieval operation.
/// A task that returns the retrieved entity, if found, or null if no entity is found.
Task GetAsync(string key, bool removeOnRetrieval, CancellationToken? token = null);
diff --git a/Abblix.Oidc.Server/Features/Tokens/Formatters/AuthServiceJwtFormatter.cs b/Abblix.Oidc.Server/Features/Tokens/Formatters/AuthServiceJwtFormatter.cs
index 13ff3ee5..b5f5f396 100644
--- a/Abblix.Oidc.Server/Features/Tokens/Formatters/AuthServiceJwtFormatter.cs
+++ b/Abblix.Oidc.Server/Features/Tokens/Formatters/AuthServiceJwtFormatter.cs
@@ -27,11 +27,20 @@
namespace Abblix.Oidc.Server.Features.Tokens.Formatters;
///
-/// Provides functionality to format and sign JSON Web Tokens (JWTs) specifically for use within the authentication service.
-/// This class leverages signing and optional encryption to generate JWTs that authenticate and authorize internal service operations.
+/// Provides functionality to format and sign JSON Web Tokens (JWTs) specifically for use within the authentication
+/// service. This class processes tokens issued by the authentication service itself, including access tokens,
+/// refresh tokens and Registration Access Tokens generated during client registration via the dynamic registration API.
+/// It leverages signing and optional encryption to generate JWTs that authenticate and authorize internal service
+/// operations.
///
public class AuthServiceJwtFormatter : IAuthServiceJwtFormatter
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The service responsible for creating and issuing JWTs.
+ /// The provider that supplies cryptographic keys used for signing and
+ /// encrypting JWTs.
public AuthServiceJwtFormatter(
IJsonWebTokenCreator jwtCreator,
IAuthServiceKeysProvider serviceKeysProvider)
@@ -45,14 +54,16 @@ public AuthServiceJwtFormatter(
///
/// Formats and signs a JWT for use by the authentication service, applying the appropriate cryptographic operations
- /// based on the JWT's specified requirements and the available cryptographic keys.
+ /// based on the JWT specified requirements and the available cryptographic keys.
///
/// The JSON Web Token (JWT) to be formatted and signed, potentially also encrypted.
- /// A that represents the asynchronous operation, resulting in the JWT formatted as a string.
+ /// A that represents the asynchronous operation, resulting in the JWT formatted
+ /// as a string.
///
- /// This method selects the appropriate signing key based on the algorithm specified in the JWT's header.
+ /// This method selects the appropriate signing key based on the algorithm specified in the JWT header.
/// If encryption is supported and keys are available, it also encrypts the JWT. The result is a JWT string
- /// that is ready for use in authenticating and authorizing service operations.
+ /// that is ready for use in authenticating and authorizing service operations, including access tokens,
+ /// refresh tokens and Registration Access Tokens.
///
public async Task FormatAsync(JsonWebToken token)
{
@@ -60,7 +71,7 @@ public async Task FormatAsync(JsonWebToken token)
var signingCredentials = await _serviceKeysProvider.GetSigningKeys(true)
.FirstByAlgorithmAsync(token.Header.Algorithm);
- // Optionally select an encryption key if available
+ // Optionally, select an encryption key if available
var encryptingCredentials = await _serviceKeysProvider.GetEncryptionKeys()
.FirstOrDefaultAsync();
diff --git a/Abblix.Oidc.Server/Features/Tokens/Formatters/IAuthServiceJwtFormatter.cs b/Abblix.Oidc.Server/Features/Tokens/Formatters/IAuthServiceJwtFormatter.cs
index cfc1eead..b1bb99e7 100644
--- a/Abblix.Oidc.Server/Features/Tokens/Formatters/IAuthServiceJwtFormatter.cs
+++ b/Abblix.Oidc.Server/Features/Tokens/Formatters/IAuthServiceJwtFormatter.cs
@@ -25,15 +25,18 @@
namespace Abblix.Oidc.Server.Features.Tokens.Formatters;
///
-/// Formats and signs JSON Web Tokens (JWTs) according RFC 7519 for the authentication service itself.
-/// These tokens are utilized in various API endpoints for authentication and authorization purposes.
+/// Defines the interface for formatting and signing JSON Web Tokens (JWTs) within the authentication service.
+/// Implementations of this interface are responsible for processing tokens, such as access tokens, refresh tokens,
+/// and Registration Access Tokens, by applying the necessary cryptographic operations to produce a valid JWT.
///
public interface IAuthServiceJwtFormatter
{
///
- /// Formats a JWT asynchronously for the authentication service itself.
+ /// Formats and signs a JWT for use within the authentication service, applying cryptographic operations such as
+ /// signing and optionally encrypting the token based on the specified requirements.
///
- /// The JWT to format.
- /// A formatted JWT as a string.
+ /// The JSON Web Token (JWT) to be formatted and signed, potentially also encrypted.
+ /// A task representing the asynchronous operation, which results in the JWT formatted as a string.
+ ///
Task FormatAsync(JsonWebToken token);
}
diff --git a/Abblix.Oidc.Server/Features/Tokens/Revocation/TokenStatusValidatorDecorator.cs b/Abblix.Oidc.Server/Features/Tokens/Revocation/TokenStatusValidatorDecorator.cs
index 14f7fe4d..5b306c38 100644
--- a/Abblix.Oidc.Server/Features/Tokens/Revocation/TokenStatusValidatorDecorator.cs
+++ b/Abblix.Oidc.Server/Features/Tokens/Revocation/TokenStatusValidatorDecorator.cs
@@ -44,7 +44,7 @@ public TokenStatusValidatorDecorator(
private readonly ITokenRegistry _tokenRegistry;
private readonly IJsonWebTokenValidator _innerValidator;
- public IEnumerable SigningAlgValuesSupported => _innerValidator.SigningAlgValuesSupported;
+ public IEnumerable SigningAlgorithmsSupported => _innerValidator.SigningAlgorithmsSupported;
///
/// Validates a JSON Web Token (JWT) and checks its revocation status.
diff --git a/Abblix.Oidc.Server/Features/Tokens/Validation/AuthServiceJwtValidator.cs b/Abblix.Oidc.Server/Features/Tokens/Validation/AuthServiceJwtValidator.cs
index 75b7fe92..673f4292 100644
--- a/Abblix.Oidc.Server/Features/Tokens/Validation/AuthServiceJwtValidator.cs
+++ b/Abblix.Oidc.Server/Features/Tokens/Validation/AuthServiceJwtValidator.cs
@@ -29,12 +29,20 @@
namespace Abblix.Oidc.Server.Features.Tokens.Validation;
///
-/// Validates JWTs (JSON Web Tokens) used in the authentication service.
-/// Ensures tokens meet the necessary criteria for issuer, audience and signing keys,
-/// as defined in the OAuth 2.0 and OpenID Connect standards.
+/// Validates JSON Web Tokens (JWTs) issued by the authentication service, ensuring they are authentic and compliant
+/// with the expected issuer, audience, and cryptographic signatures.
///
public class AuthServiceJwtValidator : IAuthServiceJwtValidator
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The service used to perform the core JWT validation.
+ /// The provider used to retrieve information about clients during
+ /// audience validation.
+ /// The provider used to resolve the expected issuer of the JWT.
+ /// The provider used to retrieve the cryptographic keys for signing and
+ /// decrypting tokens.
public AuthServiceJwtValidator(
IJsonWebTokenValidator validator,
IClientInfoProvider clientInfoProvider,
@@ -53,11 +61,14 @@ public AuthServiceJwtValidator(
private readonly IAuthServiceKeysProvider _serviceKeysProvider;
///
- /// Validates a JWT for authenticity and compliance with the expected issuer, audience, and cryptographic signatures.
+ /// Asynchronously validates a JWT, checking its authenticity, issuer, audience, and cryptographic signatures.
///
- /// The JWT to validate.
- /// Validation options to apply. Default is .
- /// A task representing the asynchronous validation operation, which upon completion yields a .
+ /// The JWT string to validate.
+ /// Validation options to apply. Defaults to .
+ ///
+ /// A task representing the asynchronous validation operation, which yields a
+ /// indicating the result of the validation.
+ ///
public Task ValidateAsync(string jwt, ValidationOptions options = ValidationOptions.Default)
{
return _validator.ValidateAsync(
@@ -72,6 +83,11 @@ public Task ValidateAsync(string jwt, ValidationOptions opt
});
}
+ ///
+ /// Validates the issuer of the JWT against the expected issuer.
+ ///
+ /// The issuer value to validate.
+ /// A task that yields true if the issuer is valid, otherwise false.
private Task ValidateIssuerAsync(string issuer)
{
var result = issuer == _issuerProvider.GetIssuer();
@@ -83,6 +99,11 @@ private Task ValidateIssuerAsync(string issuer)
return Task.FromResult(result);
}
+ ///
+ /// Validates the audience of the JWT by checking if it matches any known client information.
+ ///
+ /// A collection of audience values to validate.
+ /// A task that yields true if any of the audience values are valid, otherwise false.
private async Task ValidateAudienceAsync(IEnumerable audiences)
{
foreach (var audience in audiences)
diff --git a/Abblix.Oidc.Server/Features/Tokens/Validation/ClientJwtValidator.cs b/Abblix.Oidc.Server/Features/Tokens/Validation/ClientJwtValidator.cs
new file mode 100644
index 00000000..1e7d9f88
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/Tokens/Validation/ClientJwtValidator.cs
@@ -0,0 +1,177 @@
+// 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
+
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Common.Interfaces;
+using Abblix.Oidc.Server.Features.ClientInformation;
+using Abblix.Oidc.Server.Features.Licensing;
+using Abblix.Utils;
+using Microsoft.Extensions.Logging;
+
+namespace Abblix.Oidc.Server.Features.Tokens.Validation;
+
+///
+/// Validates JWTs issued by clients to the authentication service, supporting scenarios such as private JWT client
+/// authentication and the validation of request objects. This class plays a crucial role in ensuring that
+/// tokens received from clients are legitimate, properly signed, and authorized for use within
+/// the authentication service.
+///
+///
+/// The class is used for validating tokens like client authentication JWTs and request objects in OpenID Connect
+/// flows. It checks the authenticity of the JWT issuer, audience, and cryptographic signatures to ensure that
+/// only valid and authorized clients can interact with the authentication service.
+///
+public class ClientJwtValidator: IClientJwtValidator
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger used for recording validation activities and outcomes.
+ /// Provides information about the current request, including the request URI.
+ ///
+ /// The service used to perform core JWT validation.
+ /// Provides access to client information for validation purposes.
+ /// Provides access to the client's JSON Web Keys (JWKs) for verifying signatures.
+ ///
+ public ClientJwtValidator(
+ ILogger logger,
+ IRequestInfoProvider requestInfoProvider,
+ IJsonWebTokenValidator tokenValidator,
+ IClientInfoProvider clientInfoProvider,
+ IClientKeysProvider clientJwksProvider)
+ {
+ _logger = logger;
+ _clientInfoProvider = clientInfoProvider;
+ _clientJwksProvider = clientJwksProvider;
+ _requestInfoProvider = requestInfoProvider;
+ _tokenValidator = tokenValidator;
+ }
+
+ private readonly ILogger _logger;
+ private readonly IRequestInfoProvider _requestInfoProvider;
+ private readonly IJsonWebTokenValidator _tokenValidator;
+ private readonly IClientInfoProvider _clientInfoProvider;
+ private readonly IClientKeysProvider _clientJwksProvider;
+
+ ///
+ /// Validates the JWT issued by a client, ensuring that it meets the expected criteria for issuer, audience,
+ /// and cryptographic signatures. This method is used in scenarios such as private JWT client authentication
+ /// and request object validation.
+ ///
+ /// The JWT to validate.
+ /// Options to customize the validation process.
+ ///
+ /// A task representing the asynchronous operation. The task result is a tuple containing the validation result
+ /// and the associated client information if the issuer is validated.
+ ///
+ public async Task<(JwtValidationResult, ClientInfo?)> ValidateAsync(
+ string jwt,
+ ValidationOptions options = ValidationOptions.Default)
+ {
+ var result = await _tokenValidator.ValidateAsync(
+ jwt,
+ new ValidationParameters
+ {
+ Options = options,
+ ValidateAudience = ValidateAudience,
+ ValidateIssuer = ValidateIssuer,
+ ResolveIssuerSigningKeys = ResolveIssuerSigningKeys,
+ });
+
+ return (result, ClientInfo);
+ }
+
+ ///
+ /// Holds client information after the issuer has been successfully validated.
+ ///
+ private ClientInfo? ClientInfo { get; set; }
+
+ ///
+ /// Validates the audience by checking if it matches the request URI.
+ ///
+ /// The collection of audiences to validate against the request URI.
+ /// A task that represents the asynchronous operation. The task result indicates whether
+ /// the audience is valid.
+ private Task ValidateAudience(IEnumerable audiences)
+ {
+ var requestUri = _requestInfoProvider.RequestUri;
+ var result = audiences.Contains(requestUri);
+ if (!result)
+ {
+ _logger.LogWarning(
+ "Audience validation failed, token audiences: {@Audiences}, actual requestUri: {RequestUri}",
+ audiences, requestUri);
+ }
+
+ return Task.FromResult(result);
+ }
+
+ ///
+ /// Validates the issuer by attempting to match it with known client information. Ensures that the JWT issuer
+ /// corresponds to an authorized client, and handles scenarios where client information is already known.
+ ///
+ /// The issuer value to validate.
+ ///
+ /// A task that represents the asynchronous operation. The task result indicates whether the issuer is valid
+ /// and corresponds to an authorized client.
+ ///
+ /// Thrown if attempting to validate a different issuer than the one
+ /// associated with the stored client information.
+ private async Task ValidateIssuer(string issuer)
+ {
+ switch (ClientInfo)
+ {
+ // Case where client information is already known and the issuer matches the client ID.
+ case { ClientId: var clientId } when issuer == clientId:
+ return true;
+
+ // Case where client information is already known, but the issuer does not match the known client ID.
+ case { ClientId: var clientId }:
+ throw new InvalidOperationException(
+ $"Trying to validate issuer {issuer}, but already has info about client {clientId}");
+
+ // Case where client information is not yet known; attempt to find the client by issuer.
+ case null:
+ ClientInfo = await _clientInfoProvider.TryFindClientAsync(issuer).WithLicenseCheck();
+
+ // If the client is found but does not use the expected authentication method, validation fails.
+ return ClientInfo != null;
+ }
+ }
+
+ ///
+ /// Asynchronously resolves the signing keys for a validated issuer's JWTs, allowing the authentication service
+ /// to verify the JWT signature.
+ ///
+ /// The issuer URL whose signing keys are to be resolved.
+ /// An asynchronous stream of objects representing the issuer's signing keys.
+ ///
+ private async IAsyncEnumerable ResolveIssuerSigningKeys(string issuer)
+ {
+ if (!await ValidateIssuer(issuer))
+ yield break;
+
+ var clientInfo = ClientInfo.NotNull(nameof(ClientInfo));
+ await foreach (var key in _clientJwksProvider.GetSigningKeys(clientInfo))
+ yield return key;
+ }
+}
diff --git a/Abblix.Oidc.Server/Features/Tokens/Validation/IAuthServiceJwtValidator.cs b/Abblix.Oidc.Server/Features/Tokens/Validation/IAuthServiceJwtValidator.cs
index ba60ecb4..63e88954 100644
--- a/Abblix.Oidc.Server/Features/Tokens/Validation/IAuthServiceJwtValidator.cs
+++ b/Abblix.Oidc.Server/Features/Tokens/Validation/IAuthServiceJwtValidator.cs
@@ -28,9 +28,18 @@ namespace Abblix.Oidc.Server.Features.Tokens.Validation;
/// Validates JWT issued by the OpenID service using specified options.
///
///
-/// Returns parsed object model of JWT in case of success or detailed error otherwise.
+/// Returns parsed model of JWT in case of success or detailed error otherwise.
///
public interface IAuthServiceJwtValidator
{
+ ///
+ /// Asynchronously validates a JSON Web Token (JWT) based on the provided validation options.
+ /// This method ensures that the JWT is correctly formatted, signed, and adheres to the expected claims and audience.
+ ///
+ /// The JWT string to be validated.
+ /// The validation options that control how the JWT is validated, including checks for issuer,
+ /// audience, expiration, and more. Defaults to if not specified.
+ /// A task representing the asynchronous operation, resulting in a
+ /// that indicates whether the JWT is valid or provides details of any validation errors.
public Task ValidateAsync(string jwt, ValidationOptions options = ValidationOptions.Default);
}
diff --git a/Abblix.Oidc.Server/Features/Tokens/Validation/IClientJwtValidator.cs b/Abblix.Oidc.Server/Features/Tokens/Validation/IClientJwtValidator.cs
new file mode 100644
index 00000000..092a0eeb
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/Tokens/Validation/IClientJwtValidator.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
+
+using Abblix.Jwt;
+using Abblix.Oidc.Server.Features.ClientInformation;
+
+namespace Abblix.Oidc.Server.Features.Tokens.Validation;
+
+///
+/// Defines a contract for validating JSON Web Tokens (JWTs) issued by clients, specifically for client authentication.
+///
+///
+/// This interface ensures that JWTs used in client authentication are properly validated according to
+/// the specified options. It also retrieves client information associated with the validated JWT,
+/// which is essential for authorizing client requests. Implementations of this interface should handle JWT validation,
+/// including verifying the token's signature, issuer, audience and other claims.
+///
+public interface IClientJwtValidator
+{
+ ///
+ /// Asynchronously validates a JWT and retrieves associated client information if the validation is successful.
+ ///
+ /// The JWT to validate.
+ /// Optional validation options that define the specific checks and constraints
+ /// to apply during validation. Default is .
+ ///
+ /// A task that represents the asynchronous operation. The task result is a tuple containing the validation result
+ /// and the associated if the JWT is valid; otherwise, it returns null for the client info.
+ ///
+ public Task<(JwtValidationResult, ClientInfo?)> ValidateAsync(
+ string jwt, ValidationOptions options = ValidationOptions.Default);
+}
diff --git a/Abblix.Oidc.Server/Model/AuthorizationRequest.cs b/Abblix.Oidc.Server/Model/AuthorizationRequest.cs
index a3be487f..4f6926f2 100644
--- a/Abblix.Oidc.Server/Model/AuthorizationRequest.cs
+++ b/Abblix.Oidc.Server/Model/AuthorizationRequest.cs
@@ -155,22 +155,23 @@ public record AuthorizationRequest
/// verifier.
///
[JsonPropertyName(Parameters.CodeChallengeMethod)]
- [AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256)]
+ [AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256, CodeChallengeMethods.S512)]
public string? CodeChallengeMethod { get; init; }
///
- /// The request as a JWT whose Claims are the request parameters.
+ /// A JWT (JSON Web Token) that encapsulates the entire authorization request as its payload.
+ /// This parameter is often used to transmit the request securely.
///
[JsonPropertyName(Parameters.Request)]
- public string? Request { get; init; }
+ public string? Request { get; init; }
///
- /// URL referencing a resource containing a Request Object value, which is a JWT containing the request
- /// parameters.
+ /// A URL referencing a resource that contains a Request Object, which is a JWT with the authorization request
+ /// parameters as its claims. This URL must use HTTPS.
///
[JsonPropertyName(Parameters.RequestUri)]
- [AbsoluteUri(RequireScheme = "https")]
- public Uri? RequestUri { get; init; }
+ [AbsoluteUri(RequireScheme = "https")]
+ public Uri? RequestUri { get; init; }
///
/// Specifies the resource for which the access token is requested.
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationError.cs b/Abblix.Oidc.Server/Model/BackChannelAuthenticationError.cs
similarity index 61%
rename from Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationError.cs
rename to Abblix.Oidc.Server/Model/BackChannelAuthenticationError.cs
index 403fc7a4..3b37f6a9 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationError.cs
+++ b/Abblix.Oidc.Server/Model/BackChannelAuthenticationError.cs
@@ -20,10 +20,16 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+namespace Abblix.Oidc.Server.Model;
///
-/// Indicates that a back-channel authentication request failed and describes what was wrong and why.
+/// Represents an error response in the backchannel authentication process.
+/// This record encapsulates details about the error, including an error code and a human-readable
+/// description of what went wrong. It is returned when the authentication request fails due to various
+/// reasons such as invalid parameters, unauthorized access, or other validation issues.
///
+/// The error code that identifies the type of error encountered during the backchannel
+/// authentication process.
+/// A human-readable description providing more details about the error.
public record BackChannelAuthenticationError(string Error, string ErrorDescription)
: BackChannelAuthenticationResponse;
diff --git a/Abblix.Oidc.Server/Model/BackChannelAuthenticationForbidden.cs b/Abblix.Oidc.Server/Model/BackChannelAuthenticationForbidden.cs
new file mode 100644
index 00000000..0f4ba87e
--- /dev/null
+++ b/Abblix.Oidc.Server/Model/BackChannelAuthenticationForbidden.cs
@@ -0,0 +1,12 @@
+namespace Abblix.Oidc.Server.Model;
+
+///
+/// Represents a forbidden response for a backchannel authentication request.
+/// This response typically indicates that the client is authenticated but does not have permission
+/// to perform the requested operation.
+///
+/// The error code that identifies the type of failure.
+///
+/// A human-readable description of the error, providing more details about the failure.
+public record BackChannelAuthenticationForbidden(string Error, string ErrorDescription)
+ : BackChannelAuthenticationError(Error, ErrorDescription);
\ No newline at end of file
diff --git a/Abblix.Oidc.Server/Model/BackChannelAuthenticationRequest.cs b/Abblix.Oidc.Server/Model/BackChannelAuthenticationRequest.cs
index 50ab5de4..ec8347fc 100644
--- a/Abblix.Oidc.Server/Model/BackChannelAuthenticationRequest.cs
+++ b/Abblix.Oidc.Server/Model/BackChannelAuthenticationRequest.cs
@@ -20,10 +20,128 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using System.Text.Json.Serialization;
+using Abblix.Utils.Json;
+
namespace Abblix.Oidc.Server.Model;
///
-/// Represents a back-channel authentication request in scenarios such as OpenID Connect's Client Initiated Backchannel
-/// Authentication (CIBA) flow.
+/// Represents a client-initiated backchannel authentication request, typically used in the CIBA (Client-Initiated
+/// Backchannel Authentication) flow as part of OpenID Connect. This request allows a client to request user
+/// authentication through a backchannel communication, which involves the authorization server interacting with
+/// the user asynchronously.
///
-public record BackChannelAuthenticationRequest;
+public record BackChannelAuthenticationRequest
+{
+ ///
+ /// A space-separated list of scopes requested by the client. Scopes define the level of access requested by
+ /// the client and the types of information that the client wants to retrieve from the user's account.
+ ///
+ [JsonPropertyName(Parameters.Scope)]
+ [JsonConverter(typeof(SpaceSeparatedValuesConverter))]
+ public string[] Scope { get; init; } = Array.Empty();
+
+ ///
+ /// A token issued by the client that the authorization server uses to notify the client about the result
+ /// of the authentication request. This token allows the authorization server to securely deliver
+ /// the authentication result to the client.
+ ///
+ [JsonPropertyName(Parameters.ClientNotificationToken)]
+ public string? ClientNotificationToken { get; init; }
+
+ ///
+ /// A list of requested Authentication Context Class References (ACRs) that the client wishes to be used
+ /// for authentication. ACR values indicate the level of authentication strength required,
+ /// such as multifactor authentication or biometric verification.
+ ///
+ [JsonPropertyName(Parameters.AcrValues)]
+ public List? AcrValues { get; init; }
+
+ ///
+ /// A token used to pass a hint about the login identifier to the authorization server.
+ /// This token is typically used to identify the user for the authentication process.
+ ///
+ [JsonPropertyName(Parameters.LoginHintToken)]
+ public string? LoginHintToken { get; init; }
+
+ ///
+ /// An ID token previously issued to the client, used as a hint to identify the user for authentication.
+ /// The ID token hint can be used by the authorization server to verify the user's identity without
+ /// requiring re-authentication.
+ ///
+ [JsonPropertyName(Parameters.IdTokenHint)]
+ public string? IdTokenHint { get; init; }
+
+ ///
+ /// A hint about the user's login identifier (such as email or username), used by the authorization server
+ /// to identify the user for authentication. This can help streamline the authentication process
+ /// by pre-filling the user's information.
+ ///
+ [JsonPropertyName(Parameters.LoginHint)]
+ public string? LoginHint { get; init; }
+
+ ///
+ /// A human-readable message intended to be shown to the user, providing context or instructions for
+ /// the authentication process.
+ /// This message is often used to help the user understand the purpose of the authentication request.
+ ///
+ [JsonPropertyName(Parameters.BindingMessage)]
+ public string? BindingMessage { get; init; }
+
+ ///
+ /// A user code provided by the user, typically as a reference for the authentication request.
+ /// This code is often used in scenarios where the user is identified by a code that they provide to the client.
+ ///
+ [JsonPropertyName(Parameters.UserCode)]
+ public string? UserCode { get; init; }
+
+ ///
+ /// An optional parameter that specifies the requested expiry time for the authentication request.
+ /// This defines how long the authentication request remains valid before it expires.
+ ///
+ [JsonPropertyName(Parameters.RequestedExpiry)]
+ [JsonConverter(typeof(TimeSpanSecondsConverter))]
+ public TimeSpan? RequestedExpiry { get; init; }
+
+ ///
+ /// An optional parameter that contains a signed JWT (JSON Web Token) which encodes the entire authentication request.
+ /// This JWT can be used to pass the authentication request parameters in a compact and secure manner.
+ /// It allows the client to include all necessary request information without relying on query parameters or other
+ /// HTTP mechanisms. The request parameter can also be used to sign and optionally encrypt the entire request,
+ /// providing additional security for sensitive data.
+ ///
+ [JsonPropertyName(Parameters.Request)]
+ public string? Request { get; init; }
+
+ ///
+ /// Specifies the resource for which the access token is requested.
+ /// As defined in RFC 8707, this parameter is used to request access tokens with a specific scope for a particular
+ /// resource.
+ ///
+ [JsonPropertyName(Parameters.Resource)]
+ [JsonConverter(typeof(SingleOrArrayConverter))]
+ public Uri[]? Resources { get; set; }
+
+ ///
+ /// The detailed request for specific claims (user attributes) to be included in the ID token or
+ /// returned from the UserInfo endpoint.
+ ///
+ [JsonPropertyName(Parameters.Claims)]
+ public RequestedClaims? Claims { get; init; }
+
+ public static class Parameters
+ {
+ public const string Scope = "scope";
+ public const string ClientNotificationToken = "client_notification_token";
+ public const string AcrValues = "acr_values";
+ public const string LoginHintToken = "login_hint_token";
+ public const string IdTokenHint = "id_token_hint";
+ public const string LoginHint = "login_hint";
+ public const string BindingMessage = "binding_message";
+ public const string UserCode = "user_code";
+ public const string RequestedExpiry = "requested_expiry";
+ public const string Request = "request";
+ public const string Resource = "resource";
+ public const string Claims = "claims";
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationResponse.cs b/Abblix.Oidc.Server/Model/BackChannelAuthenticationResponse.cs
similarity index 70%
rename from Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationResponse.cs
rename to Abblix.Oidc.Server/Model/BackChannelAuthenticationResponse.cs
index 75d0a136..a99cb093 100644
--- a/Abblix.Oidc.Server/Endpoints/BackChannelAuthentication/Interfaces/BackChannelAuthenticationResponse.cs
+++ b/Abblix.Oidc.Server/Model/BackChannelAuthenticationResponse.cs
@@ -20,6 +20,12 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-namespace Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces;
+namespace Abblix.Oidc.Server.Model;
+///
+/// Serves as the base class for all types of backchannel authentication responses.
+/// This abstract record represents the outcome of processing a backchannel authentication request,
+/// which can either be a success or an error. Subclasses of this record should define specific
+/// outcomes such as successful authentication or error conditions.
+///
public abstract record BackChannelAuthenticationResponse;
diff --git a/Abblix.Oidc.Server/Model/BackChannelAuthenticationSuccess.cs b/Abblix.Oidc.Server/Model/BackChannelAuthenticationSuccess.cs
new file mode 100644
index 00000000..2ad2e0d2
--- /dev/null
+++ b/Abblix.Oidc.Server/Model/BackChannelAuthenticationSuccess.cs
@@ -0,0 +1,69 @@
+// 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
+
+using System.Text.Json.Serialization;
+using Abblix.Utils.Json;
+
+namespace Abblix.Oidc.Server.Model;
+
+///
+/// Represents a successful backchannel authentication response. This record indicates that the
+/// backchannel authentication request has been successfully processed and the client has been authenticated.
+///
+public record BackChannelAuthenticationSuccess : BackChannelAuthenticationResponse
+{
+ ///
+ /// The unique identifier of the authentication request. The client uses this ID
+ /// to poll the authorization server and check the status of the user's authentication.
+ ///
+ [JsonPropertyName(Parameters.AuthenticationRequestId)]
+ public string AuthenticationRequestId { get; set; } = default!;
+
+ ///
+ /// Specifies the time period (in seconds) after which the backchannel authentication request expires.
+ /// After this duration, the authentication request is no longer valid, and the client will need
+ /// to initiate a new request if authentication has not been completed.
+ ///
+ [JsonPropertyName(Parameters.ExpiresIn)]
+ [JsonConverter(typeof(TimeSpanSecondsConverter))]
+ public TimeSpan ExpiresIn { get; set; }
+
+ ///
+ /// The interval (in seconds) that the client should use when polling the authorization server
+ /// to check the status of the authentication. This prevents excessive polling and ensures
+ /// that the client follows the recommended polling rate.
+ ///
+ [JsonPropertyName(Parameters.Interval)]
+ [JsonConverter(typeof(TimeSpanSecondsConverter))]
+ public TimeSpan Interval { get; set; }
+
+ ///
+ /// Contains constants representing the parameter names used in the backchannel authentication response.
+ /// These are included in the JSON response to the client to ensure the correct values are returned.
+ ///
+ public static class Parameters
+ {
+ public const string AuthenticationRequestId = "auth_req_id";
+ public const string ExpiresIn = "expires_in";
+ public const string Interval = "interval";
+ }
+}
diff --git a/Abblix.Oidc.Server/Model/BackChannelAuthenticationUnauthorized.cs b/Abblix.Oidc.Server/Model/BackChannelAuthenticationUnauthorized.cs
new file mode 100644
index 00000000..1166859b
--- /dev/null
+++ b/Abblix.Oidc.Server/Model/BackChannelAuthenticationUnauthorized.cs
@@ -0,0 +1,12 @@
+namespace Abblix.Oidc.Server.Model;
+
+///
+/// Represents an unauthorized response for a backchannel authentication request.
+/// This response typically indicates that the request failed due to invalid client credentials
+/// or other authorization-related issues.
+///
+/// The error code that identifies the type of failure.
+///
+/// A human-readable description of the error, providing more details about the failure.
+public record BackChannelAuthenticationUnauthorized(string Error, string ErrorDescription)
+ : BackChannelAuthenticationError(Error, ErrorDescription);
\ No newline at end of file
diff --git a/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs b/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs
index 0d848cd2..c5200320 100644
--- a/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs
+++ b/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs
@@ -20,7 +20,6 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Abblix.Jwt;
using Abblix.Oidc.Server.Common.Constants;
@@ -38,10 +37,9 @@ 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]
[ElementsRequired]
public Uri[] RedirectUris { get; set; } = default!;
@@ -63,7 +61,8 @@ public record ClientRegistrationRequest
[AllowedValues(
Common.Constants.GrantTypes.AuthorizationCode,
Common.Constants.GrantTypes.Implicit,
- Common.Constants.GrantTypes.RefreshToken)]
+ Common.Constants.GrantTypes.RefreshToken,
+ Common.Constants.GrantTypes.Ciba)]
public string[] GrantTypes { get; init; } = { Common.Constants.GrantTypes.AuthorizationCode };
///
@@ -73,7 +72,7 @@ public record ClientRegistrationRequest
public string ApplicationType { get; init; } = ApplicationTypes.Web;
///
- /// Array of e-mail addresses of people responsible for this client.
+ /// E-mail addresses of people responsible for this client.
///
[JsonPropertyName(Parameters.Contacts)]
public string[]? Contacts { get; init; }
@@ -98,7 +97,7 @@ public record ClientRegistrationRequest
public Uri? LogoUri { get; init; }
///
- /// URL of the home page of the client.
+ /// URL that points to the home page of the client.
///
[JsonPropertyName(Parameters.ClientUri)]
[AbsoluteUri]
@@ -148,7 +147,6 @@ public record ClientRegistrationRequest
/// JWS algorithm for the ID Token issued to this client.
///
[JsonPropertyName(Parameters.IdTokenSignedResponseAlg)]
- [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? IdTokenSignedResponseAlg { get; init; }
///
@@ -167,7 +165,6 @@ public record ClientRegistrationRequest
/// JWS algorithm for UserInfo Responses.
///
[JsonPropertyName(Parameters.UserInfoSignedResponseAlg)]
- [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? UserInfoSignedResponseAlg { get; init; }
///
@@ -204,18 +201,12 @@ public record ClientRegistrationRequest
/// Requested Authentication Method Reference values for this client.
///
[JsonPropertyName(Parameters.TokenEndpointAuthMethod)]
- [AllowedValues(
- ClientAuthenticationMethods.ClientSecretBasic,
- ClientAuthenticationMethods.ClientSecretPost,
- ClientAuthenticationMethods.PrivateKeyJwt,
- ClientAuthenticationMethods.None)]
public string TokenEndpointAuthMethod { get; init; } = ClientAuthenticationMethods.ClientSecretBasic;
///
/// JWS algorithm that MUST be used for Private Key JWT Client Authentication at the Token Endpoint.
///
[JsonPropertyName(Parameters.TokenEndpointAuthSigningAlg)]
- [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? TokenEndpointAuthSigningAlg { get; init; }
///
@@ -312,17 +303,52 @@ 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)]
public bool? FrontChannelLogoutSessionRequired { get; set; } = false;
+ ///
+ /// Array of URIs to which the OP will redirect the user's user agent after logging out.
+ /// These URIs are used to continue the user's browsing session after logout.
+ ///
[JsonPropertyName(Parameters.PostLogoutRedirectUris)]
[ElementsRequired]
public Uri[] PostLogoutRedirectUris { get; set; } = Array.Empty();
+ ///
+ /// The backchannel token delivery mode to be used by this client. This determines how tokens are delivered
+ /// during backchannel authentication.
+ ///
+ [JsonPropertyName(Parameters.BackChannelTokenDeliveryMode)]
+ [AllowedValues(
+ BackchannelTokenDeliveryModes.Ping,
+ BackchannelTokenDeliveryModes.Poll,
+ BackchannelTokenDeliveryModes.Push)]
+ public string? BackChannelTokenDeliveryMode { get; set; }
+
+ ///
+ /// The endpoint where backchannel client notifications are sent for this client.
+ ///
+ [JsonPropertyName(Parameters.BackChannelClientNotificationEndpoint)]
+ [AbsoluteUri]
+ public Uri? BackChannelClientNotificationEndpoint { get; set; }
+
+ ///
+ /// The signing algorithm used for backchannel authentication requests sent to this client.
+ ///
+ [JsonPropertyName(Parameters.BackChannelAuthenticationRequestSigningAlg)]
+ public string? BackChannelAuthenticationRequestSigningAlg { get; set; }
+
+ ///
+ /// Indicates whether the backchannel authentication process supports user codes for this client.
+ ///
+ [JsonPropertyName(Parameters.BackChannelUserCodeParameter)]
+ public bool BackChannelUserCodeParameter { get; set; } = false;
+
public static class Parameters
{
public const string RedirectUris = "redirect_uris";
@@ -363,5 +389,9 @@ public static class Parameters
public const string FrontChannelLogoutUri = "frontchannel_logout_uri";
public const string FrontChannelLogoutSessionRequired = "frontchannel_logout_session_required";
public const string PostLogoutRedirectUris = "post_logout_redirect_uris";
+ public const string BackChannelTokenDeliveryMode = "backchannel_token_delivery_mode";
+ public const string BackChannelClientNotificationEndpoint = "backchannel_client_notification_endpoint";
+ public const string BackChannelAuthenticationRequestSigningAlg = "backchannel_authentication_request_signing_alg";
+ public const string BackChannelUserCodeParameter = "backchannel_user_code_parameter";
}
}
diff --git a/Abblix.Oidc.Server/Model/ConfigurationResponse.cs b/Abblix.Oidc.Server/Model/ConfigurationResponse.cs
index 1e278db1..b81b3a18 100644
--- a/Abblix.Oidc.Server/Model/ConfigurationResponse.cs
+++ b/Abblix.Oidc.Server/Model/ConfigurationResponse.cs
@@ -19,6 +19,27 @@
//
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+// 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
using System.Text.Json.Serialization;
@@ -33,7 +54,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
{
@@ -58,6 +81,7 @@ public static class Parameters
public const string ResponseTypesSupported = "response_types_supported";
public const string ResponseModesSupported = "response_modes_supported";
public const string TokenEndpointAuthMethodsSupported = "token_endpoint_auth_methods_supported";
+ public const string TokenEndpointAuthSigningAlgValuesSupported = "token_endpoint_auth_signing_alg_values_supported";
public const string IdTokenSigningAlgValuesSupported = "id_token_signing_alg_values_supported";
public const string SubjectTypesSupported = "subject_types_supported";
public const string CodeChallengeMethodsSupported = "code_challenge_methods_supported";
@@ -68,6 +92,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 +161,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; }
@@ -142,7 +171,7 @@ public static class Parameters
/// Indicates whether the provider requires clients to use the Pushed Authorization Requests (PAR) only.
///
[JsonPropertyName(Parameters.RequirePushedAuthorizationRequests)]
- public bool RequirePushedAuthorizationRequests { get; set; } //TODO use it!
+ public bool? RequirePushedAuthorizationRequests { get; set; }
///
/// Indicates whether the OpenID Provider supports front channel logout, allowing clients to log out users
@@ -220,6 +249,14 @@ public static class Parameters
[JsonPropertyName(Parameters.TokenEndpointAuthMethodsSupported)]
public IEnumerable TokenEndpointAuthMethodsSupported { init; get; } = default!;
+ ///
+ /// Lists the signing algorithms supported by the OpenID Provider for authenticating clients at the token endpoint.
+ /// These algorithms are used to verify the signatures of the authentication requests sent to the token endpoint,
+ /// ensuring the integrity and authenticity of the requests.
+ ///
+ [JsonPropertyName(Parameters.TokenEndpointAuthSigningAlgValuesSupported)]
+ public IEnumerable? TokenEndpointAuthSigningAlgValuesSupported { get; init; }
+
///
/// Lists the ID token signing algorithm values supported by the OpenID Provider,
/// indicating the algorithms that can be used to sign the ID token.
@@ -246,7 +283,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 +298,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 +306,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.
@@ -277,5 +314,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; }
+
+ ///
+ /// 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.
diff --git a/Abblix.Oidc.Server/Model/TokenRequest.cs b/Abblix.Oidc.Server/Model/TokenRequest.cs
index 44b0dda2..5a2bd477 100644
--- a/Abblix.Oidc.Server/Model/TokenRequest.cs
+++ b/Abblix.Oidc.Server/Model/TokenRequest.cs
@@ -28,8 +28,10 @@
namespace Abblix.Oidc.Server.Model;
///
-/// Represents a request to get various types of tokens
-/// (e.g., access token, refresh token) from the authorization server.
+/// Represents a request to get various types of tokens (e.g., access token, refresh token) from
+/// the authorization server. This is part of the OAuth 2.0 and OpenID Connect token exchange flow,
+/// where clients can request tokens based on different grant types like 'authorization_code', 'refresh_token'
+/// and others.
///
public record TokenRequest
{
@@ -44,10 +46,11 @@ public static class Parameters
public const string Username = "username";
public const string Password = "password";
public const string CodeVerifier = "code_verifier";
+ public const string AuthenticationRequestId = "auth_req_id";
}
///
- /// The grant type of the token request, indicating the method being used to obtain the token.
+ /// The grant type of the token request, indicating the method being used to get the token.
/// Common values include 'authorization_code', 'refresh_token', 'password', etc.
///
[JsonPropertyName(Parameters.GrantType)]
@@ -62,57 +65,67 @@ public static class Parameters
public string GrantType { get; set; } = default!;
///
- /// The authorization code received from the authorization server. This is used in the authorization code grant type.
+ /// The authorization code received from the authorization server.
+ /// This is used in the authorization code grant type to exchange for an access token.
///
[JsonPropertyName(Parameters.Code)]
public string? Code { get; set; }
///
- /// The redirect URI where the response will be sent. This must match the redirect URI registered with the authorization server.
+ /// The redirect URI where the response will be sent.
+ /// This must match the redirect URI registered with the authorization server during the initial request.
///
[JsonPropertyName(Parameters.RedirectUri)]
public Uri? RedirectUri { get; set; }
///
- /// The resource for which the access token is being requested.
- /// This is optional and is used in scenarios such as OAuth 2.0 for APIs.
+ /// The resource URI(s) for which the access token is being requested.
+ /// This parameter is optional and used in scenarios such as OAuth 2.0 for APIs
+ /// to specify the resource(s) being accessed.
///
///
- /// Defined in RFC 8707.
+ /// Defined in RFC 8707 as a way to express the resource(s) the client is requesting access to.
///
[JsonPropertyName(Parameters.Resource)]
[JsonConverter(typeof(SingleOrArrayConverter))]
public Uri[]? Resources { get; set; }
///
- /// The refresh token used to obtain a new access token. Required for the refresh token grant type.
+ /// The refresh token used to get a new access token. Required when using the refresh token grant type.
///
[JsonPropertyName(Parameters.RefreshToken)]
public string? RefreshToken { get; set; }
///
- /// The scope of the access request, expressed as a list of space-delimited, case-sensitive strings.
+ /// The scope of the access request, expressed as a space-separated list of case-sensitive strings.
+ /// This defines the permissions or resources the client is requesting access to.
///
[JsonPropertyName(Parameters.Scope)]
- [AllowedValues(Scopes.OpenId, Scopes.Profile, Scopes.Email, Scopes.Phone, Scopes.OfflineAccess)]
public string[] Scope { get; set; } = Array.Empty();
///
- /// The username of the resource owner. Required for the password grant type.
+ /// The username of the resource owner, required when using the resource owner password credentials grant type.
///
[JsonPropertyName(Parameters.Username)]
public string? UserName { get; set; }
///
- /// The password of the resource owner. Required for the password grant type.
+ /// The password of the resource owner, required when using the resource owner password credentials grant type.
///
[JsonPropertyName(Parameters.Password)]
public string? Password { get; set; }
///
- /// The code verifier for the PKCE (Proof Key for Code Exchange) process.
- /// Required for public clients using the authorization code grant type.
+ /// The code verifier used in the PKCE (Proof Key for Code Exchange) process.
+ /// Required for public clients using the authorization code grant type to enhance security.
///
[JsonPropertyName(Parameters.CodeVerifier)]
public string? CodeVerifier { get; set; }
+
+ ///
+ /// The authentication request ID, used in CIBA (Client-Initiated Backchannel Authentication) flow.
+ /// This identifier references a backchannel authentication request initiated by the client.
+ ///
+ [JsonPropertyName(Parameters.AuthenticationRequestId)]
+ public string? AuthenticationRequestId { get; set; }
}
diff --git a/Abblix.Oidc.Server/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/ServiceCollectionExtensions.cs
index 3f0dda44..7724bfdf 100644
--- a/Abblix.Oidc.Server/ServiceCollectionExtensions.cs
+++ b/Abblix.Oidc.Server/ServiceCollectionExtensions.cs
@@ -111,7 +111,9 @@ public static IServiceCollection AddFeatures(this IServiceCollection services)
.AddRandomGenerators()
.AddLogoutNotification()
.AddStorages()
- .AddUserInfo();
+ .AddUserInfo()
+ .AddRequestObject()
+ .AddBackChannelAuthentication();
}
///
diff --git a/Abblix.Utils.UnitTests/Abblix.Utils.UnitTests.csproj b/Abblix.Utils.UnitTests/Abblix.Utils.UnitTests.csproj
index 85586a4d..a6341817 100644
--- a/Abblix.Utils.UnitTests/Abblix.Utils.UnitTests.csproj
+++ b/Abblix.Utils.UnitTests/Abblix.Utils.UnitTests.csproj
@@ -10,8 +10,8 @@
-
-
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/Abblix.Utils/Abblix.Utils.csproj b/Abblix.Utils/Abblix.Utils.csproj
index e85a0f2a..9c75f97a 100644
--- a/Abblix.Utils/Abblix.Utils.csproj
+++ b/Abblix.Utils/Abblix.Utils.csproj
@@ -20,8 +20,8 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
- 1.1.0.0
- 1.1.0.0
+ 1.2.0.0
+ 1.2.0.0
diff --git a/Abblix.Utils/CertificateId.cs b/Abblix.Utils/CertificateId.cs
index 72072273..5af82679 100644
--- a/Abblix.Utils/CertificateId.cs
+++ b/Abblix.Utils/CertificateId.cs
@@ -26,6 +26,7 @@ namespace Abblix.Utils;
/// Represents the identifiers for a certificate, including paths to certificate and key files, and an optional password.
///
/// Path to the certificate file in PEM format.
-/// Optional path to the key file in PEM format. If not provided, assumes the certificate file contains the key.
+/// Optional path to the key file in PEM format.
+/// If not provided, assume the certificate file contains the key.
/// Optional password for the key file. Required if the key file is encrypted.
public record CertificateId(string CertPemFilePath, string? KeyPemFilePath = null, string? Password = null);
diff --git a/Abblix.Utils/Sanitized.cs b/Abblix.Utils/Sanitized.cs
index 0961dfd9..b821efd4 100644
--- a/Abblix.Utils/Sanitized.cs
+++ b/Abblix.Utils/Sanitized.cs
@@ -86,7 +86,7 @@ public override string ToString()
return builder != null ? builder.ToString() : source;
}
- private void ReplaceTo(ref StringBuilder? builder, string source, int i, string? replacement)
+ private static void ReplaceTo(ref StringBuilder? builder, string source, int i, string? replacement)
{
builder ??= new StringBuilder(source, 0, i, source.Length + (replacement?.Length ?? 0) - 1);
builder.Append(replacement);