Separating people from accounts: why a login isn't a person
In a four-part series earlier this year I walked through why the authenticated actor and the account or person they're acting within are often not the same thing, and how Auth0 and Auth0 FGA model that gap with a delegate relation and a not_expired condition. That series treated the authenticated login as the whole security principal - Auth0 authenticates it, Auth0 FGA decides what it's allowed to touch, and that was the end of the story.
It's not quite the end of the story. The same real person routinely holds more than one login - a personal account and a separate workplace account, say - and those two logins should never be treated as interchangeable just because the same human is behind both. If your authorisation model can't say "these are two different security principals, but I can prove they're the same real person when it matters," you either merge logins that shouldn't share a blast radius (a personal social login and a corporate SSO session ending up as one Auth0 user via account linking) or you bolt a fragile, ad hoc "is this the same person" check onto the side of your application, disconnected from the relationship graph that's meant to be answering exactly this kind of question.
In this post I will detail a small, deliberate extension to the delegated-access model from that series: a person type that sits above the login, a persona type for the login itself (what the earlier series simply called user), and the same separation applied one level up to companies via a business type. I have built out the full model along with a dozen worked industry examples in a public repository, which this post and its follow-ups will walk through in detail.
Why one more type
The original model had one principal type, user, and everything - ownership, delegation, approval - hung off it directly. That's the right level of abstraction for "who is acting right now", and it's still the right level for that question. It stops being the right level the moment you need to ask a second question: "is the person behind this login the same person behind that other login?"
That second question comes up constantly once you look for it. A carer might have a personal healthcare login and, separately, be employed by a home-care agency with its own credentials. A company director might sit on the board of two unrelated companies under two entirely different logins. An accountant might be an ordinary retail customer of the same platform they administer professionally for a client. None of these are edge cases - they're the normal shape of how real people relate to the systems they touch, and none of them are answerable if "the user" is the only concept your authorisation model has.
person is the answer to that second question, and it's deliberately inert. A person never holds a role. No owner, admin, viewer, or delegate relation is ever granted to a person directly - it exists purely so a persona can be resolved back to a real human when that matters for audit, compliance, or conflict-of-interest checks, without an authorisation check ever needing to ask "which person is this?" in order to make a decision.
The model: person, persona, business
The core of the model is small enough to read in one sitting:
type person
type persona
relations
define individual: [person]
type business
relations
define owner: [persona]
define admin: [persona] or owner
define staff: [persona] or admin
persona.individual is a direct-only relation - a fact about who controls the login, never a grant of access. One person can control several personas, each carrying its own, entirely unrelated set of roles. The diagram below shows Alex, who has exactly this shape: a personal login for his family's streaming subscription, and a completely separate workplace login for his employer's corporate news subscription.

Neither of Alex's personas has any bearing on the other. His personal login can't touch the corporate subscription, and his workplace login can't touch the family plan - not because of an access-control rule that says so, but because there is no tuple connecting them at all. The only thing connecting persona:alex-streamflix and persona:alex-northwind in the entire relationship graph is that both resolve back to the same person:alex via individual, and no authorisation check ever traverses that relation.
business extends the identical principle one level up. A company is a real entity, but like person, it's never itself a security principal - no relation on a service or an account is ever granted to a bare business object. Only its staff userset (business#staff, which already includes the business's own admins and owner through its own concentric chain) is ever granted a role or a delegation. That means a role can be handed to a business's whole current roster in one tuple, and it's revoked for an individual automatically the moment they leave the staff list - no per-employee cleanup required.
Roles, delegation, and something in between
Once you have persona as the thing that actually holds access, the question of how it holds access splits cleanly into three shapes that are easy to flatten into one "shared access" feature if you're not paying attention:
- A role (
owner,admin,viewer,member) is a standing relationship. It doesn't expire on its own - it ends when the tuple is removed. Joint ownership, permanent co-administration, a professional engagement with an accountant or a law firm: all roles, all ended by explicit instruction. - A delegation (
delegate) is temporary by construction. Everydelegatetuple carries anot_expiredcondition. It's for someone acting on behalf of someone else for a bounded period that's expected to end on its own: a carer during a recovery, a power of attorney under periodic review, a friend covering an account while its owner travels. - Temporary elevation looks like a delegation but isn't one.
adminin this model accepts a time-bounded grant alongside the permanent kind - the same relation, held either way. Someone covering for a colleague on leave isn't acting on their behalf; they hold the elevated role themselves, in their own name, just for a bounded time. It's a role with an expiry attached, not a delegation.
The distinction matters because getting it wrong has a direction of failure. Model a genuine delegation as a permanent role and you've handed out standing access that nobody remembers to revoke. Model a genuine role as a delegation and you've quietly restricted someone (a joint account holder, a company director) to view-only access when they should have full authority, because this model's delegate relation deliberately never resolves into can_manage - only can_view. That last rule is itself deliberate: whether a delegated relationship also authorises a specific manage-level action is left to the application, checked against the delegation's own recorded scope, rather than encoded as a growing list of FGA relations.
What a persona actually is
Everything above uses readable shorthand - persona:alex-streamflix - for what a real deployment actually writes: the sub claim from an Auth0-issued token, verbatim. Auth0's sub (identical to user_id in the Management API) is a compound string, <connection-strategy>|<identifier> for a direct connection or <connection-strategy>|<connection-name>|<idp-user-id> for an enterprise connection federated to an external IdP:
| Connection type | Format | Example |
|---|---|---|
| Database (email/password) | auth0|<id> | auth0|65f2b3c4d5e6f7a8b9c0d1e2 |
| Social (Google) | google-oauth2|<id> | google-oauth2|109876543210987654321 |
| SAML enterprise connection | samlp|<connection>|<idp-id> | samlp|northwind-okta|alex.smith@northwind.com |
| OIDC enterprise connection | oidc|<connection>|<idp-id> | oidc|northwind-okta|alex.smith@northwind.com |
| Azure AD / Entra ID | waad|<connection>|<idp-id> | waad|northwind-aad|alex.smith@northwind.com |
For Alex's personal login on Streamflix's tenant, that's persona:google-oauth2|109876543210987654321. For his workplace login, Northwind Ltd has its own enterprise connection federated via OIDC to its Okta tenant, and the token Auth0 issues on sign-in carries sub: oidc|northwind-okta|alex.smith@northwind.com and, if the tenant uses Auth0 Organizations, an org_id claim that a post-login Action can resolve to a specific business:northwind object.

Two things happen at that workplace login that don't happen for the personal one: a brand-new persona object with no relationship at all to Alex's personal one, and a resolution from org_id (or the connection name) to business:northwind - a mapping decision the application makes once, typically in a post-login Action, since Auth0 has no native concept of what business:northwind means.
Why not just use Account Linking
Auth0 already has a feature for tying multiple logins to one person - Account Linking. It's the wrong tool for this particular problem, and it's worth being explicit about why: Account Linking merges identities into a single primary Auth0 user, one user_id, one set of tokens, with the other connections listed in that one profile's identities array. That collapses exactly the separation this model exists to preserve. If Alex's personal and workplace logins were account-linked, he'd have one sub, one session, and one blast radius - compromising his personal Google login would compromise his employer's subscription too.
person and individual solve the same underlying problem - "these logins belong to the same real human" - one layer up, without merging anything. Alex keeps two entirely separate Auth0 users, two entirely separate sessions, two entirely separate tokens. The application is the only thing that knows they're the same person, and it only uses that knowledge for what person is for in this model: audit, compliance, and conflict-of-interest resolution, never for authorising a request.
Auth0 has no built-in concept of "the same verified human across unrelated logins" - the application has to establish it, typically off the back of an identity-verification session that anchors the person record, or through attribute-matching that always requires an explicit confirmation step before the individual tuple is written. Either way, the tuple itself is trivial once that decision has been made:
- user: person:alex
relation: individual
object: persona:oidc|northwind-okta|alex.smith@northwind.comNext Steps
This is the first of a short series extending the delegated-access model with a person/persona separation, a business type for the same idea applied to companies, and a handful of things I didn't originally plan for - AI agents as delegated actors, identity verification levels, and the gap between a delegation being requested and being approved. The full model, a dozen worked industry examples, and the accompanying diagrams are all in a public repository if you want to run the tests yourself before the next post walks through them.