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

User Authentication & Login — API Analysis (API-A) ​

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.

Feature: User Authentication & Login

Four groups of endpoints: initialising the sign-in surface, the post-exchange identity sync, the authenticated user's own profile, and administration of linked identities and sign-in configuration.

Paths below are written without any version or gateway prefix: how a project versions and mounts the routes is an application decision, not part of the concept. Success envelope: { data, status, message, requestId }. Permission codes are named descriptively; a project resolves them against its own Permission Model catalog.

Endpoint index: the git host renders an outline from the ## headings below — there is no hand-maintained endpoint list to fall out of date.


1. Initialize the sign-in surface ​

Endpoint: GET /login?locale=$locale

Description ​

Everything the sign-in surface needs to render for a locale: branding, the providers on offer, localized text, policy links and an administrator contact. This is the one read that must work for a caller with no identity at all.

Authorization ​

Public. No token, no permission. The response must therefore contain nothing that is not already safe to show an anonymous visitor — which is the real constraint on what may be added to it.

A permission code registered against a route that is public is a trap: it reads as enforcement and enforces nothing. Either the route is public and carries no code, or it is not public.

Request Headers ​

None beyond the project's shared conventions.

Query Parameters ​

ParameterTypeRequiredDescription
localeStringYesLocale selecting the text and reference values to return; an unsupported locale falls back to the default and the response says so

Request Logic ​

Reads the sign-in configuration and the text for the requested (or fallback) locale. Read-only, and deliberately cheap: it is called before every sign-in, including by clients that never complete one.

Success Response (200 OK) ​

json
{
  "data": {
    "translations": { "locale": "en-GB", "data": { "login.title": "Sign in" } },
    "branding": {
      "brandName": "Example Corp",
      "logoBase64": null,
      "primaryButtonColor": "#1E90FF",
      "borderRadiusPx": 8,
      "background": { "type": "solid", "color": "#F3F4F6" }
    },
    "providers": [ { "key": "corporate-directory", "type": "oidc" } ],
    "privacyPolicyUrl": null,
    "termsAndConditionsUrl": null,
    "administratorContactName": null,
    "administratorContactEmail": null
  },
  "status": 200,
  "message": "Sign-in surface initialized successfully.",
  "requestId": "a8123fa1-2045-42b4-83fc-97dafe11f4a5"
}

Response Data Mapping ​

Response fieldSource / value
branding, providers, policy and contact fieldsSign-in configuration
translationsText for the resolved locale
messageCarries the fallback notice when the requested locale is unsupported

Error Responses ​

StatusCondition
400locale missing or malformed

2. Sync the authenticated identity ​

Endpoint: POST /identity/sync

Description ​

Called by the sign-in layer immediately after the token exchange: resolve the token's identity to an internal user, link it where the project's linking rules allow, and refresh the sign-in snapshot.

Authorization ​

Requires a valid token but does not require the identity to resolve to a user yet — that is the question this endpoint answers. It is the one authenticated route where "unknown identity" is a normal input rather than an error condition.

Request Headers ​

text
Authorization: Bearer <access_token>

Request Body ​

None. Every input comes from the token's claims — which is the point: a body would let the caller assert an identity rather than prove one.

Request Logic ​

  • Look up the identity by (issuer, subject).
  • If absent, apply the project's linking rules — administrative-only, or automatic under the precondition described on the feature page.
  • On success, refresh the last-sign-in timestamp and the snapshots, and populate the identity cache.

Transactional Operations ​

The resolve-or-link and the sign-in touch commit in one transaction; caches are populated after commit, never inside it.

Success Response (200 OK) ​

json
{
  "data": { "userId": "018fa51f-fda1-79f4-8461-2cb8f1cabc10" },
  "status": 200,
  "message": "Identity synced.",
  "requestId": "3ab34d88-65d1-4c10-897a-237c9a5b116f"
}

Error Responses ​

StatusCondition
401Token missing, invalid, expired, or from an untrusted issuer
401Token valid but the identity resolves to no internal user

Both cases are 401, and a project should keep them distinguishable in the payload without distinguishing them in the status. "I do not know you" and "I know you and you are nobody here" are different problems for support and identical for the client. Note also that a genuine 401 is easily mislabelled as a validation error when a generic validation fallback runs before the status-to-code mapping — worth checking, because it makes every unmapped identity look like a malformed request.


3. Read the current user's profile ​

Endpoint: GET /me

Description ​

The profile of the currently authenticated user.

Authorization ​

Requires authentication; a permission granted to any authenticated user. Not scope-bound, and it takes no user identifier — the subject is the caller. An endpoint that accepted a user id here would be a different endpoint with a different permission.

Request Headers ​

text
Authorization: Bearer <access_token>

Request Logic ​

Reads the resolved user from the authenticated context. Read-only.

Success Response (200 OK) ​

