Middleware & Authentication
When to use
- Protecting Acorn routes with authentication (JWT, API tokens, custom guards)
- Request preprocessing: rate limiting, CSRF validation, locale detection
- Response postprocessing: adding headers, logging, caching
- Role / capability checks before a controller runs
- Route-specific authorization groups
When NOT to use
- Filtering native WordPress admin pages or
register_rest_route() endpoints — middleware does NOT run there; use add_action('admin_init') / rest_pre_dispatch instead
- Template-hierarchy front-end pages (pages, archives, single posts) — use
template_redirect action
- Business logic that should live in a service class
- Authentication for REST endpoints that bypass Acorn routes — extend
wp-rest-api skill guidance instead
Prerequisites
- Acorn installed with a working
routes/web.php or routes/api.php
RouteServiceProvider booted
- For JWT:
firebase/php-jwt installed via lando theme-composer require firebase/php-jwt
What Middleware Does in Acorn
Middleware filters HTTP requests before they reach route controllers — the same pipeline concept as Laravel. Each middleware inspects or transforms the request, then either passes it forward or returns a response early.
Critical distinction: Middleware only runs on Acorn-registered routes (defined in routes/web.php or routes/api.php). It does NOT intercept native WordPress requests (admin pages, REST API endpoints registered via register_rest_route(), or front-end page loads handled by the template hierarchy). Use add_action/add_filter hooks for WordPress-native requests.
Quick Start
# 1. Generate a middleware class
lando acorn make:middleware EnsureJsonResponse
# Or use the helper script (see Scripts section)
bash skills/acorn-middleware/scripts/create-middleware.sh MyMiddleware --type=filter
// app/Http/Middleware/EnsureJsonResponse.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureJsonResponse
{
public function handle(Request $request, Closure $next): Response
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
// app/Http/Kernel.php — register middleware aliases
protected $middlewareAliases = [
'auth.jwt' => \App\Http\Middleware\AuthenticateJwt::class,
'role' => \App\Http\Middleware\CheckRole::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
// routes/api.php — apply to routes
Route::middleware(['auth.jwt', 'throttle:60,1'])->group(function () {
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store'])->middleware('role:editor,administrator');
});
Common Patterns (Summary)
| Pattern |
Class |
Registration |
| Force JSON responses |
EnsureJsonResponse |
api middleware group |
| JWT auth |
AuthenticateJwt |
'auth.jwt' alias |
| Role check |
CheckRole |
'role:administrator' |
| Rate limiting |
built-in ThrottleRequests |
'throttle:60,1' |
| CORS |
built-in HandleCors |
global middleware |
| Capability check |
CheckCapability |
'capability:edit_posts' |
References
Deep content extracted from this skill. Read on demand — zero tokens until needed.
- jwt-auth.md — Full JWT setup:
JwtService, AuthenticateJwt middleware, AuthController (login/refresh/me), route registration, client-side token flow, and .env variables.
- custom-guards.md — Laravel
Guard contract, WordPressGuard implementation, registering via Auth::extend(), configuring config/auth.php, using auth()->user() in controllers.
- request-filtering.md — Before/after middleware, HTTP Kernel setup, middleware groups (
web, api, api.auth), middleware parameters, CheckRole, ThrottleRequests, CORS config, middleware ordering.
- troubleshooting.md — Common failure modes (middleware not executing, JWT secret issues,
current_user_can() returning false), best practices table, common mistakes table, escalation paths.
Scripts
# Create a new middleware via Lando
bash skills/acorn-middleware/scripts/create-middleware.sh <Name> [--type=auth|filter]
# Examples
bash skills/acorn-middleware/scripts/create-middleware.sh CheckApiKey --type=filter
bash skills/acorn-middleware/scripts/create-middleware.sh AuthenticateJwt --type=auth
Script: scripts/create-middleware.sh — runs lando acorn make:middleware <Name> with guard checks.
Assets
Boilerplate templates with {{PLACEHOLDER}} tokens. Copy and replace placeholders.
- middleware-auth.php.tpl — Auth guard check middleware. Replace
{{CLASS_NAME}} and {{GUARD_NAME}}.
- middleware-filter.php.tpl — General-purpose request filter with
passes() hook. Replace {{CLASS_NAME}}, {{REJECTION_MESSAGE}}, {{REJECTION_STATUS}}.
Verification
- Send a request without credentials — confirm middleware returns a 401/403 JSON response (not pass through).
- Send a request with a valid JWT Bearer token — confirm the controller responds with 200.
- Run
lando acorn route:list and verify the middleware alias appears in the middleware column for protected routes.
For detailed debug tips and common mistake patterns, read references/troubleshooting.md.
Failure modes
See references/troubleshooting.md for the full failure modes list, common mistakes table, and escalation paths.
Critical Rules
- Middleware only runs on Acorn routes. Never expect it to intercept WordPress admin, REST API (
register_rest_route), or template-hierarchy requests.
- Always call
wp_set_current_user($user->ID) in JWT/auth middleware, or current_user_can() will fail downstream.
- Never hardcode
JWT_SECRET. Use env('JWT_SECRET') and store a 32+ character random string in .env.
- Create
app/Http/Kernel.php before registering middleware aliases or groups — without it, aliases silently do nothing.
- Order middleware correctly: auth before role/capability checks;
EnsureJsonResponse before auth in API groups.
- Throttle auth endpoints strictly:
throttle:5,1 for login, throttle:10,1 for token refresh.
- Return JSON errors from API middleware, not HTML. Always pair error returns with the correct HTTP status code (401 for unauthenticated, 403 for unauthorized).
- Use
$request->bearerToken(), not manual Authorization header parsing.
1---2name: superpowers-sage-acorn-middleware3description: HTTP middleware, JWT authentication, auth middleware, custom guards, JWT guard, route middleware, request filter, middleware group, Acorn middleware, guard, rate limiting, CORS, CheckRole, throttle, bearer token, request filtering, middleware registration, HTTP Kernel — using Acorn's Laravel middleware stack inside WordPress/Sage/Bedrock4---56# Middleware & Authentication78## When to use910- Protecting Acorn routes with authentication (JWT, API tokens, custom guards)11- Request preprocessing: rate limiting, CSRF validation, locale detection12- Response postprocessing: adding headers, logging, caching13- Role / capability checks before a controller runs14- Route-specific authorization groups1516## When NOT to use1718- Filtering native WordPress admin pages or `register_rest_route()` endpoints — middleware does NOT run there; use `add_action('admin_init')` / `rest_pre_dispatch` instead19- Template-hierarchy front-end pages (pages, archives, single posts) — use `template_redirect` action20- Business logic that should live in a service class21- Authentication for REST endpoints that bypass Acorn routes — extend `wp-rest-api` skill guidance instead2223## Prerequisites2425- Acorn installed with a working `routes/web.php` or `routes/api.php`26- `RouteServiceProvider` booted27- For JWT: `firebase/php-jwt` installed via `lando theme-composer require firebase/php-jwt`2829## What Middleware Does in Acorn3031Middleware filters HTTP requests before they reach route controllers — the same pipeline concept as Laravel. Each middleware inspects or transforms the request, then either passes it forward or returns a response early.3233**Critical distinction:** Middleware only runs on Acorn-registered routes (defined in `routes/web.php` or `routes/api.php`). It does NOT intercept native WordPress requests (admin pages, REST API endpoints registered via `register_rest_route()`, or front-end page loads handled by the template hierarchy). Use `add_action`/`add_filter` hooks for WordPress-native requests.3435## Quick Start3637```bash38# 1. Generate a middleware class39lando acorn make:middleware EnsureJsonResponse4041# Or use the helper script (see Scripts section)42bash skills/acorn-middleware/scripts/create-middleware.sh MyMiddleware --type=filter43```4445```php46// app/Http/Middleware/EnsureJsonResponse.php47namespace App\Http\Middleware;4849use Closure;50use Illuminate\Http\Request;51use Symfony\Component\HttpFoundation\Response;5253class EnsureJsonResponse54{55 public function handle(Request $request, Closure $next): Response56 {57 $request->headers->set('Accept', 'application/json');58 return $next($request);59 }60}61```6263```php64// app/Http/Kernel.php — register middleware aliases65protected $middlewareAliases = [66 'auth.jwt' => \App\Http\Middleware\AuthenticateJwt::class,67 'role' => \App\Http\Middleware\CheckRole::class,68 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,69];7071// routes/api.php — apply to routes72Route::middleware(['auth.jwt', 'throttle:60,1'])->group(function () {73 Route::get('/posts', [PostController::class, 'index']);74 Route::post('/posts', [PostController::class, 'store'])->middleware('role:editor,administrator');75});76```7778## Common Patterns (Summary)7980| Pattern | Class | Registration |81|---|---|---|82| Force JSON responses | `EnsureJsonResponse` | `api` middleware group |83| JWT auth | `AuthenticateJwt` | `'auth.jwt'` alias |84| Role check | `CheckRole` | `'role:administrator'` |85| Rate limiting | built-in `ThrottleRequests` | `'throttle:60,1'` |86| CORS | built-in `HandleCors` | global middleware |87| Capability check | `CheckCapability` | `'capability:edit_posts'` |8889## References9091Deep content extracted from this skill. Read on demand — zero tokens until needed.9293- **[jwt-auth.md](references/jwt-auth.md)** — Full JWT setup: `JwtService`, `AuthenticateJwt` middleware, `AuthController` (login/refresh/me), route registration, client-side token flow, and `.env` variables.94- **[custom-guards.md](references/custom-guards.md)** — Laravel `Guard` contract, `WordPressGuard` implementation, registering via `Auth::extend()`, configuring `config/auth.php`, using `auth()->user()` in controllers.95- **[request-filtering.md](references/request-filtering.md)** — Before/after middleware, HTTP Kernel setup, middleware groups (`web`, `api`, `api.auth`), middleware parameters, `CheckRole`, `ThrottleRequests`, CORS config, middleware ordering.96- **[troubleshooting.md](references/troubleshooting.md)** — Common failure modes (middleware not executing, JWT secret issues, `current_user_can()` returning false), best practices table, common mistakes table, escalation paths.9798## Scripts99100```bash101# Create a new middleware via Lando102bash skills/acorn-middleware/scripts/create-middleware.sh <Name> [--type=auth|filter]103104# Examples105bash skills/acorn-middleware/scripts/create-middleware.sh CheckApiKey --type=filter106bash skills/acorn-middleware/scripts/create-middleware.sh AuthenticateJwt --type=auth107```108109Script: [`scripts/create-middleware.sh`](scripts/create-middleware.sh) — runs `lando acorn make:middleware <Name>` with guard checks.110111## Assets112113Boilerplate templates with `{{PLACEHOLDER}}` tokens. Copy and replace placeholders.114115- **[middleware-auth.php.tpl](assets/middleware-auth.php.tpl)** — Auth guard check middleware. Replace `{{CLASS_NAME}}` and `{{GUARD_NAME}}`.116- **[middleware-filter.php.tpl](assets/middleware-filter.php.tpl)** — General-purpose request filter with `passes()` hook. Replace `{{CLASS_NAME}}`, `{{REJECTION_MESSAGE}}`, `{{REJECTION_STATUS}}`.117118## Verification119120- Send a request without credentials — confirm middleware returns a 401/403 JSON response (not pass through).121- Send a request with a valid JWT Bearer token — confirm the controller responds with 200.122- Run `lando acorn route:list` and verify the middleware alias appears in the middleware column for protected routes.123124For detailed debug tips and common mistake patterns, read [`references/troubleshooting.md`](references/troubleshooting.md).125126## Failure modes127128See [`references/troubleshooting.md`](references/troubleshooting.md) for the full failure modes list, common mistakes table, and escalation paths.129130## Critical Rules1311321. **Middleware only runs on Acorn routes.** Never expect it to intercept WordPress admin, REST API (`register_rest_route`), or template-hierarchy requests.1332. **Always call `wp_set_current_user($user->ID)`** in JWT/auth middleware, or `current_user_can()` will fail downstream.1343. **Never hardcode `JWT_SECRET`.** Use `env('JWT_SECRET')` and store a 32+ character random string in `.env`.1354. **Create `app/Http/Kernel.php`** before registering middleware aliases or groups — without it, aliases silently do nothing.1365. **Order middleware correctly:** auth before role/capability checks; `EnsureJsonResponse` before auth in API groups.1376. **Throttle auth endpoints strictly:** `throttle:5,1` for login, `throttle:10,1` for token refresh.1387. **Return JSON errors from API middleware**, not HTML. Always pair error returns with the correct HTTP status code (401 for unauthenticated, 403 for unauthorized).1398. **Use `$request->bearerToken()`**, not manual `Authorization` header parsing.