Implementing Team-Based Authorization in Laravel Jetstream
Learn how to implement team-based authorization in Laravel Jetstream using scoped Gates and the currentTeam mechanism to prevent data leakage between organizations.
18 Feb 2026, 19:29 UTC

The Problem: Scoping Data to Multiple Organizations
\nMost applications start with a simple 1:1 relationship between a user and their data. However, as an application grows into a B2B or collaborative tool, you need a way to group users into teams where permissions vary depending on which team the user is currently acting within. Without a structured approach, developers often struggle with \"leaky\" authorization, where a user might accidentally access data from Team A while they are switched to Team B.
\nThe takeaway: Laravel Jetstream solves this by combining a many-to-many database relationship with a session-based currentTeam scope and Laravel Gates. This ensures that authorization checks are not just based on who the user is, but which team context they are operating in.
Enabling the Team Mechanism
\nJetstream's team functionality is optional. To activate the underlying schema and logic, you must modify the configuration file. This triggers the creation of the teams table and the team_user pivot table, which tracks the relationship between users and their respective roles.
// config/jetstream.php\n\n'features' => [\n Features::teams(),\n],\nAfter updating the config, run the migrations from your terminal to apply the schema changes:
\n# Run as the application user with database permissions\nphp artisan migrate\n\nImplementing Team-Scoped Authorization
\nOnce teams are enabled, you should not check permissions globally. Instead, use Laravel Gates to verify the user's role within their active team. Jetstream provides a Jetstream::user()->currentTeam() helper to identify the active context.
Below is a configuration for a custom Gate in the AuthServiceProvider that ensures a user can only edit a project if they have the \"admin\" role within the team that owns that project.
// app/Providers/AuthServiceProvider.php\n\nuse Illuminate\Support\Facades\Gate;\nuse App\\Models\\Project;\nuse App\\Models\\User;\n\npublic function boot()\n{\n Gate::define('edit-project', function (User $user, Project $project) {\n // 1. Verify the user is currently switched to the team that owns the project\n if ($user->currentTeam->id !== $project->team_id) {\n return false;\n }\n\n // 2. Verify the user has the required role within that specific team\n return $user->hasTeamRole($project->team_id, 'admin');\n });\n}\nIn your controller, you can then apply this check using the authorize method:
public function update(Request $request, Project $project)\n{\n $this->authorize('edit-project', $project);\n // Proceed with update logic\n}\n\nLimitations and Common Pitfalls
\nThe \"Active Team\" Trap
\nA common mistake is relying solely on $user->currentTeam without validating that the requested resource actually belongs to that team. If a user manually changes a URL ID to a project belonging to a different team, the currentTeam check will fail, but if you forget to compare the project->team_id against the currentTeam->id, you may inadvertently allow access based on a global role.
Production Migration Risks
\nEnabling teams after your application is already in production requires caution. Jetstream adds a current_team_id column to the users table. If you have existing users, they will have a null value for this column, which will cause errors in the UI and authorization gates until every user is assigned to at least one team.
Overriding the Team Model
\nIf you customize the Team model (e.g., adding custom attributes), you must ensure that the Jetstream Action classes (like CreateNewTeam) are updated. If the Action class attempts to create a team using a method that conflicts with your model's new requirements, the team creation process will throw a runtime exception.
Verification and Testing
\nTo verify the implementation is working correctly, perform the following checks:
\n- \n
- Schema Check: Confirm that the
team_usertable exists and containsteam_id,user_id, androlecolumns. \n - Context Switch: Create two teams. Join both. Switch to Team A and attempt to access a resource owned by Team B; the application should return a 403 Forbidden response. \n
- Role Escalation: Assign a \"member\" role to a user. Attempt to trigger the
edit-projectgate; it should fail. Change the role to \"admin\" and verify the gate now returns true. \n
Rollback Procedure
\nIf the team feature causes instability, you can revert the state by removing the feature from config/jetstream.php and rolling back the migrations. Warning: This will permanently delete all team membership and team data.
# Rollback the migrations that added team tables\nphp artisan migrate:rollback0 replies
A thoughtful contribution can make all the difference. Be the first to share one.