Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/architecture/backend/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,48 @@ export interface M2MTokenResponse {
}
```

## 🆔 Identity Claims: `username` vs `sub`

Two distinct identifiers travel on the OIDC user (`req.oidc.user`), and choosing the wrong one breaks upstream lookups. They are **not** interchangeable.

### What each one is

| Claim | Example | Shape | Source claim(s) |
| ------------------------------ | ---------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| **`sub`** (Auth0 subject) | `auth0\|lguerra` | Provider-prefixed, opaque, globally unique per identity | `user.sub` |
| **`username`** (LFID username) | `lguerra` | Bare LF login handle, no provider prefix | `user['https://sso.linuxfoundation.org/claims/username']`, `user.nickname`, `user.username` |
Comment thread
audigregorie marked this conversation as resolved.
Outdated

- **`sub`** identifies the **Auth0 identity record**. It carries a connection prefix (`auth0|`, `github|`, `samlp|`, …), so the same person can have different `sub` values across connections. Treat it as an opaque token — never parse or display it raw (strip the prefix with `stripAuthPrefix` if you must show it).
- **`username`** identifies the **LF person** by their LFID login handle (bare form, no prefix) and what upstream microservices index on. For example, the member-service `b2b_org_settings` index tags each doc with `member:<username>` (the query-service matches on the `tags` param; the legacy `writers.username:` filter form matches nothing), and the caller's role is read from `data.members[].username` (legacy fallback: `data.writers[]` / `data.auditors[]`). On surveys the bare username is persisted as `creator_username`, while the sibling `creator_id` currently stores the `sub` (migrating to username under LFXV2-1962).
Comment thread
audigregorie marked this conversation as resolved.
Outdated

### When to use which

| Use case | Use |
| ------------------------------------------------------------------------------------- | ------------ |
| Calling an upstream microservice / query-service API that keys on the LF login handle | **username** |
| Persisting an author/owner/creator (`creator_id`, role grants, changelog viewer) | **username** |
| Analytics / observability user identity (DataDog RUM, OpenFeature targeting key) | **username** |
| Per-caller cache keys for user-scoped data | **username** |
| Anything that must match an Auth0 identity record exactly (rare, provider-specific) | **sub** |
Comment thread
audigregorie marked this conversation as resolved.
Outdated

> **Default to `username`.** `sub` is being phased out of backend identity references — see the migration note below.
Comment thread
audigregorie marked this conversation as resolved.
Outdated

### Server-side helpers (impersonation-aware)

Read identity through the helpers in `apps/lfx-one/src/server/utils/auth-helper.ts`, never directly off `req.oidc.user`. They transparently return the **target** user's identity during impersonation and the session user's otherwise.

| Helper | Returns | Status |
| --------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------- |
| `getEffectiveUsername(req)` | Impersonated username or OIDC nickname/username | **Preferred** for all new identity references |
| `getEffectiveSub(req)` | Impersonated sub or OIDC sub | **Deprecated** — only for call sites whose upstream still wants the prefixed sub |
| `getEffectiveEmail(req)` | Impersonated email or OIDC email (lowercased) | For email-keyed lookups |

### Migration: `sub` → `username` (LFXV2-1962)

Backend identity references are migrating from the Auth0 `sub` to the LFID `username`. As upstream handlers learn to accept the username, call sites flip from `getEffectiveSub` to `getEffectiveUsername`, and front-end identity references (DataDog RUM `id`, OpenFeature `targetingKey`, survey `creator_id`) will move to the `https://sso.linuxfoundation.org/claims/username` claim instead of `sub` (today they still read `sub` / `preferred_username`).

`getEffectiveSub` remains as a fallback for the migration window and should be treated as deprecated (annotate it `@deprecated` in `auth-helper.ts` as the migration lands). When adding new code, use `username` unless the specific upstream handler still requires the prefixed sub — and if so, note why inline.

## 🏗 Server-Side Implementation

### Auth Context Injection
Expand Down
12 changes: 7 additions & 5 deletions docs/architecture/backend/impersonation.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,13 @@ This is the single choke point — every controller and service uses `req.bearer

Many controllers and services read the user's email/username from `req.oidc.user` for server-side filtering (e.g., "get my meetings"). During impersonation, `req.oidc.user` is still the real user. Three helpers resolve the correct identity:

| Helper | Returns |
| --------------------------- | --------------------------------------------- |
| `getEffectiveEmail(req)` | Impersonated email or OIDC email (lowercased) |
| `getEffectiveUsername(req)` | Impersonated username or OIDC nickname |
| `getEffectiveSub(req)` | Impersonated sub or OIDC sub |
| Helper | Returns | Notes |
| --------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
| `getEffectiveEmail(req)` | Impersonated email or OIDC email (lowercased) | Email-keyed lookups |
| `getEffectiveUsername(req)` | Impersonated username or OIDC nickname/username | **Preferred** for identity references (LFID username, e.g. `lguerra`) |
| `getEffectiveSub(req)` | Impersonated sub or OIDC sub | **Deprecated** — Auth0 sub (prefixed, e.g. `auth0\|lguerra`); migrating to username |

For the full `username` vs `sub` distinction and the LFXV2-1962 migration, see [`authentication.md`](./authentication.md#-identity-claims-username-vs-sub).
Comment thread
audigregorie marked this conversation as resolved.
Outdated

These check `req.appSession['impersonationUser']` first, falling back to `req.oidc.user`. All controllers/services that filter by user identity use these helpers (meetings, events, committees, votes, surveys, mailing lists, documents, analytics, badges, persona detection).

Expand Down
Loading