Complete knowledge and operation guide for Tyro - the Laravel authentication, authorization, and role-based access control package. Use this skill when working with Laravel projects that use Tyro, when setting up authentication/authorization, managing roles/privileges, using Tyro commands, middleware, Blade directives, or REST API endpoints. Trigger for any mention of Tyro, Laravel auth, RBAC, role management, privilege management, user suspension, or Laravel Sanctum integration with permissions.
This skill provides complete knowledge of Tyro - a comprehensive authentication, authorization, and role-based access control (RBAC) solution for Laravel 12 and 13.
Use this skill when:
Tyro provides:
Default seeded roles:
super-admin - Full system accessadministrator - Administrative accesseditor - Content managementuser - Standard registered usercustomer - Customer accountall - Universal access (wildcard)Default credentials (after seeding):
[email protected]tyrocomposer require hasinhayder/tyro
php artisan tyro:sys-install
This single command:
If you need more control:
# Install package
composer require hasinhayder/tyro
# Publish assets (optional)
php artisan vendor:publish --tag=tyro-config
php artisan vendor:publish --tag=tyro-migrations
# Run migrations
php artisan migrate
# Seed roles and privileges
php artisan tyro:seed-all --force
# Prepare User model
php artisan tyro:user-prepare
Your User model (default: App\Models\User) must use the HasTyroRoles trait:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
use HasinHayder\Tyro\Concerns\HasTyroRoles;
class User extends Authenticatable
{
use HasApiTokens, HasTyroRoles;
}
The trait provides ALL role/privilege methods. No other code changes needed.
# Full installation
php artisan tyro:sys-install
# Prepare User model with trait
php artisan tyro:user-prepare
# Show version
php artisan tyro:sys-version
# Show package info
php artisan tyro:sys-about
# Open documentation
php artisan tyro:sys-doc
# Open GitHub repo
php artisan tyro:sys-star
# Get Postman collection
php artisan tyro:sys-postman
# Create user with default role
php artisan tyro:user-create
# List all users
php artisan tyro:user-list
# List users with roles
php artisan tyro:user-list-with-roles
# Update user
php artisan tyro:user-update --user=1 --name="New Name"
# Delete user (prevents deleting last admin)
php artisan tyro:user-delete --user=1
# Suspend user (revokes all tokens)
php artisan tyro:user-suspend [email protected] --reason="Policy violation"
# Unsuspend user
php artisan tyro:user-unsuspend [email protected]
# List suspended users
php artisan tyro:user-suspended
# Show user's roles and privileges
php artisan tyro:user-roles --user=1
# List all roles
php artisan tyro:role-list
# List roles with privileges
php artisan tyro:role-list-with-privileges
# Create new role
php artisan tyro:role-create --name="Content Manager" --slug="content-manager"
# Update role
php artisan tyro:role-update --role="content-manager" --name="Content Editor"
# Delete role (protected roles cannot be deleted)
php artisan tyro:role-delete --role="content-manager"
# Assign role to user
php artisan tyro:role-assign --user=5 --role="editor"
# Remove role from user
php artisan tyro:role-remove --user=5 --role="editor"
# List users with specific role
php artisan tyro:role-users --role="editor"
# List all privileges
php artisan tyro:privilege-list
# Create privilege
php artisan tyro:privilege-create --name="Delete Articles" --slug="articles.delete"
# Update privilege
php artisan tyro:privilege-update --privilege="articles.delete" --name="Remove Articles"
# Delete privilege
php artisan tyro:privilege-delete --privilege="articles.delete"
# Attach privilege to role
php artisan tyro:privilege-attach --role="editor" --privilege="articles.publish"
# Detach privilege from role
php artisan tyro:privilege-detach --role="editor" --privilege="articles.publish"
# Show user's privileges
php artisan tyro:user-privileges --user=1
# Login (mint token)
php artisan tyro:auth-login [email protected]
# Create token without password
php artisan tyro:user-token --user=1
# Revoke specific token
php artisan tyro:auth-logout --token=xyz
# Revoke all user tokens
php artisan tyro:auth-logout-all --user=1
# Emergency: revoke ALL tokens
php artisan tyro:auth-logout-all-users --force
# Inspect token
php artisan tyro:auth-me --token=xyz
# Seed everything (roles, privileges, admin user)
php artisan tyro:seed-all --force
# Seed only roles
php artisan tyro:seed-roles --force
# Seed only privileges
php artisan tyro:seed-privileges --force
# Purge all roles
php artisan tyro:role-purge --force
# Purge all privileges
php artisan tyro:privilege-purge --force
# List audit logs
php artisan tyro:audit-list --limit=50
# Filter by event type
php artisan tyro:audit-list --event=role.assigned
# Purge old logs (respects retention_days config)
php artisan tyro:audit-purge --days=30
| Middleware | Alias | Behavior |
|---|---|---|
EnsureTyroRole | role | User must have ALL specified roles |
EnsureAnyTyroRole | roles | User must have ANY of the specified roles |
EnsureTyroPrivilege | privilege | User must have ALL specified privileges |
EnsureAnyTyroPrivilege | privileges | User must have ANY of the specified privileges |
TyroLog | tyro.log | Logs request/response for debugging |
use Illuminate\Support\Facades\Route;
// Require admin role only
Route::middleware(['auth:sanctum', 'role:admin'])
->get('admin/dashboard', [AdminDashboardController::class, 'index']);
// Require both admin AND super-admin
Route::middleware(['auth:sanctum', 'role:admin,super-admin'])
->delete('users/{user}', [UserController::class, 'destroy']);
// Allow editor OR admin
Route::middleware(['auth:sanctum', 'roles:editor,admin'])
->post('articles/publish', [ArticleController::class, 'publish']);
// Require specific privilege
Route::middleware(['auth:sanctum', 'privilege:reports.run'])
->get('reports', [ReportController::class, 'index']);
// Allow any of multiple privileges
Route::middleware(['auth:sanctum', 'privileges:billing.view,reports.run'])
->get('dashboard', [DashboardController::class, 'index']);
// Combine multiple middleware
Route::middleware(['auth:sanctum', 'role:admin', 'privilege:users.delete'])
->delete('users/{user}', [UserController::class, 'destroy']);
// Audit sensitive routes
Route::middleware(['auth:sanctum', 'role:admin', 'tyro.log'])
->post('roles', [RoleController::class, 'store']);
| Directive | Purpose |
|---|---|
@userCan('ability') | Check if user has role OR privilege |
@hasRole('slug') | Check if user has specific role |
@hasAnyRole('slug1','slug2') | Check if user has ANY listed roles |
@hasAllRoles('slug1','slug2') | Check if user has ALL listed roles |
@hasPrivilege('slug') | Check if user has specific privilege |
@hasAnyPrivilege('slug1','slug2') | Check if user has ANY listed privileges |
@hasAllPrivileges('slug1','slug2') | Check if user has ALL listed privileges |
All directives return false if no user is authenticated.
@hasRole('admin')
<div class="admin-panel">
<h2>Admin Dashboard</h2>
</div>
@endhasRole
@hasAnyRole('editor', 'author')
<div class="content-tools">
<a href="/articles/create">New Article</a>
</div>
@endhasAnyRole
@hasAllRoles('admin', 'super-admin')
<button class="btn-danger">Delete User</button>
@endhasAllRoles
@hasPrivilege('articles.publish')
<button class="btn-success">Publish</button>
@endhasPrivilege
@hasAnyPrivilege('articles.edit', 'articles.delete')
<div class="article-actions">
@hasPrivilege('articles.edit')
<button>Edit</button>
@endhasPrivilege
@hasPrivilege('articles.delete')
<button>Delete</button>
@endhasPrivilege
</div>
@endhasAnyPrivilege
@userCan('admin')
<!-- Checks both role 'admin' and privilege 'admin' -->
<div>Admin content</div>
@enduserCan
@hasRole('subscriber')
<div class="premium-content">
{{ $article->premium_content }}
</div>
@else
<p>This is premium content. <a href="/subscribe">Subscribe now</a></p>
@endhasRole
All routes are prefixed by config('tyro.route_prefix') (default: api)
Include Bearer token in headers:
Authorization: Bearer {token}
GET /api/tyro # Package information
GET /api/tyro/version # Version number
POST /api/login # Authenticate, receive token
POST /api/users # Register new user
GET /api/me # Current user info
PUT /api/users/{user} # Update self
POST /api/users/{user} # Update self
DELETE /api/users/{user} # Delete own account
User Management:
GET /api/users # List all users
GET /api/users/{user} # Get user details
DELETE /api/users/{user} # Delete user (admin)
POST /api/users/{user}/roles # Assign role
DELETE /api/users/{user}/roles/{role} # Remove role
POST /api/users/{user}/suspend # Suspend user
DELETE /api/users/{user}/suspend # Unsuspend user
Role Management:
GET /api/roles # List roles
POST /api/roles # Create role
GET /api/roles/{role} # Get role details
PUT /api/roles/{role} # Update role
DELETE /api/roles/{role} # Delete role
GET /api/roles/{role}/users # Users with role
GET /api/roles/{role}/privileges # Role privileges
POST /api/roles/{role}/privileges # Attach privilege
DELETE /api/roles/{role}/privileges/{privilege} # Detach
Privilege Management:
GET /api/privileges # List privileges
POST /api/privileges # Create privilege
GET /api/privileges/{privilege} # Get details
PUT /api/privileges/{privilege} # Update
DELETE /api/privileges/{privilege} # Delete
Audit Logs:
GET /api/audit-logs # List audit logs
# Login
curl -X POST http://localhost/api/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"tyro"}'
# Register
curl -X POST http://localhost/api/users \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"[email protected]","password":"password123","password_confirmation":"password123"}'
# Get roles (authenticated)
curl http://localhost/api/roles \
-H "Authorization: Bearer YOUR_TOKEN"
# Assign role to user
curl -X POST http://localhost/api/users/5/roles \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role_id":4}'
$user->roles(): BelongsToMany
// Eloquent relationship to roles
$user->assignRole(Role $role): void
// Attach a role to user
$user->removeRole(Role $role): void
// Detach a role from user
$user->hasRole(string $role): bool
// Check if user has role (supports wildcard '*')
$user->hasRoles(array $roles): bool
// Check if user has ALL roles
$user->tyroRoleSlugs(): array
// Get all role slugs as array (cached)
// Returns: ['admin', 'editor']
$user->privileges(): Collection
// Get all unique privileges from user's roles
$user->hasPrivilege(string $privilege): bool
// Check if user has specific privilege
$user->hasPrivileges(array $privileges): bool
// Check if user has ALL privileges
$user->tyroPrivilegeSlugs(): array
// Get all privilege slugs as array (cached)
// Returns: ['articles.create', 'articles.publish']
$user->can($ability, $arguments = []): bool
// Unified permission check:
// 1. Checks privileges first (exact match)
// 2. Then checks roles
// 3. Falls back to Laravel Gate
$user->suspend(?string $reason = null): void
// Suspend user, store reason, revoke all tokens
$user->unsuspend(): void
// Remove suspension
$user->isSuspended(): bool
// Check if user is suspended
$user->getSuspensionReason(): ?string
// Get suspension reason
php artisan tyro:publish-config --force
Edit config/tyro.php:
return [
// Models
'models' => [
'user' => App\Models\User::class,
'privilege' => HasinHayder\Tyro\Models\Privilege::class,
],
// Table names
'tables' => [
'users' => 'users',
'roles' => 'roles',
'pivot' => 'user_roles',
'privileges' => 'privileges',
'role_privilege' => 'privilege_role',
],
// Route configuration
'route_prefix' => 'api',
'guard' => 'sanctum',
'disable_api' => env('TYRO_DISABLE_API', false),
// Default role for new users
'default_user_role_slug' => 'user',
// Protected roles (cannot be deleted)
'protected_role_slugs' => ['super-admin', 'administrator', 'all'],
// Single session login
'delete_previous_access_tokens_on_login' => false,
// Cache configuration
'cache' => [
'enabled' => true,
'store' => null, // null = default cache store
'ttl' => 300, // seconds, null = indefinite
],
// Audit trail
'audit' => [
'enabled' => true,
'retention_days' => 30,
],
];
# Disable CLI commands
TYRO_DISABLE_COMMANDS=true
# Disable REST API
TYRO_DISABLE_API=true
# Password validation
TYRO_PASSWORD_MIN_LENGTH=8
TYRO_PASSWORD_MAX_LENGTH=64
TYRO_PASSWORD_REQUIRE_NUMBERS=true
TYRO_PASSWORD_REQUIRE_UPPERCASE=true
TYRO_PASSWORD_REQUIRE_LOWERCASE=true
TYRO_PASSWORD_REQUIRE_SPECIAL_CHARS=true
TYRO_PASSWORD_REQUIRE_CONFIRMATION=true
TYRO_PASSWORD_CHECK_COMMON=true
TYRO_PASSWORD_DISALLOW_USER_INFO=true
roles:
id - Primary keyname - Display nameslug - Unique identifiercreated_at, updated_atprivileges:
id - Primary keyname - Display nameslug - Unique identifierdescription - Optional descriptioncreated_at, updated_atuser_roles (pivot):
id - Primary keyuser_id - Foreign key to usersrole_id - Foreign key to rolesunique(user_id, role_id)created_at, updated_atprivilege_role (pivot):
id - Primary keyprivilege_id - Foreign key to privilegesrole_id - Foreign key to rolesunique(privilege_id, role_id)created_at, updated_attyro_audit_logs:
id - Primary keyuser_id - Actor (nullable)event - Event nameauditable_type - Target model typeauditable_id - Target model IDold_values - JSON (before state)new_values - JSON (after state)metadata - JSON (context)created_atusers (extended):
suspended_at - Timestamp or nullsuspension_reason - VARCHAR or nullTyro uses a two-tier caching system:
tyro:user:{user_id}:rolestyro:user:{user_id}:privilegesAutomatic on:
Manual:
$user->clearTyroCache(); // Clear for specific user
| Category | Events |
|---|---|
| User | role.assigned, role.removed, suspended, unsuspended |
| Role | created, updated, deleted |
| Privilege | created, updated, deleted, attached, detached |
# CLI
php artisan tyro:audit-list --limit=100 --event=role.assigned
# API
GET /api/audit-logs?limit=100&event=role.assigned
# Create blog-specific roles
php artisan tyro:role-create --name="Author" --slug="author"
php artisan tyro:role-create --name="Reviewer" --slug="reviewer"
# Create privileges
php artisan tyro:privilege-create --name="Create Articles" --slug="articles.create"
php artisan tyro:privilege-create --name="Edit Own Articles" --slug="articles.edit.own"
php artisan tyro:privilege-create --name="Edit Any Article" --slug="articles.edit.any"
php artisan tyro:privilege-create --name="Publish Articles" --slug="articles.publish"
php artisan tyro:privilege-create --name="Delete Articles" --slug="articles.delete"
# Attach to author
php artisan tyro:privilege-attach --role="author" --privilege="articles.create"
php artisan tyro:privilege-attach --role="author" --privilege="articles.edit.own"
# Attach to reviewer
php artisan tyro:privilege-attach --role="reviewer" --privilege="articles.edit.any"
php artisan tyro:privilege-attach --role="reviewer" --privilege="articles.publish"
// Authors can create
Route::post('articles', [ArticleController::class, 'store'])
->middleware(['auth:sanctum', 'privilege:articles.create']);
// Reviewers can publish
Route::post('articles/{article}/publish', [ArticleController::class, 'publish'])
->middleware(['auth:sanctum', 'privilege:articles.publish']);
# Check if commands are disabled
grep TYRO_DISABLE_COMMANDS .env
# Re-enable by removing or setting to false
TYRO_DISABLE_COMMANDS=false
# Check if API is disabled
grep TYRO_DISABLE_API .env
# Re-enable
TYRO_DISABLE_API=false
# Check route list
php artisan route:list --path=tyro
# Clear application cache
php artisan cache:clear
# Clear specific Tyro cache
php artisan tyro:cache-clear
# Check if suspended
php artisan tyro:user-suspended
# Unsuspend if needed
php artisan tyro:user-unsuspend [email protected]
# Re-run migrations
php artisan migrate:fresh --seed
# Or just Tyro migrations
php artisan migrate --path=vendor/hasinhayder/tyro/database/migrations
super-admin, administrator, all cannot be deleted* grants universal access