skip to content

Workforce Identity

Signing users in against a customer's identity provider instead of your own login form, and keeping accounts in step as people join, move and leave. Interviewers probe the leaver path.

on this pageshow

questions

29

A booking tool's account row carries one identity-provider column. What does replacing it with a separate identity table buy, and what does it cost?

level: middleimportance: must knowfreq 45%

answer

  1. one human, several ways in
  2. identity rows, not a provider column
  3. constrain issuer and subject together
  4. a lookup on every federated sign-in
  5. the last route cannot be removed

basics

~20 s

A child identity table of (issuer, subject) rows, uniquely constrained on both columns together, lets one account be reached by several sign-in routes. It costs a lookup on every federated sign-in and a last-route rule the schema cannot enforce.

solid answer

~50 s

A `provider` column on the account row encodes the assumption that a person has exactly one way in, and that assumption dies the moment a second identity provider is connected to a tool people already have passwords for. The shape that survives is a child table: one row per identity, carrying `user_id`, the `issuer` that asserted it, the `subject` that issuer minted, and when and how the row was linked — with `unique (issuer, subject)` as the key. Both columns together, because a subject is only unique inside the issuer that minted it. What it buys is that a second provider, a re-keyed connection, an unlink and a merge all become row operations instead of schema changes. What it costs is an indexed lookup on every federated sign-in, a second query wherever the UI shows how someone signs in, and one invariant — *this account must keep at least one usable way in* — that no constraint can express for you.

code

pseudocode · 14 lines
pseudocode
# sign-in: the only automatic join is an exact identity-row match
on federated_sign_in(iss, sub):
    identity = identities.find(issuer = iss, subject = sub)   # unique (issuer, subject)
    if identity is not null:
        identity.last_seen_at = now
        return identity.user_id
    return NO_ACCOUNT_YET        # never fall through to an address match

# unlink: the rule no constraint can express for you
on unlink(user_id, identity_id):
    routes_left = identities.count(user_id) - 1
    if routes_left == 0 and not has_usable_password(user_id):
        reject "this is the account's last way in"
    identities.delete(identity_id)

go deeper

for a junior

Recall the shape: an account is who the person is to the product, and a separate table lists the ways that person can sign in. One person, several routes, one account.

for a middle

Be able to explain the key. The pair of issuer and subject is unique together because a subject only means anything inside the issuer that minted it, and an asserted address is an attribute rather than a key.

for a senior

Show the operational consequences: the extra probe on the sign-in path, the screens that silently go stale when they keep reading a column, and the invariant that must live in code because no constraint can express 'keep at least one way in'.

for a principal

Argue the cost of getting it wrong late. Retrofitting an identity table after two providers are live means a data migration over live sign-ins, and the only cheap moment to choose this shape is before the first federated connection exists.