json
{
  "data": {
    "userId": "018fa51f-fda1-79f4-8461-2cb8f1cabc10",
    "displayName": "Anna Novak",
    "email": "anna.novak@example.com",
    "avatarUrl": null
  },
  "status": 200,
  "message": "Profile loaded successfully.",
  "requestId": "ad9cd51f-d993-4014-a2b2-8f563fe887fd"
}

Error Responses ​

StatusCondition
401No valid token

4. Attach an identity to a user ​

Endpoint: POST /admin/users/{userId}/identities

Description ​

Link an external identity to an existing user. The administrative counterpart to automatic linking, and the only path a project that disables automatic linking has.

Authorization ​

An administrative create permission over user identities. Identities are typically not scope-bound (see Are identities global, or scoped? on the feature page), so this is usually a platform-level permission rather than a scoped one.

Request Body ​

json
{ "providerKey": "corporate-directory", "subjectRef": "67d3401f-1a2b-4c3d-9e8f-000000000000" }
FieldTypeRequiredDescription
providerKeyStringYesOne of the supported providers
subjectRefStringYesThe subject as the administrator knows it

subjectRef is not necessarily the stored subject. Providers identify people in their own formats, and the value an administrator can copy out of a directory is often not the value that arrives in a token. The concept therefore separates the two: the request takes a provider-appropriate reference, and the service derives the issuer and the canonical subject from it. A project must say, per provider, what that reference is and how it converts — and must not validate subjectRef against one provider's format at the input layer, which would reject every other provider's references before the provider-specific check can produce a useful error.

Request Parameters ​

NameInTypeRequiredDescription
userIdPathUUIDYesUser to attach the identity to

Request Logic ​

Validate the provider and the reference, derive issuer and subject, then create or restore the identity row.

Transactional Operations ​

One transaction. The unique (issuer, subject) constraint plus duplicate-key handling makes concurrent creates safe; a previously soft-deleted row is restored rather than duplicated.

Success Response (201 Created) ​

json
{
  "data": {
    "id": "018fa520-1c33-7a90-9d21-9f0b2a44e8c1",
    "userId": "018fa51f-fda1-79f4-8461-2cb8f1cabc10",
    "providerKey": "corporate-directory",
    "issuer": "https://sso.example.com/",
    "subject": "67d3401f-1a2b-4c3d-9e8f-000000000000"
  },
  "status": 201,
  "message": "Created user identity.",
  "requestId": "16ddc487-1092-496e-b9a4-97d3e3082ee6"
}

Error Responses ​

StatusCondition
400Provider not supported
400Subject reference not valid for that provider
403Caller lacks the permission
404User does not exist
409That (issuer, subject) is already linked — possibly to a different user

The 409 is the interesting one: it means someone else may already own this credential. The response must not disclose which user, and the resolution is an administrative decision rather than an overwrite.


5. List a user's identities ​

Endpoint: GET /admin/users/{userId}/identities

Description ​

The external identities linked to a user — the answer to "why can this person sign in, and by which route".

Authorization ​

An administrative read permission over user identities.

Request Parameters ​

NameInTypeRequiredDescription
userIdPathUUIDYesUser whose identities to list

Request Logic ​

Reads the user's live identity rows. Read-only.

Success Response (200 OK) ​

json
{
  "data": {
    "identities": [
      {
        "id": "018fa520-1c33-7a90-9d21-9f0b2a44e8c1",
        "userId": "018fa51f-fda1-79f4-8461-2cb8f1cabc10",
        "providerKey": "corporate-directory",
        "issuer": "https://sso.example.com/",
        "subject": "67d3401f-1a2b-4c3d-9e8f-000000000000",
        "lastLoginAt": "2026-05-12T08:14:22Z",
        "createdAt": "2026-01-22T10:00:00Z",
        "updatedAt": "2026-01-22T10:00:00Z"
      }
    ]
  },
  "status": 200,
  "message": "Fetched user identities.",
  "requestId": "16ddc487-1092-496e-b9a4-97d3e3082ee6"
}

Error Responses ​

StatusCondition
403Caller lacks the permission
404User does not exist

6. Re-point an identity ​

Endpoint: PUT /admin/users/{userId}/identities/{identityId}

Description ​

Change which external credential a link points at — the repair path when a directory migration changes a person's subject.

Authorization ​

An administrative update permission over user identities.

Request Body ​

Same shape and validation as create.

Request Parameters ​

NameInTypeRequiredDescription
userIdPathUUIDYesOwning user
identityIdPathUUIDYesIdentity to re-point

Request Logic ​

Validate, derive issuer and subject, update the row, and evict the cache entry for the old(issuer, subject) — the pair that is no longer valid is the one still cached.

Transactional Operations ​

One transaction; the unique constraint guards against collisions with another user's identity.

Success Response (200 OK) ​

