On a first federated sign-in, what decides whether an account is created and which tenant it binds to?
answer
- policy per tenant, not per product
- three answers: open, invited, none
- tenant comes from the connection
- issuer plus subject, not the address
- insert and catch the conflict
basics
~20 sThe tenant's admission policy decides: create anyone the provider vouches for, create only pre-invited people, or create nobody. The tenant binding comes from the connection the sign-in was routed through, never from an attribute the provider sent.
solid answer
~40 sTwo settings, both per tenant. The **admission policy** decides whether a person the customer's identity provider vouches for gets an account at all: open self-service (anyone it authenticates), invite-only (match an existing invitation, otherwise refuse with a distinct, actionable error), or closed (accounts arrive only through the provisioning channel). The **tenant binding** is not a decision at all - it is taken from the connection the sign-in arrived on, which is already tenant-scoped, rather than from anything in the message, because a self-asserted tenant lets one customer's provider place accounts inside another's. The join key is the issuer-and-subject pair, not the address, so the row survives a person changing their name or address. Create with a unique constraint on that pair and let the database settle concurrent first sign-ins.
code
pseudocode · 24 lineson_first_sign_in(connection, assertion):
subject = assertion.attribute(connection.attributeNames.subject)
if subject is empty:
return FAIL("no stable subject identifier") # fatal: no join key
existing = accounts.find(connection.id, subject)
if existing is not null:
return refresh_mutable_attributes(existing, assertion)
switch connection.admissionPolicy:
case "closed": return DENY("not provisioned")
case "invite-only":
invite = invites.find(connection.tenantId, assertion.address)
if invite is null: return DENY("no invitation for this tenant")
account = new Account(
tenantId = connection.tenantId, # from the connection, never the message
issuer = connection.issuer,
subject = subject)
try:
accounts.insert(account) # unique (connection, subject)
catch UniqueViolation:
account = accounts.find(connection.id, subject) # the racing sign-in won
return accountgo deeper
Know that a first federated sign-in may or may not create an account, and that the customer decides which. Authenticating successfully and being allowed into the product are two separate outcomes.
Explain the three admission policies and the create path: where the tenant comes from, why the join key is the issuer-and-subject pair rather than the address, and how a unique constraint settles two simultaneous first sign-ins.
Show the customer-shaped detail: attribute names read from the connection, required versus optional attributes, a refusal message that distinguishes 'not authenticated' from 'not admitted', and the alert when an asserted tenant disagrees with the connection.
Treat admission as the contract you sell. Open self-service against a customer's whole directory is a licensing and data-retention commitment as much as a technical one; invite-only pushes work onto their administrators. Say which default you ship and why.
## Three admission policies, chosen per customer A housekeeping app rolled out across a hotel group will meet all three inside the first year, which is why this is configuration rather than a product decision: | Policy | Who gets an account | Where it fits | |---|---|---| | Open self-service | Anyone the brand's identity provider authenticates | A brand whose directory already contains exactly the people who should have the app | | Invite-only | Only a person matching an invitation already recorded for that tenant | A brand whose directory contains thousands of staff and whose licence covers eighty | | Closed | Nobody; accounts exist beforehand or not at all | A brand that pushes its roster through the provisioning channel and wants sign-in to be a pure authentication step | Under invite-only and closed, the refusal matters as much as the policy. A person who authenticated correctly and still cannot in is not looking at a broken sign-in - tell them, distinctly from a failed authentication, that their brand has not granted them access and who to ask. An unhelpful refusal here generates more support load than the feature saves. ## Binding the account to a tenant The new row's tenant is taken from **the connection the sign-in was routed through**. That connection is already bound to one tenant, and the domain that routed to it was verified by that tenant. Any other source is weaker: - An attribute in the message is written by the customer's provider, so a customer could place accounts in a tenant that is not theirs. - The typed address is unverified input and may not even be the address the provider asserted. - An invitation's tenant is a reasonable cross-check under invite-only, but it should *agree* with the connection rather than replace it; a disagreement is a refusal and an alert, not a merge. ## The enterprise-shaped parts of the create The generic shape of first-login creation is well known. What differs when the other side is a customer's directory rather than a consumer provider: - **Attributes are named by the customer.** One brand sends the display name as one attribute, another as a different one, a third sends a first and last name separately. Read them through the per-connection attribute-name map rather than a fixed set of names. - **Attributes go missing.** Decide per attribute whether it is required to create at all. Missing the subject identifier is fatal. Missing a display name is not: create the account and show the address until the next sign-in fills it. What you must not do is create a half-account that the rest of the product cannot render. - **The join key is the issuer-and-subject pair**, held with the connection, not the address. Addresses change, are reassigned to new staff, and are the one attribute a customer's directory will happily reuse. A `sub` or a `NameID` interpreted inside its issuer's namespace is the stable identity; `email_verified` from a consumer provider is not a substitute for that stability. - **Later sign-ins refresh mutable attributes.** The name and the groups are the provider's to change; the account's own local state is not. ## Two first sign-ins at once A person who double-taps a launch tile, or a browser that retries, produces two first sign-ins racing into the create path. A check-then-insert has a window between the two statements and loses that race, occasionally, in production, on a Monday morning when a whole brand signs in at once. Put a unique constraint on (connection, subject), insert optimistically, catch the conflict, and re-read the winning row. The database is the only component in the path that can settle it. ## What this leaf does not decide If the subject arriving is new but the *person* is not - they already have a local account from before the brand federated - that is a join between two identities, and it is deliberately not the create path's job to guess. The create path handles a subject it has never seen. Matching an arriving identity onto an existing account, and merging two accounts that turn out to be one person, are separate work with their own consent and re-authentication rules.
- A brand's provider asserts a tenant identifier as an attribute. Should you use it?No - at most as a cross-check. That attribute is written by the customer, so honouring it lets one brand's provider create accounts inside another brand's tenant. The connection the sign-in was routed through already carries a tenant, established when that customer verified its domain and its connection was made live. If the asserted value disagrees with the connection, refuse the sign-in and alert; do not reconcile it silently.
- Why key the account on the issuer-and-subject pair instead of the email address?Because an address is a mutable attribute that customers reassign. People marry, brands rename their domains, and a departed employee's address is handed to their replacement - at which point address-keyed matching hands the new person the old person's account. The subject identifier is stable within its issuer's namespace, so the pair identifies one human across all of that.
- The provider sends no display name for a new person. Do you refuse the sign-in?Not for that. Split required from optional per attribute: a missing subject identifier is fatal because there is no join key, but a missing display name only affects presentation. Create the account, fall back to the address for display, and refresh the name when a later sign-in carries it. Refusing on a cosmetic attribute turns a customer's directory hygiene into your outage.
saying these in an interview costs you the question
- Creates an account for anyone any connection vouches for, regardless of tenant policy
- Takes the tenant from an attribute the customer's provider wrote
- Keys the local account on the email address the assertion carried
- Uses check-then-insert and calls the race unlikely enough to ignore
- Refuses the whole sign-in because an optional display attribute was absent
- Reports an admission refusal as a failed authentication