Type to search pages and rules.

Browse pages

LS2012: Handler Authorizes Itself

LS2012 is a compiler check that reports a command or query handler, the type that runs a use case, authorizing itself instead of leaving authorization to the pipeline guard. A handler authorizes itself when it throws an access refusal or when it takes the authorization guard. This page states what the rule reports, where the error sits, and how to move the check into the declared access.

Markdown

Reference#

What the Rule Enforces#

LS2012 enforces Enforce target authorization in the pipeline (litespec/backend/application#enforce-target-authorization-in-the-pipeline). It runs in Application, Infrastructure, and Worker. Its category is LiteSpec.Layers. Severity and enablement follow the Analyzers overview.

What the Rule Reports#

The rule reports a command or query handler, which is a class whose name ends in CommandHandler or QueryHandler, or that implements ICommandHandler or IQueryHandler, in either of two cases. The first case is a handler that throws an exception type whose name ends in ForbiddenException, UnauthorizedException, or AccessDeniedException. For a throw new the diagnostic sits on the created exception type; for any other thrown value it sits on that value. The second case is a handler that takes a constructor parameter whose type is the authorization guard: a type whose name ends in AuthorizationGuard, or a type that derives from or implements such a type. The diagnostic sits on the parameter type. In either case its message names the handler and the exception or guard type.

Failing Example#

C#
internal sealed class RenewLoanCommandHandler
{
    public void Handle(RenewLoanCommand command)
    {
        throw new LoanForbiddenException();
    }
}
C#
internal sealed class RenewLoanCommandHandler(IAuthorizationGuard authorizationGuard)
{
}

Passing Example#

C#
internal sealed class RenewLoanCommandHandler(ILoanRepository loanRepository)
{
    public void Handle(RenewLoanCommand command)
    {
        var loan = loanRepository.GetById(command.LoanId);
        loanRepository.Store(loan);
    }
}

How to Fix#

Declare the grant on the use-case record and let the authorization guard enforce it before the handler runs, so the handler never compares the caller with a target, never throws the forbidden refusal itself, and never takes the authorization guard. Remove the guard parameter from the handler constructor and leave the guard in the pipeline.

Notes#

What the Rule Ignores#

The rule does not report a throw of any other exception type, a throw in an event handler or a job, or a rethrow without a value. It does not report the authorization guard taken outside a constructor, taken by a type that is not a command or query handler, or a handler that takes any other constructor type, such as a repository. It does not run in Domain or WebApi.