Skip to main content

Authentication comes first

Authorization requirements only make sense once the router knows which authentication schemes it should advertise.
Those schemes operate at the router level. Authorization then decides what an authenticated caller is allowed to do on a given endpoint.

Two attributes, two jobs

Authorization is split into two parts:
  • [UseAuthorization] — turns on authorization middleware for a controller
  • [Authorization] — adds role or permission requirements to a specific method

Attribute-based authorization

Authorization middleware declared through [UseAuthorization] is constructed through Router.ServiceProvider, so constructor injection works there in the same way it does for controllers and middleware.

How the flow works

  • [UseAuthorization] registers one or more controller-level authorization middleware types
  • [Authorization] adds requirements to individual methods
  • AuthorizationMiddleware<T> only receives requirements of type T
  • The middleware must set AuthorizationContext.Authorized = true
  • Rejected requirements return 403 Forbidden
You can apply [UseAuthorization] more than once to the same controller when different requirement types should be evaluated by different middleware classes.

Roles and permissions together

Low-level equivalent

When to choose attributes vs manual setup

Choose attributes when:
  • The route is already controller-based
  • The requirement belongs naturally to one endpoint
  • You want the policy close to the action method
Choose the manual API when:
  • You register low-level endpoints directly
  • You need fine-grained control over pipeline wiring
  • You build a reusable router module outside controller discovery
For intentional HTTP failures inside authorization or middleware, throw HttpException subclasses such as ForbiddenException. Unexpected exceptions are converted into 500 responses by the router’s exception boundary.
Advanced patterns covers raw request handling, manual endpoints, and parameter-name codecs.