Перейти к содержимому
Эта страница ещё не переведена.

IUserDeviceAuthenticationHandler Interface

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.

C#
public interface IUserDeviceAuthenticationHandler

Remarks

Implementation Guide:

Implement this interface to integrate your authentication mechanism with CIBA. Your implementation should:

  • Send authentication request to user's device (push notification, SMS, email, etc.)
  • Display binding_message if present in the request
  • Handle user approval/denial asynchronously
  • Update authentication status when user responds

Example Implementation with Ping Mode Support:

C#
public class MyUserDeviceAuthenticationHandler : IUserDeviceAuthenticationHandler
{
    private readonly IBackChannelRequestStorage _storage;
    private readonly IAuthenticationCompletionHandler _completion;
    private readonly ISessionIdGenerator _sessionIdGenerator;
    private readonly IMyPushNotificationService _pushService;
    private readonly IBackChannelLongPollingService? _longPolling;

    public async Task<Result<AuthSession, OidcError>> InitiateAuthenticationAsync(
        ValidBackChannelAuthenticationRequest request)
    {
        // Extract user hint and send authentication request to their device
        var userIdentifier = ExtractUserIdentifier(request);
        var bindingMessage = request.Model.BindingMessage;

        // Send push notification to user's device
        await _pushService.SendAuthRequestAsync(userIdentifier, bindingMessage);

        // Return pending - authentication completes asynchronously
        // User will approve/deny on their device
        return new OidcError(ErrorCodes.AuthorizationPending, "Waiting for user approval");
    }

    // Called when user approves on their device
    public async Task OnUserApprovedAsync(string authReqId, string userId)
    {
        // Retrieve the stored authentication request
        var storedRequest = await _storage.TryGetAsync(authReqId);
        if (storedRequest == null) return;

        // Create authenticated session
        var authSession = new AuthSession(
            userId,
            SessionId: _sessionIdGenerator.GenerateSessionId(),
            AuthenticationTime: DateTimeOffset.UtcNow,
            IdentityProvider: "local");

        // Carry the end user's answer on the grant. AuthorizedGrant is a positional member of the
        // record, so it is init-only and a `with` expression is how it is replaced; the copy carries
        // every other member unchanged.
        //
        // Nothing needs to touch Status ON THIS PATH - the denial below is a different one, and it
        // never calls CompleteAsync. A host that does set it on its own copy changes nothing
        // either way: completion reads the STORED record and writes whatever it decides itself.
        var authenticated = storedRequest with
        {
            AuthorizedGrant = new AuthorizedGrant(authSession, storedRequest.AuthorizedGrant.Context),
        };

        // Completion selects the mode-specific handler from the client's registered delivery mode
        // (PollModeCompletionHandler, PingModeCompletionHandler or PushModeCompletionHandler).
        await _completion.CompleteAsync(
            authReqId,
            authenticated,
            TimeSpan.FromMinutes(5));
    }

    // Called when user denies on their device
    public async Task OnUserDeniedAsync(string authReqId)
    {
        var storedRequest = await _storage.TryGetAsync(authReqId);
        if (storedRequest == null) return;

        storedRequest.Status = BackChannelAuthenticationStatus.Denied;
        await _storage.UpdateAsync(authReqId, storedRequest, TimeSpan.FromMinutes(5));

        // Writing the status is not telling anybody. A poll-mode client waiting on a long poll is
        // woken by IBackChannelLongPollingService, which nothing in the library calls for a status
        // the host wrote itself - so without this the user's refusal answers only when the waiter's
        // window runs out. Inject the notifier where the deployment registered one.
        if (_longPolling != null)
            await _longPolling.NotifyStatusChangeAsync(authReqId, storedRequest.Status);
    }
}

Token Delivery Modes:

The CompleteAsync(string, BackChannelAuthenticationRequest, TimeSpan) method automatically handles mode-specific behavior based on the client's registered backchannel_token_delivery_mode:

  • Poll Mode: Stores the authenticated request in IBackChannelRequestStorage. No token exists yet - the client polls the token endpoint with its auth_req_id, and the tokens are minted there when it redeems.
  • Ping Mode: Stores the authenticated request as poll mode does, then sends an HTTP POST notification via INotificationDeliveryService to the client's client_notification_endpoint carrying the auth_req_id. The tokens are minted at the token endpoint when the client redeems, exactly as in poll mode.
  • Push Mode: Generates tokens via ITokenRequestProcessor and delivers them directly via INotificationDeliveryService to the client's client_notification_endpoint. This is the only mode where the tokens exist before the client asks for them, and the request is removed once they are delivered, because a push client never comes to the token endpoint. CIBA Core 1.0 does not require that removal - it says nothing about what the OP keeps - so it is this library's choice.

Partial consent (RFC 9396 authorization_details):

The grant carried on the stored request is what will be issued, so an end user who approved part of what the client asked for is expressed by replacing its AuthorizationContext before completing: keep the entries they agreed to, drop the ones they refused, and hand the result to CompleteAsync(string, BackChannelAuthenticationRequest, TimeSpan). The example above copies the context unchanged, which is the "they agreed to all of it" case.

This is the only moment such an answer exists. InitiateAuthenticationAsync(ValidBackChannelAuthenticationRequest) runs before the end user has seen anything - the session it returns names who is about to be reached, and the request is stored pending either way - so there is nothing to narrow there.

Narrowing is yours to decide; widening is refused. Completion compares the grant's authorization_details types against what the client actually sent, and a type the request never carried denies the request rather than issuing it. RFC 9396 §7 has the server return what was granted, which is only meaningful while "granted" stays inside "requested".

Additional Key Points:

  • Binding Message: Display request.Model.BindingMessage to user for transaction confirmation
  • User Code: If request.Model.UserCode is present, require user to confirm it
  • Authentication: All notifications use Bearer token from client_notification_token

Security contract - user_code verification (CIBA Core 1.0 §7.1):

The library validates only the presence of user_code when the provider and client require it (see UserCodeValidator); it deliberately does not - and cannot - verify the code's value, because the secret is known only to the end-user and the user's authentication device, which this handler owns. Your implementation therefore MUST verify request.Model.UserCode against the user's actual code as part of the device interaction, and MUST NOT return a successful AuthSession unless that check passed. A wrong or absent code MUST resolve to a failed Result<TSuccess,TFailure> (typically access_denied). Treating presence-validation as sufficient leaves the code unenforced and defeats its purpose.

Methods

IUserDeviceAuthenticationHandler.InitiateAuthenticationAsync(ValidBackChannelAuthenticationRequest) Method

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.

C#
System.Threading.Tasks.Task<Abblix.Utils.Result<Abblix.Oidc.Server.Features.UserAuthentication.AuthSession,Abblix.Oidc.Server.Common.OidcError>> InitiateAuthenticationAsync(Abblix.Oidc.Server.Endpoints.BackChannelAuthentication.Interfaces.ValidBackChannelAuthenticationRequest request);

Parameters

request ValidBackChannelAuthenticationRequest

The validated backchannel authentication request containing user and client information required to initiate the authentication process.

Returns

System.Threading.Tasks.Task<Abblix.Utils.Result<AuthSession,OidcError>>
A System.Threading.Tasks.Task representing the asynchronous operation to initiate the authentication process.