waventra / laravel-identity
Laravel identity package for users, teams, roles, and permissions, with JWT authentication via waventra/laravel-jwt-token.
Requires
- php: ^8.2
- illuminate/auth: ^10.0|^11.0|^12.0
- illuminate/cache: ^10.0|^11.0|^12.0
- illuminate/console: ^10.0|^11.0|^12.0
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/events: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- illuminate/view: ^10.0|^11.0|^12.0
- waventra/laravel-jwt-token: ^1.0
Requires (Dev)
- orchestra/testbench: ^8.21|^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.0
README
Stable version 1.0.0.
Laravel identity package for users, teams, roles, and permissions, with authentication through waventra/laravel-jwt-token. Independent of any host application.
Attach HasIdentity to your user model. Login, refresh, and logout are issued as JWT access and rotating refresh tokens. Roles, permissions, and teams sit on top of that user.
Install: composer require waventra/laravel-identity:^1.0
Packagist: packagist.org/packages/waventra/laravel-identity
Source: github.com/waventra/laravel-identity
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
waventra/laravel-jwt-token^1.0(pulled in automatically)
Installation
composer require waventra/laravel-identity:^1.0
Laravel auto-discovers the service provider and Identity facade.
php artisan vendor:publish --tag=identity-config php artisan migrate
Migrations load automatically. Publish them only if you want copies in your app.
Setup
Add the trait to your authenticatable model:
use Illuminate\Foundation\Auth\User as Authenticatable; use Waventra\Identity\Traits\HasIdentity; use Waventra\Jwt\Contracts\JwtSubject; class User extends Authenticatable implements JwtSubject { use HasIdentity; }
HasIdentity includes JWT helpers (issueJwt(), revokeAllJwtTokens()) and custom claims for email, current team, roles, and permissions.
Point the package at that model in config/identity.php (or IDENTITY_USER_MODEL):
'user' => [ 'model' => App\Models\User::class, 'table' => 'users', ],
Register the JWT guard in config/auth.php if Identity did not already add it:
'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],
Generate JWT keys (RS256) or a secret (HS256) as documented in waventra/laravel-jwt-token:
php artisan jwt:generate-keys
Authentication
Identity registers these routes (disable with identity.auth.routes = false):
| Method | Path | Auth |
|---|---|---|
POST |
/api/auth/login |
Public |
POST |
/api/auth/refresh |
Refresh token |
GET |
/api/auth/me |
jwt.auth |
POST |
/api/auth/logout |
jwt.auth |
POST |
/api/auth/logout-all |
jwt.auth |
curl -X POST http://localhost:8000/api/auth/login \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d "{\"email\":\"ada@example.com\",\"password\":\"secret\"}"
The response is a JWT token pair plus the identity user:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 1209600,
"refresh_token": "selector.verifier",
"user": {
"id": 1,
"email": "ada@example.com",
"roles": ["admin"],
"permissions": ["users.create"]
}
}
use Waventra\Identity\Facades\Identity; $pair = Identity::attempt(['email' => $email, 'password' => $password], $request); $pair = Identity::issue($user, $request); Identity::refresh($refreshToken, $request); Identity::logout($request->bearerToken(), $refreshToken, $request); Identity::logoutAll($user);
Protect routes with the JWT middleware from laravel-jwt-token, then identity roles:
Route::middleware(['jwt.auth', 'role:admin'])->get('/admin', ...); Route::middleware(['jwt.auth', 'permission:users.create'])->post('/users', ...);
Roles and permissions
use Waventra\Identity\Facades\Identity; Identity::createRole('admin'); Identity::createPermission('users.create'); $role = Identity::findRole('admin'); $role->givePermissionTo('users.create'); $user->assignRole('admin'); $user->givePermissionTo('users.delete'); $user->hasRole('admin'); $user->hasPermissionTo('users.create'); $user->hasAnyRole(['admin', 'editor']); $user->hasAllPermissions(['users.create', 'users.delete']);
posts.* matches posts.edit. * matches every permission. Users with the super-admin role skip permission checks.
Laravel Gates are wired automatically for permissions that exist:
$user->can('users.create');
Middleware
Route::get('/users', ...)->middleware('role:admin'); Route::post('/users', ...)->middleware('permission:users.create'); Route::get('/settings', ...)->middleware('role_or_permission:admin|settings.edit');
Pipe-separated names mean any.
Blade
@role('admin') ... @endrole @permission('users.create') ... @endpermission @teamrole('owner') ... @endteamrole
Teams
$team = $user->createTeam('Acme'); $team->addMember($other, 'admin'); $user->switchTeam($team); $user->belongsToTeam($team); $user->isOwnerOf($team); $user->hasTeamRole($team, ['owner', 'admin']); $invitation = $user->inviteToTeam($team, 'jane@example.com', 'member'); $jane->acceptTeamInvitation($invitation->token);
Roles and permissions can be global or limited to a team:
$user->assignRole('editor', $team); $user->hasRole('editor', $team); Identity::setTeam($team); $user->hasPermissionTo('posts.publish'); Identity::forgetTeam();
When a current team is set (or Identity::setTeam() is used), checks use that team. Global roles still apply inside a team when identity.teams.include_global is true.
Artisan
| Command | Purpose |
|---|---|
identity:create-role {name} |
Create a role |
identity:create-permission {name} |
Create a permission |
identity:assign-role {email} {role} |
Assign a role (--team= optional) |
identity:create-team {name} --owner= |
Create a team |
identity:super-admin {email} |
Create or promote a super-admin |
identity:cache-reset |
Flush the identity cache |
Configuration
| Key | Default | Purpose |
|---|---|---|
defaults.guard |
web |
Guard stored on roles and permissions |
super_admin_role |
super-admin |
Role that bypasses permission checks (null to disable) |
teams.enabled |
true |
Team membership and team-scoped roles |
teams.include_global |
true |
Global roles apply inside a team |
wildcard |
true |
posts.* and * matching |
cache.ttl |
3600 |
Seconds (0 disables cache) |
auth.routes |
true |
Register /api/auth/* JWT routes |
auth.prefix |
api/auth |
Auth route prefix |
auth.guard |
api |
Laravel guard using the jwt driver |
auth.include_roles_in_token |
true |
Put roles and permissions in JWT claims |
Tables are prefixed with identity_ so they do not collide with other packages.
Events
RoleAssigned, RoleRemoved, PermissionAssigned, TeamCreated, TeamMemberAdded, TeamMemberRemoved.
Path repository (local package)
{
"repositories": [
{
"type": "path",
"url": "../laravel-identity"
}
]
}
composer require waventra/laravel-identity:@dev
Copyright and license
Copyright (c) 2026 Waventra. All rights reserved.
This software is free to use. You may not copy, modify, or sell it.
See LICENSE for the full terms.