## Where the trouble starts A laboratory-booking tool that has only ever had local passwords keeps its authentication data on the account row: an address, a password hash, and — when the research institute's identity provider is first connected — a `provider` column and a subject column bolted on beside them. That shape works exactly once. It encodes the assumption that a person has one way in, and the entire point of federated sign-in arriving at a tool with two years of existing users is that the same person now has two: the password they have used since the tool was bought, and the identity the institute's provider will assert from next Monday. ## One account, many identities The shape that survives is a child table. The account row keeps who the person is to your product — display name, bookings, permissions. A separate identity table keeps every route by which that person can arrive: - `user_id` — the account this identity resolves to - `issuer` — the party that asserted it, recorded exactly as that party names itself - `subject` — the identifier that party minted for the person, opaque to you - `linked_at`, `linked_by`, `link_method` — when the row was written, by whom, and whether the link was interactive or automatic - `last_seen_at` — the most recent sign-in that used this route The constraint that matters is `unique (issuer, subject)` — both columns, together. A subject is only unique inside the issuer that minted it. In OpenID Connect, `sub` is specified as unique per issuer, not globally; a SAML 2.0 `NameID` is scoped by the asserting party in the same way. Constrain the subject alone and the first value collision between two connected providers silently joins two strangers into one account. What does **not** belong in that key is the address. An address is an attribute of a person that the asserting party may change — a name change, a department move, a domain migration all rewrite it — and it is asserted by a party whose verification policy is its own. It is a useful hint for showing a human which identity is which. It is not a key. ## What the two shapes actually differ on | Situation | `provider` column on the account | separate identity table | |---|---|---| | A second provider is connected | schema change, or one identity overwrites the other | insert a row | | The same person keeps their password and gains a federated route | not representable | two rows, one account | | The customer re-keys a connection and subjects change | in-place update with no history | new rows, old rows retained and auditable | | Someone asks which routes their account has | one value, no history | a query that answers honestly | | Two accounts turn out to be one person | column overwrite, losing a route | re-point identity rows onto the survivor | | Unlinking one route | null out a column | delete one row, subject to a rule | ## What it costs The costs are real and worth stating plainly rather than pretending the child table is free: 1. **A lookup on every federated sign-in.** The path is now: read `iss` and `sub` from what arrived, probe the unique index, get a `user_id`, load the account. With the index in place it is one probe, so the cost is bounded — but it is a second round trip on the hottest path you have. 2. **The account row no longer tells you how someone signs in.** Every screen, export and support tool that used to read one column now needs a join or a second query, and the ones that are not updated quietly start lying. 3. **Link and unlink become code paths.** They were column updates; they are now row operations with rules attached, and rules need tests. 4. **An invariant the schema cannot hold.** `unique (issuer, subject)` stops two accounts claiming one identity. Nothing stops an account ending up with zero identities and no usable password — an account with data, permissions and history and no way in at all. ## The invariants worth writing down - **(issuer, subject) is unique across the whole table.** One asserted identity resolves to at most one account, always. - **An account must retain at least one usable authentication route.** Enforce it in the sign-in and unlink code, on every path that removes a route: unlink, merge, and the moment local passwords are switched off. - **Every identity row records how it was linked.** When a link is later disputed, or an incident asks which accounts were joined automatically, that column is the whole investigation. - **Identity rows are never edited in place to change the subject.** A changed subject is a different identity: insert, and retire the old row with its history intact. The pay-off is that the awkward operations of federated identity — a second provider, a merge, an unlink, an enforcement flip — all reduce to inserting, moving or deleting rows in one narrow table whose rules you can state in four lines.

  • Should the table also be unique on (user_id, issuer)?
    That is a policy choice, not a correctness one. Adding it means an account holds at most one identity per provider, which is usually what you want and makes a re-key obvious: the insert fails instead of quietly leaving two live rows for one person at one issuer. Omit it only if you genuinely support one person holding several distinct identities at the same provider, and can explain to support which one signed in.
  • The institute re-keys its connection and every subject changes. What does the identity table let you do that a column would not?
    Insert the new identity rows alongside the old ones and let people land on their existing accounts as they sign in, then retire the unmatched old rows once the tail has drained. With a single column you would have to overwrite every value in one shot, with no way to run the two routes side by side and no record of what the old subject was when someone disputes where their bookings went.
  • What does last_seen_at on an identity row earn its keep for?
    It answers the two questions support actually asks: which route this person really uses, and whether a route is dead. A row untouched since the enforcement flip is either a person who never moved over or an identity the provider stopped asserting, and both are worth finding before someone reports being locked out.

A shared laboratory's key register. The register lists people, and separately lists keys, each stamped with which locksmith cut it — because two locksmiths both number their keys from one, and key 45 from one of them opens nothing of the other's. A person may hold several keys; the register's one rule that no lock can enforce is that you do not take back someone's last key while their samples are still in the freezer.

saying these in an interview costs you the question

  • Puts a provider name and subject in columns on the account row.
  • Makes the asserted address the unique key across providers.
  • Constrains the subject alone, so two issuers can collide.
  • Assumes a subject identifier is globally unique without its issuer.
  • Believes a database constraint can enforce keeping one sign-in route.
  • Edits an identity row's subject in place when a connection is re-keyed.
open as a page

With only an email address typed at sign-in, how do you route the person to the right brand's identity provider?

level: middleimportance: must knowfreq 48%

basics

~20 s

Take the domain from the typed address, look it up in a table of domains a tenant has verified, then follow that tenant to its identity-provider connection record and redirect. An unmatched domain falls back to local sign-in.

open as a page

Your federated sign-in tests install an already-authenticated principal in the harness — which part of the integration stays unexercised?

level: middleimportance: must knowfreq 40%

basics

~20 s

A helper-installed principal leaves the whole validation path unexercised. The test starts after the assertion would have been parsed, its signature checked, its audience and validity window enforced and its identifier recorded — so every rejection rule the service provider owns is untested.

open as a page

When you stand in for a venue operator's identity provider in tests, why must the stand-in really sign its assertions?

