$npx -y skills add Lonsdale201/wp-agent-skills --skill br-single-use-tokenConfigure Better Route 1.1 atomic single-use tokens for authorization codes, password resets, magic links, invitations, and email confirmation. Use when a token must be stored hashed and consumed no more than once across concurrent requests.
| 1 | # Better Route single-use tokens |
| 2 | |
| 3 | Use `SingleUseTokenMiddleware` when replay must be rejected atomically before the handler runs. |
| 4 | |
| 5 | ```php |
| 6 | use BetterRoute\Middleware\Write\SingleUseTokenMiddleware; |
| 7 | use BetterRoute\Middleware\Write\WpdbSingleUseTokenStore; |
| 8 | |
| 9 | register_activation_hook(__FILE__, static function (): void { |
| 10 | (new WpdbSingleUseTokenStore())->installSchema(); |
| 11 | }); |
| 12 | |
| 13 | $singleUse = new SingleUseTokenMiddleware( |
| 14 | store: new WpdbSingleUseTokenStore(), |
| 15 | tokenSource: static fn ($request): ?string => $request->get_param('code'), |
| 16 | hashSalt: MY_PLUGIN_SINGLE_USE_TOKEN_SALT, |
| 17 | ttlSeconds: 300 |
| 18 | ); |
| 19 | |
| 20 | $router->post('/oauth/token', $handler) |
| 21 | ->middleware([$singleUse]) |
| 22 | ->protectedByMiddleware('singleUseToken'); |
| 23 | ``` |
| 24 | |
| 25 | Create a high-entropy raw token with `Crypto::token()`, deliver it over the intended secure channel, and call `storeToken($rawToken, $safeContext, $ttl)`. The middleware stores and looks up an HMAC-SHA256 token hash; successful consumption exposes the stored context as `singleUseToken`. |
| 26 | |
| 27 | ## Store selection |
| 28 | |
| 29 | - Use `WpdbSingleUseTokenStore` as the general production choice and install its schema during activation/migration. Consumption is an atomic conditional `UPDATE`. |
| 30 | - Use `WpCacheSingleUseTokenStore` only with a persistent external object cache. It refuses the normal non-persistent WordPress cache because its add-lock would not be cross-request atomic. |
| 31 | - Use `ArraySingleUseTokenStore` only in tests or a genuinely single-process environment. |
| 32 | |
| 33 | ## Rules |
| 34 | |
| 35 | - Configure a dedicated non-empty hash salt. If omitted under WordPress, Better Route derives one from `wp_salt('auth')`; explicit application separation is clearer for portable integrations. |
| 36 | - Do not put secrets or objects into stored context. The wpdb store uses `unserialize(..., ['allowed_classes' => false])`, but keep context data-only and minimal. |
| 37 | - Keep TTLs as short as the user flow permits. |
| 38 | - Consume before issuing credentials or performing any side effect. Consumption is intentionally not rolled back when the handler later fails. |
| 39 | - A previously consumed live token returns `409 single_use_token_reused`; unknown or expired tokens return `401 invalid_single_use_token`. |
| 40 | - Pair the route with rate limiting to slow token guessing. |
| 41 | |
| 42 | Test two simultaneous consumes, expiration, unknown/reused tokens, schema absence, cache-store startup without a persistent cache, and handler failure after consumption. |
| 43 | |
| 44 | Source references: `src/Middleware/Write/SingleUseTokenMiddleware.php`, `src/Middleware/Write/WpdbSingleUseTokenStore.php`, `src/Middleware/Write/WpCacheSingleUseTokenStore.php`. |
| 45 | |
| 46 | ## References |
| 47 | |
| 48 | - Official documentation: <https://lonsdale201.github.io/better-docs/docs/better-route/agents> |