json
{
  "data": {
    "id": "018fa520-1c33-7a90-9d21-9f0b2a44e8c1",
    "userId": "018fa51f-fda1-79f4-8461-2cb8f1cabc10",
    "providerKey": "corporate-directory",
    "issuer": "https://sso.example.com/",
    "subject": "b3d20b91-05ff-4f4d-8a29-c70e4405acde"
  },
  "status": 200,
  "message": "Updated user identity.",
  "requestId": "ad9cd51f-d993-4014-a2b2-8f563fe887fd"
}

Error Responses ​

StatusCondition
400Provider not supported, or subject reference invalid for it
403Caller lacks the permission
404User or identity does not exist
409The new (issuer, subject) collides with an existing identity

7. Remove an identity ​

Endpoint: DELETE /admin/users/{userId}/identities/{identityId}

Description ​

Unlink an external credential. Soft delete, because the audit trail must keep referring to something.

Authorization ​

An administrative delete permission over user identities.

Request Parameters ​

NameInTypeRequiredDescription
userIdPathUUIDYesOwning user
identityIdPathUUIDYesIdentity to remove

Request Logic ​

Soft-delete the row and evict the cache entry for its (issuer, subject). Skipping the eviction leaves the removed identity working until the TTL expires, which is the whole point of removing it.

Transactional Operations ​

The soft delete commits in one transaction; the cache entry is evicted after commit.

Success Response (204 No Content) ​

Empty body.

Error Responses ​

StatusCondition
403Caller lacks the permission
404User or identity does not exist

8. Read the sign-in configuration ​

Endpoint: GET /admin/settings/sign-in

Description ​

The current branding, offered providers, policy links and administrator contact — the administrative view of what endpoint 1 serves anonymously.

Authorization ​

An administrative read permission over sign-in settings.

Request Logic ​

Reads the configuration (the Config Store concept). Read-only.

Success Response (200 OK) ​

json
{
  "data": {
    "branding": { "brandName": "Example Corp", "primaryButtonColor": "#1E90FF", "borderRadiusPx": 8 },
    "providers": [ { "key": "corporate-directory", "type": "oidc" } ],
    "privacyPolicyUrl": null,
    "termsAndConditionsUrl": null,
    "administratorContactName": null,
    "administratorContactEmail": null
  },
  "status": 200,
  "message": "Sign-in settings loaded successfully.",
  "requestId": "a8123fa1-2045-42b4-83fc-97dafe11f4a5"
}

Error Responses ​

StatusCondition
403Caller lacks the permission

9. Update the sign-in configuration ​

Endpoint: PUT /admin/settings/sign-in

Description ​

Write the branding, policy links and contact, then return the re-read result.

Authorization ​

An administrative update permission over sign-in settings.

Request Body ​

json
{
  "branding": {
    "brandName": "Example Corp",
    "logoBase64": null,
    "primaryButtonColor": "#1E90FF",
    "borderRadiusPx": 8,
    "background": { "type": "linear-gradient", "angleDeg": 135, "colors": ["#F3F4F6", "#FFFFFF"] }
  },
  "privacyPolicyUrl": null,
  "termsAndConditionsUrl": null,
  "administratorContactName": null,
  "administratorContactEmail": null
}

Every branding value is format-validated — colours, numeric ranges, URLs, the contact address. The values are rendered on a public, unauthenticated page, so validation here is not cosmetic: it is the boundary that stops administrative input becoming anonymous-facing content.

The enabled-provider list is deliberately not editable here. Turning a provider on requires issuer and client configuration that this endpoint does not carry, so offering it as a checkbox invites a sign-in surface advertising a provider that cannot complete a flow.

Request Logic ​

Validate, write, re-read, return. Returning the re-read rather than the request body is what makes the response honest about what was actually stored.

Transactional Operations ​

The configuration write commits in one transaction; the settings are re-read after commit.

Success Response (200 OK) ​

Same shape as the read.

Error Responses ​

StatusCondition
400Invalid branding value, malformed URL or contact address
403Caller lacks the permission

Internal / non-endpoint access ​

Two paths carry more traffic than every endpoint above and are not endpoints at all:

  • Per-request token validation. On each protected request the bearer token is validated (signature against the issuer's keys; issuer, audience and expiry against the trusted set), then (issuer, subject) is resolved to an internal user — from the identity cache first, from the database second. Where automatic linking is enabled and its precondition holds, an unknown identity may be linked here rather than rejected; otherwise the request is unauthenticated. This is the hot path, and it is the reason the identity cache exists.
  • Identity repository writes. Identity rows are created, restored, sign-in-touched, updated and soft-deleted through an internal repository that records the acting party and joins the surrounding transaction. There is no public write path beyond the administrative endpoints above.

A product that exposes more than one application surface commonly adds one further authenticated read: which surfaces may this user enter, and in which scope. Its shape depends entirely on how the project divides its surfaces and how it scopes them, so the concept notes the need and leaves the contract to the project — it belongs to whichever feature owns the shell, not to authentication.