level: middleimportance: must knowfreq 33%

basics

~20 s

Because the checks worth testing are rejections, and a stand-in that does not sign cannot produce a message that is signed by the wrong key. Holding a test keypair lets the suite mint both the accepted message and every rejected variant of it.

open as a page

In a shared driver-hours system whose records are a legal archive, what does deleting a departed driver's account cost that deactivating it does not?

level: middleimportance: must knowfreq 55%

basics

~20 s

Deleting destroys attribution: the hours entries, corrections and approvals the driver authored can no longer be resolved to a person, and the identifier could later be handed to someone else. Deactivation keeps the identity resolvable while ending sign-in.

open as a page

Your permit system's assertion consumer service is an unauthenticated POST endpoint on the public internet — what do you harden on it?

level: middleimportance: must knowfreq 55%

basics

~20 s

Treat every request as hostile input from a stranger: cap the body size before reading it, parse the XML with document type definitions and external entity resolution disabled, rate-limit per connection, return a generic failure, and issue no session until every check has passed.

open as a page

Which checks on an inbound SAML response must you configure yourself, and which does an integration library leave off by default?

level: middleimportance: must knowfreq 50%

basics

~20 s

A library verifies the signature; the bindings to you are yours to configure. Set the expected audience to your own entityID, require the recipient and destination to be your endpoint, bound the validity window with an explicit skew, correlate InResponseTo against a stored request id, and process only the element verification returned.

open as a page

Serving SCIM 2.0 /Users, what does a successful create return, and what must a colliding create return instead?

level: middleimportance: must knowfreq 38%

basics

~20 s

A successful create returns 201 with the resource as stored, including the id your service minted, plus a Location header. A create whose userName already exists returns 409 carrying scimType uniqueness — never a silent overwrite, a 200, or a 500 from a constraint.

open as a page

In a SCIM 2.0 PatchOp your service receives, what do op and path mean, and when is path required?

level: middleimportance: must knowfreq 42%

basics

~20 s

A PatchOp carries an ordered Operations array; each entry has an op of add, replace or remove, an optional value, and a path selecting what it targets. Path is optional for add and replace, which then merge value at the resource root, and required for remove.

open as a page

What must a brand's email domain prove before it may route sign-ins to that brand's identity provider?

level: seniorimportance: must knowfreq 42%

basics

~20 s

Control of the domain, proved out of band - typically a record the domain's own operator publishes in DNS, which you check and re-check. An unverified claim lets the claiming tenant's identity provider vouch for every address under that domain.

open as a page

Where does your service provider's assertion replay cache live when several replicas sit behind a load balancer, and how long do entries last?

level: seniorimportance: must knowfreq 45%

basics

~20 s

In a store every replica shares, claimed with one atomic set-if-absent keyed on issuer plus message id, with a time-to-live running to the end of the message's validity window plus the clock skew you allow. A per-process cache leaves a replay hole at the load balancer.

open as a page

On a first federated sign-in, what decides whether an account is created and which tenant it binds to?

level: middleimportance: should knowfreq 38%

basics

~20 s

The 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.

open as a page

Two booking-tool accounts turn out to be one researcher and must be merged — what do you have to decide, and what can you not undo?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Decide which account survives, what happens to rows a per-user unique index will not let you move, and what the history now says. The merge is irreversible in practice once it commits, and it does not end anything already issued to the losing account.

open as a page

What does a per-tenant identity-provider connection record hold so onboarding a brand needs no code change?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Everything that differs per customer: the issuer identifier, the sign-in URL, the current signing certificates, the audience and endpoint you publish for them, their attribute names, their admission and entry-point settings, plus state and audit columns. The code reads it; it never hard-codes it.

open as a page

Every customer names its directory groups differently, so how do you map those groups to your roles without a deploy?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Hold the mapping as tenant-scoped rows - the group value the customer sends on the left, your role on the right - read on every sign-in and editable at runtime. Grant nothing for an unmapped value, record it, and alert when a rule stops matching.

open as a page

Why must the clock be an injected input for tests that post a SAML assertion to your AssertionConsumerService?

level: seniorimportance: should knowfreq 27%

basics

~20 s

Because every bound a federated sign-in enforces is clock-relative. With the system clock, a fixture's validity window expires overnight and the just-inside and just-outside cases of each bound and of the skew tolerance are impossible to express at all.

open as a page

