Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Jwtauthroles Laravel Package

werk365/jwtauthroles

A Laravel package that combines JSON Web Token authentication with role-based access control, letting you issue JWTs and restrict routes or actions by user role. A lightweight option for adding token auth and simple role checks to existing Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

The package plugs FusionAuth-issued JWTs into Laravel's auth system and exposes the roles claim to your app. It supports two verification modes: a static PEM public key, or a JWKS endpoint (the production-friendly option, since FusionAuth rotates keys).

Version 1.3.1 explicitly declares support for Laravel 9 and 10. If you're on Laravel 11 or 12, expect to fork and widen the constraint before you can install at all.

Minimal path:

composer require werk365/jwtauthroles:^1.3.1
php artisan vendor:publish --provider="Werk365\JwtAuthRoles\JwtAuthRolesServiceProvider"
php artisan migrate   # if the package ships role/user tables

Configure .env:

# Pick one: JWKS (recommended) or a PEM public key
JWT_JWKS_URL=https://auth.example.com/.well-known/jwks.json
# JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"

JWT_ALGORITHM=RS256
JWT_ISSUER=https://auth.example.com
JWT_AUDIENCE=my-laravel-api

Wire the guard in config/auth.php:

'guards' => [
    'api' => [
        'driver'   => 'jwt',
        'provider' => 'users',
    ],
],

Add the package trait to your User model (check the installed trait name — it's the one that gives you hasRole/hasAnyRole and the JWT sub mapping):

use Werk365\JwtAuthRoles\Traits\JwtAuthRoles;

class User extends Authenticatable
{
    use JwtAuthRoles;
}

Where to look first:

  • composer.json — confirm the laravel/framework and illuminate/* constraints say ^9.0|^10.0 before you build on it.
  • config/jwtauthroles.php — claim names (sub, roles), key source, algorithm, user model binding.
  • The service provider — how the guard is registered and whether a role: middleware alias exists.
  • The guard class — whether it auto-provisions users, looks them up by sub or email, and whether it persists roles.
  • The trait — the actual role-checking API surface you'll use in controllers and policies.

First real use case: a SPA or mobile app authenticates against FusionAuth, receives an access token, and calls your Laravel API with Authorization: Bearer <token>. Your API validates the signature (via JWKS), resolves the sub to a local user, syncs roles, and gates routes on those roles.

Route::middleware('auth:api')->get('/me', fn (Request $r) => $r->user());

Route::middleware(['auth:api', 'role:admin'])->group(function () {
    Route::get('/admin/users', [UserController::class, 'index']);
});

Implementation Patterns

Guard-per-surface. Keep the JWT guard distinct from any Sanctum/session guard (auth:jwt vs auth:web). Mixing them makes Auth::user() unpredictable and complicates middleware ordering.

Map sub explicitly. Don't assume FusionAuth's sub equals users.id. Add a fusionauth_id (or sub) column, make it unique and indexed, and configure the package to resolve on it. Email-based matching breaks the first time a user changes their email in FusionAuth.

Decide where roles live. Two viable models:

  • Claim-only: read roles from the decoded token on each request. Zero DB writes, always fresh, but not queryable ("list all admins") and doesn't survive token expiry.
  • Persisted: the guard syncs roles into local roles/role_user tables at authentication time. Queryable and joins well, but you must use sync() semantics so revoked FusionAuth roles actually disappear, and you should sync on login or token refresh — not on every request.

Authorization gates over ad-hoc checks. Wrap role logic once:

Gate::define('manage-billing', fn (User $u) => $u->hasAnyRole(['billing', 'admin']));

Then use $this->authorize('manage-billing') in controllers or @can in Blade. This keeps role semantics out of route files and makes them testable.

Middleware for coarse gates, policies for fine ones. Use role:admin on route groups for obvious boundaries; use policies when the decision depends on the resource (e.g., "editor of this team").

JWKS with caching. In production, wrap the JWKS fetch in Cache::remember() with a TTL (an hour is typical) and handle the kid-not-found case by busting the cache once and retrying. Without caching, every API request makes an outbound HTTP call to FusionAuth.

PEM for local/dev. A static public key is simpler in tests and local environments; switch to JWKS in staging/prod so key rotation doesn't require a deploy.

Testing. Mint tokens with a throwaway keypair in tests/, point the package config at the test public key in phpunit.xml, and assert on sub/roles claims. For unit tests of controllers, actingAs($user, 'api') is usually enough if the guard honors it.

Token refresh is FusionAuth's job, not Laravel's. Keep access tokens short-lived, let the client refresh, and never issue your own tokens from this package.

Fork-first on Laravel 11+. 1.3.1 tops out at Laravel 10. Rather than hacking composer.json in vendor/, use a VCS/path repository fork, bump the illuminate/* constraints, and run the test suite — the guard surface is small enough that this is usually a constraint edit, not a code change.

Gotchas and Tips

  • Package maturity. Latest release is 1.3.1 ("Added laravel 9|10 support"); the project is still small (low stars, no dependents) and slow-moving. Pin ^1.3.1, read the guard source before shipping, and be prepared to fork. The 1.3.1 constraint bump means Laravel 9/10 are officially covered — anything older or newer is on you.
  • Version constraint failures. On Laravel 11/12, composer require will refuse to resolve. Verify with composer why-not werk365/jwtauthroles 1.3.1 (it'll name laravel/framework), then fork and widen rather than forcing with --ignore-platform-reqs (that flag won't help — it's a version constraint, not a platform requirement).
  • Algorithm confusion. Make sure the guard validates against the algorithm configured in config/jwtauthroles.php and does not trust the token header's alg. A guard that accepts none or an HS256 token signed with the public key as a secret is a critical vulnerability. Verify this in the source.
  • JWKS kid misses. After rotation, a cached JWKS may not contain the token's kid. Retry once with a fresh fetch; don't hard-fail the request on the first miss.
  • Clock skew. FusionAuth and app servers drift. Allow a small leeway (30–60s) on exp/nbf if the package exposes it; otherwise keep hosts NTP-synced.
  • PEM in .env. Multi-line PEM is painful. Either use escaped \n, base64-encode the key and decode in config/jwtauthroles.php, or reference a mounted file path.
  • Claim-name mismatch. FusionAuth can emit roles under roles or a namespaced custom claim like https://example.com/roles. If hasRole() always returns false, decode a real token (jwt.io or base64 -d) and compare to roles_claim in config. This is the single most common silent failure.
  • iss/aud validation. Confirm the package actually validates them. If it doesn't, add it — a valid signature from the wrong issuer is still a valid signature.
  • Guard name clashes. If auth:api is already Sanctum/Passport in your app, name the JWT guard something else (auth:fusion) and update route middleware accordingly.
  • Middleware alias. If the role: middleware isn't auto-registered, add it in bootstrap/app.php (Laravel 11+) or Kernel.php (10 and below). The 401/403 responses should be JSON for an API — check your exception rendering for AuthenticationException.
  • Role N+1. If roles are persisted, eager-load them (with('roles')) before calling hasRole() in a loop or collection map.
  • Don't log tokens. Log sub and jti only; full bearer tokens in logs are a credential leak.
  • CI without network. Point JWKS at a local fixture or use PEM in tests so CI doesn't depend on FusionAuth availability.
  • Stateless semantics. This guard has no session, no logout, no CSRF. Don't use it as the primary guard for a server-rendered web app that needs those things.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource