Hosted Tenant Identity Is Seaworthy
Hosted Tenant Identity Is Seaworthy
Multi-tenancy is easy to fake.
You can put an organization ID on a record, add an organization picker to a dashboard, and call it done. That gets you a demo. It does not get you a safe shared identity system. The hard part begins when the same browser, the same person, and the same upstream sign-in provider can all touch more than one tenant.
That is the work we have been finishing in M7 Identity.
The hosted tenant path now has the shape it needs to serve as a real platform boundary: tenant accounts have their own profile and identity records, hosted sign-in keeps remembered accounts within the relevant tenant, federation creates tenant-native identities, and the token and UserInfo paths know the difference between a consumer, a tenant member, and a machine client.
This is not a claim that identity work is ever “done.” It is a claim that the tenant path is now seaworthy: it has a coherent hull, the seams have been tested, and the next work can be improvements instead of structural rescue.
The tenant is the boundary
The central rule is simple: a tenant member is not just a consumer user with an organization field bolted on afterward.
M7 separates the root identity credential from the persona that uses it. A consumer identity can provide a password credential to a tenant member when the tenant explicitly links it, but the tenant account remains its own account. Its name, profile, email addresses, groups, status, and organization membership belong to the tenant.
That keeps the useful part of shared ownership without turning the entire tenant system into a shadow copy of consumer accounts.
It also makes the password-linking rule intentionally narrow. A linked consumer account proves the password. It does not make the consumer profile, email, groups, or federation connections leak into the tenant account.
Remembered sessions now have a home
The browser is where multi-tenant identity systems tend to get sloppy. If a person signs into two tenants from the same browser, a shared remembered-session list can accidentally become an account discovery feature—or worse, an account selection problem.
M7's server-side session records now carry the host that issued them. A session carrier from one SSO host cannot be replayed against another host simply because someone copied a key or constructed a request outside a browser.
The session model also carries a principal type and enough public metadata for useful operations: labels, tenant organization ID where applicable, first and last login IP, and browser user agent. The encrypted session payload stays private; the lightweight public data makes the session table useful for auditing and support.
For tenant login, remembered accounts are filtered by organization. A person using Tenant A sees Tenant A's remembered accounts, not every tenant account that happens to exist in the same browser. Root tenant sessions are also bound to the canonical organization and tenant-member ID that created them.
That is the important distinction: cookie isolation is helpful, but it is not the whole security model. The server still checks the host and tenant lineage.
Federation remains on the stable M7 trunk
Federated providers need a stable client and callback surface. A future tenant may use a wildcard host, a customer host, or another deployment shape entirely; GitHub and Discord should not have to understand that topology.
So the upstream federation exchange stays on the stable sso.user.m7.org trunk. Once a provider has authenticated the person, M7 carries the tenant context back to the specific hosted tenant origin that began the request. The return origin is captured dynamically, not hard-coded as one global tenant hostname.
More importantly, tenant federation does not look through a consumer user's federated identities and guess where to attach them. A successful federated tenant signup or claim creates and uses the tenant's own client_identity record and shadow identity. The tenant owns that linkage.
This is cleaner than trying to treat a consumer federation record as portable membership proof. It avoids a pile of ambiguous questions later: which tenant should receive an upstream identity, whether an existing consumer connection can be rebound, and which persona the provider actually authenticated for the current login.
Tokens now state what they are for
Access tokens need to be boringly legible. They should not require an application to infer whether it received a human consumer session, a tenant member session, or a machine credential from a pile of optional claims.
M7 now uses principal_type to make that distinction explicit:
consumerfor an M7 consumer account;tenantfor a tenant member account; andoauth_clientfor a machine credential.
For a tenant session, the token carries the root identity ID, the canonical tenant-member subject, the tenant member handle, and a compact organization reference. It does not carry a full organization record, a full profile, or every group membership. Those details belong on the UserInfo/API side, where scopes can govern their release and responses can grow without bloating every access token.
The same discipline applies to tenant group data: group membership is active-only and remains a compact authorization concern, not a serialized tenant directory in every token.
Hosted signup is policy, not a fork
Hosted sign-up has become a tenant capability instead of a hard-coded consumer-shaped page.
Tenants can control whether hosted public registration and federated sign-in are available. They can choose whether a new password account is immediate, requires email verification, or waits for administrator approval. The policy applies when the account is created; changing it later does not silently upgrade older pending accounts.
The password form itself follows the active policy:
- Email is forced on and required for email-verification activation.
- Otherwise, email can be required, optional, or not collected.
- Display name can be required, optional, or not collected. Optional and uncollected names normalize to the username.
Those rules apply only to the direct username-and-password path. Federated signup uses the provider-backed claim flow rather than pretending it is the same operation.
We also added tenant-hosted branding: display names, hero and compact logo treatment, hosted links, and color values that can layer from tenant policy down to a particular hosted application. The M7 Identity attribution remains visible, while the tenant gets a sign-in surface that looks like its own product instead of a foreign control panel dropped into the middle of its experience.
A deliberate remaining gap
Every hosted signup now requires acceptance of the M7 Terms and Privacy Policy, with an optional tenant terms/privacy rider when the tenant enables it. The checkbox is enforced on the server for password and federated signup paths.
But acceptance is not yet stored as an auditable record. We do not currently persist the acceptance time, document version or URL snapshot, tenant rider, IP, or user agent. That means the feature is valid as a signup gate, but not yet as a durable legal-consent ledger.
That should be the next step for this particular slice of work. It is bounded, clear, and importantly no longer entangled with tenant session design or federated identity construction.
What “seaworthy” means
It means the actual lines are now in the right places.
A tenant account has a tenant identity. A federated account becomes a tenant identity rather than a borrowed consumer identity. A remembered session belongs to a host and, for hosted tenant login, is visible only in the relevant organization. A token says what principal it represents. Rich profile and membership information has a proper retrieval path. And the hosted onboarding experience can be configured without splitting the security model into a pile of special pages.
There is still work ahead: wildcard/custom domains, consent records, more verification methods, and the normal hardening that follows real use. But those are now follow-on tasks.
The core tenant identity ship floats.