aw3r1se / swagger-auth
Persistent authorization and silent token refresh for Swagger UI in Laravel
Requires
- php: ^8.3
- illuminate/contracts: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/view: ^11.0|^12.0
- symfony/http-foundation: ^7.0
Requires (Dev)
- darkaonline/l5-swagger: ^9.0|^10.0|^11.0
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0
Suggests
- darkaonline/l5-swagger: Serves the Swagger UI page the script is injected into; without it only the manual @include mode is available
This package is auto-updated.
Last update: 2026-08-03 04:32:11 UTC
README
Persistent authorization and silent token refresh for Swagger UI in Laravel.
Swagger UI drops its bearer token on reload, and once the token expires every Try it
out returns 401. This package captures the token from your own login endpoint, keeps it
in localStorage, and refreshes it before it dies.
- captures the token from the
login/refreshresponse and authorizes the UI - refreshes ahead of expiry — the interceptor reads the JWT expiry claim and awaits a
refresh once less than
skewseconds are left, so no request goes out with a dead token - collapses concurrent refreshes into one in-flight request
- clears the token on
logout, optionally refreshes in the background on an unexpected 401 - adds a toggle next to Authorize that turns all of it off
Install
composer require aw3r1se/swagger-auth
That is the whole setup. The provider is auto-discovered, appends a middleware to
l5-swagger.defaults.routes.middleware.api, and that middleware injects the script before
</head>. The script wraps window.SwaggerUIBundle, so it does not care where in the
markup it lands. An interceptor already set by the view is chained, not dropped.
It also coerces persistAuthorization: the stock l5-swagger view renders the string
"false", which is truthy in JavaScript.
Toggle
A key-with-arrows button next to Authorize. Off means no capture, no refresh, no clearing — Swagger UI behaves as if the package were absent, which is what you want when pasting a token by hand or reproducing something with a deliberately stale one. On it turns gold and spins once.
State lives in localStorage under swagger-auth.enabled and survives reloads. Also
scriptable:
SwaggerAuth.enabled(); // => true SwaggerAuth.setEnabled(false);
Configuration
php artisan vendor:publish --tag=swagger-auth-config
| Key | Default | |
|---|---|---|
scheme |
bearerAuth |
security scheme from your spec |
token_key |
access_token |
field of the login/refresh response body |
expiry_claim |
exp |
JWT payload claim holding the unix expiry |
skew |
30 |
seconds before expiry to refresh ahead of time |
routes.login |
/auth/login |
matched by pathname; null disables the hook |
routes.refresh |
/auth/refresh |
|
routes.logout |
/auth/logout |
|
inject |
true |
auto-injection; false for manual mode |
persist_authorization |
true |
null keeps l5-swagger's own setting |
toggle |
true |
the button next to Authorize |
labels.on / labels.off |
its title attribute |
|
csrf |
true |
send X-CSRF-TOKEN, if the request has a session |
refresh_on_unauthorized |
true |
refresh on a 401; the request is not replayed |
Every key has an environment counterpart — SWAGGER_AUTH_SCHEME,
SWAGGER_AUTH_REFRESH_PATH, SWAGGER_AUTH_SKEW, …
The view is publishable too, under --tag=swagger-auth-views.
Manual mode
For a hand-written page, or a Swagger UI not served by l5-swagger, set inject to false:
@include('swagger-auth::script') <script> window.ui = SwaggerUIBundle({ requestInterceptor: SwaggerAuth.requestInterceptor, responseInterceptor: SwaggerAuth.responseInterceptor, persistAuthorization: true, // ... }); </script>
The interceptors read window.ui, assigned only after SwaggerUIBundle returns, so
requests issued before that — the spec fetch — pass through untouched.
Notes
darkaonline/l5-swagger is suggested, not required: without it the provider leaves the
config alone and manual mode still works. zircote/swagger-php and the Swagger UI assets
are not involved at all.
The refresh endpoint must take the current token in the Authorization header and answer
with the new one under token_key — the default shape of php-open-source-saver/jwt-auth
and tymon/jwt-auth. Tokens with no decodable expiry claim are left alone and fall back to
refresh_on_unauthorized. A refresh that comes back 401 or 403 clears the token; anything
else — a network blip, a malformed body — is logged and left for the next attempt.
X-CSRF-TOKEN is only sent when the documentation route actually starts a session. Out of
the box l5-swagger registers its routes without the web group, so there is none and the
header is omitted rather than filled with a token belonging to nobody.
The token sits in localStorage, readable by any XSS on the same origin. Keep Swagger UI
off production, or behind authentication.
Credits
Toggle icon: rotate-ccw-key from
Lucide, ISC.
License
MIT