smartassert/security-token-extractor
Extract security tokens from incoming HTTP requests through one consistent API. This small PHP package keeps token parsing out of your controllers and middleware, so Laravel and other PHP applications can authenticate cleanly with less duplicated logic, clearer code, and easier testing.
composer require smartassert/security-token-extractor
Check composer.json first: it tells you which request abstraction the library type-hints (psr/http-message, Symfony HttpFoundation, or a bespoke minimal interface). Then read src/ — the package is deliberately tiny, usually an extractor interface, one or two concrete extractors (authorization header, request parameter), and a TokenNotFound-style exception. There is no service provider and no Laravel integration; that's your job.
First use case: replace hand-rolled substr($request->header('Authorization'), 7) parsing with a single injectable extractor, used inside middleware.
public function handle(Request $request, Closure $next)
{
try {
$token = $this->extractor->extract(/* request adapter, see Gotchas */);
} catch (TokenNotFoundException | MalformedTokenException $e) {
return response()->json(['error' => 'unauthenticated'], 401);
}
$request->attributes->set('security_token', $token);
return $next($request);
}
Register the middleware in bootstrap/app.php (Laravel 11+) or app/Http/Kernel.php, then alias it for API groups.
Custom guard via Auth::viaRequest. This is the most idiomatic Laravel fit, because it plugs into auth middleware, Gate, and $request->user() for free:
// AuthServiceProvider::boot()
Auth::viaRequest('security-token', function (Request $request) {
$token = $this->extractor->extract($request);
return ApiClient::where('token_hash', hash('sha256', $token))->first();
});
Config: 'guards' => ['api' => ['driver' => 'security-token', 'provider' => null]].
Composite extractors, ordered. If the package ships both a header extractor and a query-parameter extractor, compose them: try the Authorization header first, fall back to the parameter, and surface success only when one yields a token. Header-first ordering matters — query-string tokens end up in access logs and Referer headers.
Per-route selection. Bind named extractors in a service provider (token.extractor.bearer, token.extractor.query) and resolve the right one in middleware based on route, so a webhook endpoint can accept ?token= while the rest of the API requires a header.
Adapter layer. Keep a single class that converts Illuminate\Http\Request into whatever the package expects. Isolate it so upgrading the library or swapping it out touches one file:
final class RequestAdapter
{
public static function fromLaravel(Request $request): ServerRequestInterface
{
return (new Psr17Factory())->createServerRequest(
$request->getMethod(),
$request->fullUrl(),
$request->server->all()
)
->withQueryParams($request->query->all())
->withHeader('Authorization', (array) $request->header('Authorization'));
}
}
Testing. The surface is small enough to mock the extractor interface in middleware tests and to exercise the real thing with Illuminate\Http\Request::create() in feature tests — no HTTP kernel or Vite build needed. Test the four cases explicitly: valid header, missing header, malformed header (Bearer with no value), and token in the fallback location.
Extraction ≠ validation. The package gives you a string. Signature verification, hashing, DB lookup, and JWT parsing stay in your app. Keeping that split means you can swap the extractor for a cookie- or header-based one without touching authorization logic.
Illuminate\Http\Request is not PSR-7. It extends Symfony\Component\HttpFoundation\Request, so if the library type-hints Psr\Http\Message\ServerRequestInterface you need a bridge (nyholm/psr7 plus symfony/psr-http-message-bridge) or a hand-written adapter. If it type-hints a narrower interface, decorate instead of converting — much cheaper. Verify the signature in src/ before wiring anything.
Failures are exceptions, not null. Catch the package's narrow exception types, not \Throwable, or you'll silently swallow adapter bugs and turn them into 401s that are miserable to debug.
Header stripping. Apache/CGI frequently drops Authorization unless you add RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]; behind a proxy it may arrive as X-Forwarded-Authorization. Test against your real web server — artisan serve will happily pass a header your production host discards.
Repeated headers and whitespace. A duplicated header can arrive as an array or a comma-joined string. Confirm the extractor handles both, and trim the Bearer prefix yourself if the package returns only the raw header line.
Never log the token. Laravel's debug exception page dumps request headers. Add the header to your log redaction list, keep APP_DEBUG=false in production, and avoid $request->all() in error contexts.
Maintenance risk. Zero stars, zero dependents, last release September 2023. The codebase is a handful of classes — if you need a PHP 8.4/8.5 compatibility fix or a new token source, copying it into app/Support/SecurityToken/ is cheaper and safer than waiting for upstream. The exception semantics are the part worth preserving.
Extension point. Implement the extractor interface for anything else you need — cookies, signed URLs, X-Api-Key, a JWT claim — and reuse the same failure behavior everywhere. Depend on the interface in your container bindings, never a concrete class, so multi-source registries and test doubles stay trivial.
How can I help you explore Laravel packages today?