# Identity And Accounts

> Use when building an identity service or handling the account lifecycle. Everything beyond signing tokens: claims enriched at issuance, refresh token rotation, external OAuth providers and callbacks, account linking, enumeration safety, lockout, transactional email, account deletion, and versioned legal consent.

- Skill: `konradcinkusz/identity-and-accounts` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add konradcinkusz/identity-and-accounts`
- Raw SKILL.md: https://api.skillmd.com/api/skills/konradcinkusz/identity-and-accounts/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: konradcinkusz (https://skillmd.com/u/konradcinkusz)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/konradcinkusz/identity-and-accounts

---


# Identity, OAuth and the account lifecycle

**Read [`references/IDENTITY-AND-ACCOUNTS.md`](references/IDENTITY-AND-ACCOUNTS.md) before applying any of this.**
That file is the standard; everything below it is a summary to help you decide
whether this skill applies and to check your work afterwards.

Reference-architecture principles: P5.

## What this standard covers

- Claims are enriched at issuance
- Refresh tokens
- OAuth providers
- Account linking
- Enumeration safety
- Lockout
- Transactional email
- Account deletion
- Versioned legal consent
- Key material

## Failure modes

| Symptom | Cause |
|---|---|
| Provider error `redirect_uri_mismatch` | Callback built from an internal `Request.Host`; set the public callback base URL |
| OAuth callback 404s | Provider registered with the controller's route instead of the middleware's `/signin-*` path |
| Two accounts for one person | Link-by-email flow missing; first external sign-in created a duplicate |
| Password reset reveals which emails exist | Branches return different messages/timing; unify to the generic 200 |
| Sessions survive password reset | Refresh revocation not wired to the reset path |
| Support tickets: "locked out forever" | Lockout shipped without the admin unlock endpoint |
| Invitation email storms | No attempt log/cooldown; resend button wired straight to the sender |
| "Deleted" user's files still downloadable | Blob cleanup missing from the reaper; cascade never reaches object storage |
| Users never see updated terms | Version bumped in the document but not in configuration; `/me` never flags it |
| Dead provider buttons in the UI | Frontend hardcodes providers instead of reading the discovery endpoint |
| `openssl: command not found` on somebody's first day | Setup runbook assumes a Unix shell; no path given for the platform half the team is on |
| PEM rejected as unreadable | Newlines arrived escaped as literal `\n` through an environment variable or secret store |
| Every consumer rejects every token, but nothing is unhealthy | Key never reached the service; the algorithm was inferred as symmetric and the JWKS is a valid, empty key set |
| Connection string breaks one rotation after it was fine | Generated password contained `+`, `/`, `=` or `;` and was never escaped |
| A rotation signed everybody out | Retired public key dropped from the validation set immediately instead of after one token lifetime |

## Checklist

- [ ] Authorization facts (tier, workspace roles) stamped into claims at issuance; no downstream callback
- [ ] Refresh tokens: CSPRNG, single-use rotation, stored hashed, global revocation invoked from logout/reset/deletion
- [ ] OAuth: middleware callback paths; explicit public callback base URL; `returnUrl` in state, validated against an allowlist; provider discovery endpoint
- [ ] All four account-linking flows implemented; link-by-email notifies the user
- [ ] Forgot/reset enumeration-safe with a test per branch; OAuth-only behaviour decided and recorded
- [ ] Lockout enabled, with an admin unlock endpoint
- [ ] Email inventory documented incl. deliberate silences; attempt log + escalating cooldowns; no-op provider fallback
- [ ] Soft delete + retention + reaper + admin restore; typed confirmation + password; blob cleanup explicit
- [ ] Consent versions in config; immutable acceptance audit rows; cookie consent default-deny
- [ ] Signing keys generated rather than invented; private half held only by the identity service; `kid` derived from the key
- [ ] Rotation is rolling — retired public key stays in the validation set and the JWKS for one token lifetime
- [ ] Key generation works on every platform contributors actually use, and each command is copy-pasteable on its own line
- [ ] Local development keys are distinct from deployed ones
- [ ] The deploy asserts the published key set is non-empty

---

Generated from [`docs/guides/IDENTITY-AND-ACCOUNTS.md`](https://github.com/konradcinkusz/architecture-standards/blob/main/docs/guides/IDENTITY-AND-ACCOUNTS.md) by `scripts/build-marketplace.mjs`. Do not edit this file: change the source document, or its entry in `catalog/marketplace.catalog.json`, and re-run the generator.

