Skip to content

Tenant Isolation

The package stores subject and claim data in tokens but does not enforce tenant policy. The application owns tenant authorization rules.

Token Context with Tenant

Attach tenant or company information when issuing tokens. Use the built-in company helpers for the primary active context and values your middleware/controllers need to read:

php
$pair = app(JwtTokenService::class)->issueTokenPair(
    $user,
    TokenContext::make()
        ->companyId($activeCompanyId)
        ->companyIds($allowedCompanyIds)
        ->impersonated($isImpersonating)
        ->scopes(['invoices.read', 'invoices.write'])
);

companyId() sets subject_type=company, subject_id, and claims.company_id. companyIds() and impersonated() write app-readable claims. Claims are persisted in the claims JSON column and embedded into the signed JWT payload.

Access Tenant Data in Middleware

Read the tenant/company claim from the authenticated token. This does not require adding a custom company_id column to the package table.

php
use Sopheak\JwtAuth\Traits\HasJwtTokens;

Route::middleware('auth:api')->group(function () {
    Route::get('/invoices', function (Request $request) {
        $token = $request->user()->token();
        $companyId = $token?->companyId();

        // App-owned tenant scoping
        return Invoice::where('company_id', $companyId)->paginate();
    });
});

Token Validation Hooks

Use TokenContextValidator to reject token issuance for invalid tenant contexts:

php
use Sopheak\JwtAuth\Contracts\TokenContextValidator;
use Sopheak\JwtAuth\DTO\TokenContext;

$this->app->bind(TokenContextValidator::class, function (): TokenContextValidator {
    return new class implements TokenContextValidator
    {
        public function validate(Authenticatable $user, TokenContext $context): void
        {
            $companyId = $context->claims['company_id'] ?? null;

            if ($companyId === null) {
                throw new \RuntimeException('Company ID is required.');
            }

            if (! $user->belongsToCompany($companyId)) {
                throw new \RuntimeException('User does not belong to this company.');
            }
        }
    };
});

Multi-Tenant API Key

API keys also support tenant context:

php
$key = app(ApiKeyService::class)->createApiKey(ApiKeyContext::forCompany(
    companyId: $activeCompanyId,
    name: 'ERP sync',
    scopes: ['invoices.read'],
));

Subject vs Claims

ConceptWhen to use
subject(type, id)Primary active entity the token represents (tenant, company, workspace)
claims([...])Additional context passed through to your app logic

The package stores both but does not enforce either. Your middleware and controllers own tenant isolation.