A driver moves to another member firm and takes on new duties, so why does a grants model that only ever adds eventually let them do everything?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A move is two operations — grant the new authority and withdraw the old — and pipelines usually implement only the first. Unioned grants accumulate across moves until someone who has changed firm three times holds the union of all three.

open as a page

A contractor firm rotates its assertion-signing certificate without telling you — how does your service provider survive that?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Hold a set of accepted signing certificates per connection rather than one, refresh each counterparty's metadata on a schedule and honour its cacheDuration and validUntil, keep the last good copy when a fetch fails, and alarm per connection so one firm's rotation is visible before its people are.

open as a page

What does your service provider store at login so that a later logout message naming a SessionIndex can find the right session?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Record, against the local session you issue, the issuing counterparty, the subject identifier as sent, and the SessionIndex from the assertion — indexed so a lookup by those three is a single read. Without that row the inbound message names a session at the other side that you cannot resolve to one of yours.

open as a page

Which SCIM 2.0 filter and pagination subset do real provisioning clients send, and how do you serve it safely?

level: seniorimportance: should knowfreq 26%

basics

~20 s

In practice clients send little more than an equality filter on userName or externalId, occasionally co or sw. Serve it by parsing to a bound, parameterised query on indexed columns, refuse what you cannot evaluate with scimType invalidFilter, and cap results rather than scanning.

open as a page

A SCIM 2.0 provisioning client times out and resends the same PATCH to your viewer — which operations replay safely, and which do not?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Replace replays cleanly because it writes an absolute value. Remove converges too, though the second attempt may match nothing and the specification's answer is a 400 with scimType noTarget. Add is the dangerous one: repeating it duplicates a multi-valued element unless you apply it as a set keyed on value.

open as a page

A laboratory-booking tool switches to mandatory identity-provider sign-in — what happens to each account's stored password, and who is locked out?

level: principalimportance: should knowfreq 30%

basics

~20 s

The local credential is kept, disabled or deleted, and only the last two actually move the authentication boundary. Locked out are the people with no identity at that provider and those whose identity never linked — a list you produce before the flip, not after.

open as a page

A brand asks you to switch its domain to federated-only sign-in, so how do you sequence that and who must keep a way in?

level: principalimportance: should knowfreq 30%

basics

~20 s

Run both paths in parallel until the connection has proved itself against real traffic, then flip a per-tenant flag that gates new sign-ins for that tenant's verified domains. Keep an explicit route for people no provider vouches for, including that tenant's own administrators.

open as a page

How do you keep a corpus of federated sign-in and provisioning fixtures honest over the years a service provider runs?

level: principalimportance: should knowfreq 20%

basics

~20 s

Hold both kinds deliberately. Recorded messages prove you can still parse what real counterparties actually send; generated ones stay fresh and produce the malformed cases nobody sends. Each catches a defect class the other cannot, and both need an owner and a refresh path.

open as a page

A nightly sweep compares your driver-hours accounts against each member firm's employment records, so what may it change on its own authority?

level: principalimportance: should knowfreq 32%

basics

~20 s

Grade it by reversibility and direction: a sweep may narrow access by itself — disable accounts and drop derived grants — but must only report anything that widens it or destroys data, and must refuse to act at all when the employment answer looks empty or stale.

open as a page

A contractor firm's identity provider cannot meet one of your validation requirements — how far do you bend, and who decides?

level: principalimportance: should knowfreq 25%

basics

~20 s

Rank the requests by blast radius: clock skew is bounded and reversible, endpoint-binding checks cost you once you have more than one endpoint, and audience and single use are not negotiable. Grant the rest as per-connection data with an owner, a reason and a review date — never as a global default or a code branch.

open as a page

A practice group's provisioning client sends SCIM 2.0 writes that RFC 7644 does not describe — which divergences do you absorb, and which do you refuse?

level: principalimportance: should knowfreq 24%

basics

~20 s

Absorb a divergence only when it has exactly one possible meaning, can be normalised at the request edge, and is scoped to the tenant that forced it. Refuse anything ambiguous or lossy, loudly, with an error a support engineer can act on — because an absorbed divergence is permanent.

open as a page

What do /ServiceProviderConfig, /Schemas and /ResourceTypes tell a SCIM client, and what does over-advertising cost you?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

They are read-only discovery documents: which optional capabilities you support, the attributes of each resource, and which endpoint serves which schema. A client reads them once at setup and changes what it sends, so advertising a capability you have not built moves the failure to runtime, at one customer.

open as a page