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:1
Start with 2D
Implement
Identity and Principal on your existing user model. Issue tokens, wire middleware, ship.2
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.3
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.4
Optionally add HasType
If you need to branch on tenant category (e.g.
'staff' vs 'customer'), add HasType to AppTenant and implement getType(). Auth::type() starts returning the value immediately.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.