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

Laravel Passport Claims Laravel Package

corbosman/laravel-passport-claims

Add custom claims to Laravel Passport access tokens. Drop the provided trait onto your User model, define a getExtraClaims() method, and your extra data is embedded in the JWT for clients to read. Lightweight, no config required.

View on GitHub
Deep Wiki
Context7

Getting Started

Laravel Passport mints JWT access tokens with a fixed, minimal claim set (sub, scopes, iss, aud, iat, nbf, exp, jti). Anything else your resource servers need to know about the caller — tenant id, roles, feature flags, display name — normally forces either a database round-trip on every request or a fork of Passport's token internals. This package closes that gap with a single trait method.

Install and wire up:

composer require corbosman/laravel-passport-claims

The service provider is auto-discovered. There is no config file to publish — the extension point is your own model.

Where to look first: open vendor/corbosman/laravel-passport-claims/src and read the HasClaims trait and the service provider. The provider is what swaps the token creation path; the trait is the only thing you touch. Read the README's usage block before anything else — it is short and it is the whole API surface.

First use case — enrich the access token with identity data:

use Corbosman\Passport\HasClaims;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasClaims;

    public function getClaims(): array
    {
        return [
            'email'    => $this->email,
            'name'     => $this->name,
            'tenant_id' => $this->tenant_id,
            'roles'    => $this->roles()->pluck('name')->all(),
        ];
    }
}

Issue a token the normal way ($user->createToken('mobile') or a POST /oauth/token with the password grant), paste the access_token into jwt.io, and confirm the claims are present on the payload. If they are, the rest is just discipline about what you put there.

Implementation Patterns

Treat getClaims() as a token-time snapshot, not a live view. It is evaluated once when the access token is minted. Every downstream decision — authorization middleware, tenant scoping, feature gating — reads the value frozen in the JWT until it expires. Design around that: keep tokens short (Passport::tokensExpireIn(now()->addMinutes(15))) so a refresh re-runs getClaims() against current data.

Keep claims small, flat, and stable. The JWT is signed, not encrypted; every claim travels in an HTTP header on every request. Good candidates: tenant_id, org_slug, roles (array of strings), plan, locale, token_version. Bad candidates: nested permission trees, full profile objects, anything over a few hundred bytes.

Multi-tenant scoping is the canonical pattern. Put tenant_id in the token, read it in a middleware, and bind it to a request-scoped container entry or a global Eloquent scope. The request never has to resolve the user to know which tenant it is serving:

class TenantScope
{
    public function handle($request, Closure $next)
    {
        $payload = /* decode $request->bearerToken() */;
        abort_unless(isset($payload['tenant_id']), 401);
        app()->instance('tenant.id', $payload['tenant_id']);
        return $next($request);
    }
}

Read claims where you already have the token. Passport's guard resolves the user from sub and does not hand you the parsed payload. Either decode the bearer token in a middleware using the JWT library already in your dependency tree (Lcobucci\JWT ships with modern Passport), or expose the claims you actually need through a small request attribute. Do not re-query the user just to fetch data that is already in the token.

Use a token_version claim for cheap invalidation. Return $this->token_version in getClaims(), bump the column on the user when roles change, and have middleware reject tokens whose version does not match. This gives you near-revocation semantics without a denylist table.

Separate claims from scopes. Scopes (read:orders, write:orders) are Passport's authorization primitive and are checked by $request->user()->tokenCan(...). Claims are identity/context. Don't use a roles claim as your only authorization check unless you accept that the decision is frozen for the token lifetime.

Testing. Passport::actingAs($user, $scopes) fakes the guard and never mints a token, so claims are not exercised. Test getClaims() directly (unit) and mint a real token through the /oauth/token endpoint (feature) when you need to assert the payload. $user->createToken() is the cheapest real-token test.

Gotchas and Tips

  • Never return reserved claims. sub, iss, aud, exp, iat, nbf, jti, and scopes are managed by Passport and the JWT builder. Overriding sub is especially dangerous — Passport uses it to look the user up, so a wrong sub silently breaks authentication or impersonates another user.
  • Claims are a snapshot, not a cache invalidation mechanism. Changing a user's role does not change any existing token. Pair claims with short expiries plus refresh, or with a token_version check.
  • Refresh tokens don't carry the custom claims. A refresh mints a new access token, which re-runs getClaims(). That is usually what you want, but it means a refresh can produce a token with different claims than the one it replaced — expect that in your clients.
  • Client-credentials grants have no user. There is no model to call getClaims() on, so those tokens get the default Passport claim set. Do not assume claims are always present when decoding; always null-coalesce.
  • Token bloat is a real outage vector. Nginx's large_client_header_buffers and Apache's LimitRequestFieldSize cap request-line/header size (often 8 KB by default). A roles array or nested permissions object that grows with the org will eventually return 400s that look like random auth failures. Keep the payload under ~1 KB.
  • Never put secrets or sensitive PII in a claim. Anyone holding the token can base64-decode the payload. Signing provides integrity, not confidentiality. If you need encrypted claims, you are outside this package's scope.
  • Exceptions in getClaims() fail the whole token endpoint. A lazy-loaded relation that throws, an external service call, or a null dereference turns a login into a 500. Keep the method cheap and side-effect free; preload any needed relations and avoid network calls.
  • Watch the Passport major version. This package sits on Passport internals (the token/authorization-server path), so a Passport upgrade can break it. Pin the Composer constraint, and re-read the package's README/compatibility notes before bumping laravel/passport. Same warning applies if you already customize AccessToken, bind a custom AuthorizationServer, or use another claims package — you are both patching the same seam.
  • Decoding order matters in middleware. Passport's auth:api middleware already verified the signature and resolved the user. If you decode separately, do it after auth so you are not trusting an unverified payload.
  • Debugging tip: issue a token, then decode locally with Lcobucci\JWT (or paste into jwt.io). If the token builder throws, you get a LogicException from the JWT library — usually a reserved claim name or an unsupported claim type (objects, Carbon, Eloquent models). Cast everything to scalars or arrays of scalars.
  • Extension point: since the package works by overriding Passport's token construction, per-client or per-request dynamic claims (e.g., different claims for different OAuth clients) are not a first-class feature. If you need that, extend the package's token class and rebind it rather than trying to bend getClaims() into a request-aware method.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package