2D mode: identity is the principal
In 2D mode, one model implements bothIdentity and Principal. The logged-in user is the actor on whose behalf every request runs. There is no separate membership, role model, or tenant record — the user themselves is the scope of each action.
Auth::identity() and Auth::principal() return the same object in this mode. Auth::tenant() returns null because the principal’s getTenant() returns null. Auth::type() likewise returns null.
When to use 2D mode:
- Simple single-tenant applications where every user has the same scope
- Machine-to-machine APIs where the authenticating service account is also the actor
- Internal tools without multi-tenant isolation requirements
- Early-stage products where you want to get auth working now and grow the model later
3D mode: identity, principal, and tenant are separate
In 3D mode, three separate models carry the three concerns:- An Identity model represents the human (or service account) — it implements
IdentityandHasPrincipals - A Principal model represents the tenant-scoped actor — typically a membership or role row — and implements
Principal - A Tenant model represents the isolation boundary and implements
Tenant
Auth::identity() returns the human. Auth::principal() returns the membership they are currently acting as (pinned by the pid claim in the access token). Auth::tenant() returns the tenant that membership belongs to.
When to use 3D mode:
- Multi-tenant SaaS where users belong to one or more workspaces, organizations, or teams
- Apps where a single login should be able to switch between different tenant contexts
- Platforms with per-tenant roles or permissions that differ from the user’s global account
- Any domain where “acting on behalf of” is a first-class concept
Side-by-side comparison
- 2D mode
- 3D mode
One model handles everything. Point At runtime:
auth.providers.users.model at it and you’re done.Starting with 2D and growing into 3D
The two modes are additive — you do not need to re-platform to move from 2D to 3D. The guards change nothing; only your model implementations change. Here is a safe migration path:Start with 2D
Implement
Identity and Principal on your existing user model. Issue tokens, wire middleware, ship.Add a membership model
When you need per-tenant roles, create an
AppMembership model that implements Principal. Keep your user model implementing Identity but add HasPrincipals to it. The guards pick up the new contract automatically.Add a tenant model
Create an
AppTenant model that implements Tenant. Wire AppMembership::tenant() to return it. Auth::tenant() now works without any change to your guards or middleware.Existing access tokens issued in 2D mode continue to work during and after migration. The
pid claim in a 2D token contains the identity’s own identifier. When you switch to 3D, new tokens carry a membership identifier as pid. Old tokens will fail to resolve a Principal if the old pid no longer matches a membership row — issue a fresh token to users after migration.2D setup guide
Step-by-step walkthrough for wiring up a single-model identity that acts as its own principal.
3D setup guide
Step-by-step walkthrough for separating identity, membership, and tenant into three models.