Skip to main content
Apivalk splits authentication (“who are you?”) from authorization (“are you allowed to do this?”). Five moving parts:

The pipeline in one picture

AuthenticationMiddleware is passive — it never rejects the request. That means the same pipeline supports public routes (no RouteAuthorization), optional-auth routes (public but identity-aware), and strict routes all at once.

What you have to wire

  1. Pick or implement an authenticator. Apivalk ships JwtAuthenticator (see how to authenticate with JWT). For anything else, implement AuthenticatorInterfaceAPI key example.
  2. Register the two middlewares in the right order. Authentication before Security.
  3. Declare a security scheme in ComponentsObject. The name you give it (e.g. BearerAuth, ApiKeyAuth) becomes the link between your routes and the OpenAPI spec.
  4. Attach RouteAuthorization to each protected route.

Bootstrap

Route-level declaration

'BearerAuth' must match the name of a SecuritySchemeObject in your ComponentsObject — see OpenAPI Generator / Security Schemes.

What SecurityMiddleware does with this

For every required scope / permission:
  • If the identity has it → keep going.
  • If the identity is authenticated but missing the requirement → 403 Forbidden.
  • If the identity is a guest → 401 Unauthorized.
And finally, if the identity is still a guest at the end → 401 Unauthorized. Passing no requirements (just the scheme name) means “any authenticated user”.

The three useful route shapes

Public route. No routeAuthorization(...) at all. Guest and authenticated callers both pass.
Any-authenticated route. Pass only the scheme name. Guest → 401; anyone with a valid token → allowed.
Scoped route. Add scopes and/or permissions. All of them must be granted.

Reading the identity in a controller

Optional auth (public route, identity-aware response)

Leave RouteAuthorization off the route. The AuthenticationMiddleware still populates the identity if a valid token is present — you just branch in the controller:
This is how you run a public landing endpoint that shows “Log in” to guests and “Hi, Jane” to authenticated users with the same controller.

Scopes vs permissions

They’re two independent lists on RouteAuthorization. Both must be satisfied. How you split them is a policy choice — a common convention:
  • Scopes model who is allowed in this area (admin, animal, billing). Typically tied to an OAuth2 scope emitted at token-issuance time.
  • Permissions model fine-grained actions (animal:read, animal:create). Typically derived from the user’s roles on your side.
AbstractAuthIdentity::isScopeGranted() / isPermissionGranted() is simple in_array — override if you need hierarchy (e.g. admin implies everything).

Custom identity classes

JwtAuthIdentity is just the built-in that ships with JwtAuthenticator. Nothing stops you from defining your own:
Return it from your authenticator and downcast with PHPStan-friendly narrowing in the controllers that care.