Skip to content
Updated Sep 12, 2026 by Barča Dvořáková · Owner: analysisactiveconceptgeneric Edit on GitHub

User Authentication & Login ​

Concept layer — frozen. The User Login & Authentication generic. Nothing here is written by a normal playbook run; a project's own feature analysis is the live document and takes every edit. This layer names no project and links to none — the dependency runs one way, from an application to its concept.

Business Context ​

People must sign in and be recognised as a specific person in the system, without the system owning or managing their credentials. Where the customer already runs a corporate directory, their staff sign in with the accounts they already hold, so password policy, multi-factor challenges, onboarding and de-provisioning stay with the identity system that issues those accounts.

Business-Level Definition ​

Authentication is delegated. An external identity provider proves who a person is; the system's own job is to map that proven external identity onto an internal user record, to do so consistently across sign-ins, and to keep the mapping visible and correctable.

The concept therefore owns three things and no more: accepting a proof of identity, resolving it to an internal user, and administering that link. It does not own who the person is (the identity provider), who exists in the system (the Users & Groups concept), or what they may do (the Permission Model concept).

Requirements Definition ​

  • Sign-in uses credentials held elsewhere, so access is granted and revoked centrally by whoever administers the directory rather than by the application.
  • The system stores no end-user passwords, removing both the burden and the breach exposure.
  • A returning person resolves to the same internal user on every sign-in.
  • One internal user may hold several external identities — people acquire new accounts, providers are migrated, and a second identity must not silently become a second person.
  • The sign-in surface can be branded and can offer a configured subset of the supported providers.
  • Administrators can see, attach, re-point and remove the identities mapped to a user.

Technical Context ​

User Stories / Use Cases ​

  1. As a member of staff, I sign in with the account my employer already issued me and reach the application without an application-specific password.
  2. As a returning user, my external identity resolves to my existing internal user on every request.
  3. As an administrator, I attach, re-point or remove the identities linked to a user.
  4. As an administrator, I configure the sign-in surface: branding, policy links, and which of the supported providers are offered.
  5. As an authenticated user, I read my own profile.

UI/UX Design ​

A sign-in surface renders the configured branding, offers the enabled providers, and — where configured — links to policy documents and a contact address for the administrator. Choosing a provider hands the browser to that provider and, once it has authenticated the person, returns to the application with a session established. Two administrative surfaces follow: one for the sign-in surface's configuration, one for the identities linked to a user.

The sign-in surface is the only screen a person reaches before the system knows anything about them. Everything it renders must therefore come from configuration or from the request itself — there is no user preference to read, no scope to resolve, and no permission to evaluate yet.

Functional Requirements ​

Pluggable identity providers

  • Authentication goes through providers of a standard delegated-authentication protocol — each a trusted issuer with published signing keys and a client configuration.
  • The supported set is an enum declared once. Per-provider issuer, client and key-endpoint settings are deployment configuration; which supported providers are actually offered on the sign-in surface is runtime configuration.
  • The recurring maintenance failure here is a provider set that exists in three places at once — an enum, a literal list, and per-provider string branching — so that adding a provider becomes a scattered, many-file change. Single-source it, and treat anything that branches on a provider name as a design smell to be pushed behind the provider abstraction.

Per-request token validation

  • Every protected request carries a bearer token. The backend verifies its signature against the issuer's published keys and checks issuer, audience and expiry against a fixed list of trusted issuers.
  • The backend is a resource server: the interactive authorization-code exchange belongs to the sign-in layer that holds the browser session, not to the API. Keeping that boundary is what allows more than one sign-in surface to exist against one backend.
  • More than one trusted issuer is accepted concurrently, so independent sign-in surfaces do not interfere with each other. Each issuer brings its own audience and key set, so the validator has to select the key set before it can verify — by reading the issuer claim from the unverified token. That is correct as long as the unverified claim chooses only which keys to try and the verified claim is then checked against the trusted list; a reader who meets "unverified" in that code will raise it, so the page says the pattern.

Identity resolution

  • The identity key is the pair (issuer, subject) — the issuer alone is not unique across people and the subject alone is not unique across issuers. It is unique-constrained, which is also what makes concurrent first sign-ins safe.
  • Resolution is default-deny: a token that is cryptographically valid but resolves to no internal user is rejected. Valid ≠ authorised, and conflating the two is how an unknown person acquires an empty-but-real session.
  • Resolution yields an active internal user. A valid token whose identity points at a deactivated or deleted user is rejected identically to one that points at nobody, and deactivating a person evicts their resolved identities from the cache. The common half-measure is to filter on "not deleted" alone: the deactivated person then cannot open the application — the one route that checks — while every call they already make keeps working, for as long as the provider account lives. Where the check sits is a decision the project records: in resolution (once, before anything else — the concept's preference) or joined into permission evaluation (which does not stop the profile and sync calls).
  • Each sign-in refreshes the identity's last-sign-in timestamp and whatever claim snapshot the project keeps for diagnostics. The snapshot is never the source of truth for anything, and it holds the claims the application consumes, listed — never the whole payload spread into a column, because a provider adds claims without asking and the snapshot then stores them without anyone deciding to.
  • Resolution is one function. A project usually has two places where a token becomes a user — a sign-in sync call and the per-request validator — and whatever automatic linking is enabled runs in both, identically. Two callers with two policies is an undocumented policy: which door the caller came through decides whether they are linked.

How an identity comes to exist

Three paths, and a project must decide which of them it enables:

  • Administrative linking — an administrator attaches an external subject to an existing user. Always available, always attributable.
  • Automatic linking by subject, across sibling issuers — where one provider is reachable under several issuer URLs that share a user store (one for the web surface, one for a mobile client), the same subject under a sibling issuer is the same person: the subject is the provider's own stable key, and no claim has to be trusted. Precondition: the issuers are declared as a family in configuration, and the family shares a user store. A distinct machine actor on the created row.
  • Automatic linking by email claim — the system links an unknown identity to an existing user by matching a claim the provider asserts. This is a contested choice with a precondition; see Automatic linking — two mechanisms, one policy below.

Why a sign-in sync call exists when every request resolves the identity

The sync is the one request that runs with a valid token and possibly no identity yet. It is where a sign-in can be refused with a reason before the client has a session, where the last-sign-in timestamp and snapshots are written once rather than on every request, and where the decided-capabilities cache is warmed. It therefore needs a guard that validates the token but does not require a resolved identity — and, where every route must carry a permission code, its code is registered but by construction never evaluated. Say that on the page, or the next audit lists it as an unenforced permission.

Configuration of the sign-in surface

Offered providers, branding, policy links and administrator contact are runtime configuration (the Config Store concept), editable without a release. None of it is code.

Administrative identity management

Administrators list, attach, update and remove the identities linked to a user. Removal is a soft delete plus a cache eviction: an identity that is still cached is still an identity.

Sign-out and session lifetime

  • Sign-out ends the local session and, where the provider supports end-session, the provider's — otherwise the next visit signs the person straight back in. The return target after the provider's sign-out is configuration, not the provider's default page. Where several providers are configured, the one to sign out of is the one the session was established with.
  • The sign-in layer's session outlives the access token only if it holds a refresh grant; the scope that grants it is part of the client configuration, and its absence looks like a session that dies after a few minutes. Both are worth a line here because they are the two places delegated sign-in fails after it has worked.

Identity lifecycle auditing

Identity lifecycle events go to the Audit Log concept, with the identity row as the referenced entity and the owning user as the subject, so identity changes appear in that person's history alongside their record changes and permission assignments.

Audited eventAction
Identity linked to a usercreated
Identity unlinkeddeleted
Identity re-pointed — provider, issuer or subject changedupdated

Two rules about the contents of those entries:

  • The external subject is a credential identifier. It is masked or hashed in the audit record, never written in clear, and tokens and claim snapshots are never written to it at all.
  • Successful sign-ins are deliberately not audited. The identity already carries a last-sign-in timestamp, and one audit entry per sign-in is the fastest way to make an append-only trail grow without bound while adding nothing that the timestamp did not already answer. Sign-in stays an operational log line.

Internationalization & Localization ​

Sign-in text is served through the Translations concept, selected by the locale carried on the request, with a fallback when the requested locale is unsupported — because, as above, there is no user preference to consult yet. Provider display names are sign-in text too — they are the one string most likely to be hard-coded beside the provider key.

Non-Functional Requirements ​

  • Availability: the identity cache is an optimisation, not a dependency. If the shared cache is unreachable, authentication must still work — through a local fallback, or by paying for the database lookup. A cache outage that logs everybody out is an availability defect, not a cache defect.
  • Reliability: several trusted issuers are accepted at once, so one provider's maintenance window does not take the others down with it.
  • Performance: sign-in itself is not on a hot path — it happens once per session. Identity resolution is on the hot path, since it happens on every authenticated request, and that is the step worth caching.

Transactional Operations ​

  • Creating or restoring an identity and recording the sign-in commit atomically. A half-written link is worse than no link, because the next sign-in takes a different branch.
  • Concurrent first sign-ins for the same external identity are made safe by the unique (issuer, subject) constraint plus duplicate-key handling: the loser of the race resolves to the row the winner created instead of failing. Do not attempt to serialise this in application code.

Four participants: the sign-in layer (renders the sign-in surface, runs the authorization-code flow, holds the browser session), the identity provider (which may itself broker a corporate directory behind it), the application backend (token validation and identity resolution), and a cache.

Related concepts: Config Store (sign-in configuration), Translations (sign-in text), Users & Groups (the user record), Permission Model (what the resolved user may do), Multi-Organizations / tenant scoping (which scope the session acts in), Audit Log (identity lifecycle).

Diagrams & Models ​

Web sign-in — from an unauthenticated request to an established session:

sequenceDiagram
    autonumber
    actor U as User (browser)
    participant W as Sign-in layer
    participant P as Identity provider
    participant B as Application backend
    participant C as Cache

    U->>W: Request a protected page
    W-->>U: No session — redirect to the sign-in surface
    U->>W: Choose a provider
    W->>W: Create sign-in state (anti-forgery + PKCE)
    W->>P: Authorization request
    P->>U: Authenticate (per the provider's own policy)
    P-->>W: Authorization code
    W->>P: Exchange code for tokens
    P-->>W: Identity + access token
    W->>W: Validate signature, issuer, audience, expiry, state
    W->>B: Identity sync (bearer access token)
    B->>B: Resolve (issuer, subject) to an internal user
    alt Identity known
        B->>B: Refresh last sign-in and snapshots
    else Unknown, and automatic linking is enabled and its precondition holds
        B->>B: Link the identity to the matching user
    else Resolves to no user
        B-->>W: Rejected
        W-->>U: Sign-in surface with an error
    end
    B->>C: Cache the resolved identity
    B-->>W: Identity confirmed
    W-->>U: Session established — continue to the requested page

Entity relationship — one user, many external credentials:

erDiagram
    user ||--o{ userIdentity : "authenticates via"

    user {
        uuid id PK
        text displayName
        text email
        boolean active
    }

    userIdentity {
        uuid id PK
        uuid userId FK
        text issuer "unique with subject"
        text subject "unique with issuer"
        text providerKey
        timestamptz lastLoginAt
        jsonb claimsSnapshot
    }

API Analysis (API-A) ​

See child page: User Authentication & Login — API Analysis (API-A).

Domain Model & Data Attribute Table (DAT) ​

  • A user entity — the internal record an identity resolves to. Owned by the Users & Groups concept; this concept only reads and links to it.
  • An identity entity — one external (issuer, subject) credential linked to a user, carrying the provider key, the last-sign-in timestamp and an optional claim snapshot.

The resolved-identity cache is a cache structure, not a table, so it has no DAT.

Data ​

There is nothing to seed. Production needs configuration — the enabled providers, the branding, the policy links — not identity rows. Identities come into existence one at a time, either by administrative link or by an automatic path, and a bulk seed of them would be a list of credentials.

A test environment whose database and provider are provisioned independently needs a re-alignment step after a reseed — the provider still holds yesterday's accounts. That step is tooling, not a feature: it stays out of the request path, is gated by environment, and is described on the project page as test-data plumbing so nobody mistakes it for an access control.

Logging ​

  • .info — a successful identity resolution; an identity linked automatically (with a distinct actor so the two are separable afterwards); administrative create, update and delete of a link.
  • .warn — a valid token that resolves to no user; the identity cache unavailable and the fallback in use; a provider or key endpoint unreachable; an expected configuration value missing.
  • .error — failure while provisioning an account at the provider, where the project does that.

Caching ​

  • Resolved-identity cache — (issuer, subject) → internal user, short TTL, evicted when the identity is removed, re-pointed (the old key, or the old subject keeps resolving to the old user for a TTL) or its user deactivated. This is the cache that matters, because it removes a database lookup from every authenticated request.
  • Signing-key cache — issuer public keys, refreshed periodically. Fetching them per request makes the identity provider a hard dependency of every call.
  • Effective-permission cache — owned by the Permission Model concept; commonly warmed just after sign-in, since that is the moment the user is known and nothing is waiting on it.

Backward Compatibility and Migration ​

The accepted token contract is the compatibility surface: adding or narrowing the offered providers is a configuration change and breaks nothing, whereas changing which issuers are trusted invalidates sessions. Changes to the identity model itself follow expand-contract — add the new shape, backfill, move readers and writers, drop the old shape last — because an identity row that stops resolving locks a person out with no self-service route back in.


Delegated sign-in for an enterprise customer usually carries contractual obligations about the trust configuration and the accepted issuers. Data-protection regulation applies to the personal identifiers processed — an external subject, an email address and a name are personal data even though none of them is a credential the system chose.


Cybersecurity Considerations ​

Requests are authenticated by validating the token: signature against the issuer's keys, and issuer, audience and expiry against a fixed set of trusted issuers. The backend holds no password store. Identity mapping is default-deny.

Data privacy. Retain the minimum: the provider (issuer, subject), and whatever name and email the interface needs. A claim snapshot, if kept at all, is for diagnostics and is not authoritative. Secrets and tokens are never written to snapshots or logs.

Attribution. Administrative identity operations must record the acting administrator. An append-only trail whose actor is a constant shows that something happened but not who did it, which is precisely the question an identity trail exists to answer. This is worth calling out because it is a common shortcut: the actor is available on the request and simply is not threaded through to the write.


Risk Assessment ​

Business Risks ​

Low by construction. Authentication is delegated and no credential store exists, so the direct breach exposure of this feature is small. What remains is operational: the system's availability now includes the provider's.

Technical Risks ​

  • Provider and trust-configuration dependency. Sign-in depends on the provider and on correct trust configuration; a misconfiguration or a provider outage blocks every affected user until it is fixed, and the people best placed to fix it may work for the customer rather than for the project.
  • Automatic linking trusts a provider claim. See the section below — this is the single largest security decision in the concept.
  • Cache dependency. Identity and permission caches must degrade to a slower path, not to a failed one.
  • Multiple identities per user. Legitimate and necessary, but the edge cases — re-pointing, removing the last identity, two users claiming the same subject — need explicit answers rather than whatever the unique constraint happens to produce.

Auditing, Reporting & Measurement ​

  • Audit trail — identity lifecycle events are read through the Audit Log concept's reads. A history request that pins the entity type and id is authorised against the permission governing that entity, not against the permission that gates the unpinned cross-entity audit browser: the two answer different questions and conflating them either hides a user's own history or hands out the browser.
  • History view — the identity administration surface carries a read-only history section: one chronological row per event, reading as a sentence, with the raw context behind a per-row expander.
  • Last sign-in — the identity's own timestamp answers "when did this person last use this identity", which is why per-sign-in audit entries are unnecessary.
  • Reporting — browsing the history. There is no aggregate sign-in report in the concept; a project that needs sign-in analytics should build it on operational logs rather than on the audit trail.

Automatic linking — two mechanisms, one policy ​

Two kinds of automatic linking exist, and they are not equally risky.

By subject, across sibling issuers. One provider, several issuer URLs, one user store: the same subject under a sibling issuer is the same account at the same provider, so linking it trusts nothing the provider did not already prove by signing the token. The only precondition is that the issuers are declared as a family and really do share a user store — two providers that happen to issue overlapping subjects are not a family. Safe by construction; record the family in configuration and use a distinct actor on the row.

By email claim. When an unknown external identity arrives and its email claim matches an existing user, the system can link the two automatically instead of rejecting the sign-in. It is attractive: it removes an administrative step from every onboarding, and it repairs the common case where a person's provider changed but the person did not.

It is not part of the pattern. It is a choice, and it is safe only where its precondition holds: the provider must verify email ownership, and the project must trust it to. The claim is asserted by the provider, not proven to the application. A provider that lets a person set an unverified email address — or that lets one be set administratively — turns this feature into an account-takeover route: assert the target's address, sign in, and the system links you to their user.

A project adopting it must record, in its own analysis:

  • Which providers it is enabled for, and on what basis they are trusted to verify email ownership. "All configured providers" is an answer only if every one of them is a directory the customer controls.
  • Whether the claim is checked for a verification flag before it is trusted, where the protocol offers one — and note that a payload schema which drops the flag on parse has decided not to check it, silently.
  • That the match excludes deactivated users. Matching on "not deleted" alone hands a deactivated person a fresh identity and a working session.
  • What happens to the loser of a collision — two users with the same address, or an address that has been reassigned to a new person by the directory. Email addresses are recycled; internal users are not.
  • That the automatic link is distinguishable afterwards — a distinct actor on the created row, so an audit can separate "an administrator linked this" from "the system inferred this".

The alternative — reject unknown identities and require an administrator to link them — costs an onboarding step and buys an explicit, attributable decision per person. Neither answer is wrong; the undocumented answer is.

Whichever mechanisms are enabled, they run in every place a token becomes a user. The failure this section exists to name is not a wrong choice but a split one: the sign-in sync carries one rule, the per-request validator carries the other, and which rule a person meets depends on which request their client happens to make first. A web client that syncs before anything else never reaches the email rule; a mobile client that never syncs never reaches the sibling rule. Resolution is one function (see Identity resolution), and the page states the order in which its rules apply.

Are identities global, or scoped? ​

The other decision this concept declines to make. An identity may be:

  • Global — a person has one identity set regardless of which tenant, client or workspace they act in, and scope is resolved after authentication, from membership. This matches delegated authentication well, since the provider knows nothing of the application's scopes.
  • Scoped — identities belong to a scope, so the same human signing in against two scopes is two identities and possibly two users.

Global identities are the common choice and the one the rest of this concept assumes: it keeps authentication independent of scoping, and it lets one person hold access in several scopes without signing in twice. The cost is that the identity and user entities then sit outside the scope-carrying rule the Multi-Organizations / tenant scoping concept applies to everything else — which is a deliberate exception and must be written down as one, together with what now gates those entities instead of the scope guard.

Scoped identities buy hard isolation, including of the directory relationship itself, and are worth it where scopes are genuinely separate customers with separate providers and no shared people. They cost a mapping problem the moment one person legitimately belongs to two scopes.

Record which one the project chose, and why. The failure mode is not choosing wrongly — it is choosing implicitly, by adding a scope column to one table and not the other.

Known instances ​

None recorded in this copy. Add this project's instance here as it adopts the concept — one line naming the feature that implements it. This is the only place a concept may name a project, and the template ships it empty so a fork inherits no other project's entries.