Skip to content

Auth Endpoints

Endpoints under /api/account/... (and a handful of identity-lifecycle operations under /api/auth/...). The current realm is resolved via the Host header — no realm path prefixes.

Full endpoint source in src/dotnet/Modgud.Authentication/Api/Account/ and src/dotnet/Modgud.Authentication/Api/ExternalAuth/.

Public authentication

MethodPathDescription
POST/api/account/loginLogin with username + password
POST/api/account/logoutLogout (cookie removed, session invalidated)
POST/api/account/registerSelf-registration (when enabled per realm)
POST/api/account/forgot-passwordRequest a password-reset link
POST/api/account/reset-passwordReset password with a token

Email verification

MethodPathDescription
POST/api/account/email/send-verificationSend (or resend) a verification email for the signed-in user's address
POST/api/account/email/verifyAnonymous — verify with the token from the email

Current user & profile

MethodPathDescription
GET/api/account/meCurrent user info (incl. effective permissions, realm slug)
GET/api/account/profileDetailed profile
PUT/api/account/profileEdit profile (creates a UserChangeRequest for non-email fields)
POST/api/account/change-passwordChange password
GET/api/account/external-linksLinked OIDC identities for the signed-in user
DELETE/api/account/external-links/{linkId}Remove a link

Two-factor authentication

Status & TOTP

MethodPathDescription
GET/api/account/mfa/status2FA status (enabled, methods, recoveryCodesRemaining)
POST/api/account/mfa/setupGenerate TOTP authenticator key + QR URI
POST/api/account/mfa/enableEnable 2FA with TOTP code
POST/api/account/mfa/disableDisable 2FA
POST/api/account/mfa/recovery-codesRegenerate recovery codes
POST/api/account/mfa/loginLogin step 2 with TOTP code
POST/api/account/mfa/recovery-loginLogin with recovery code

Email OTP

MethodPathDescription
GET/api/account/email-otp/statusEmail-OTP enrolment status
POST/api/account/email-otp/login/requestRequest email OTP for login
POST/api/account/email-otp/loginLogin with email OTP

Passkey / FIDO2 / WebAuthn

MethodPathDescription
POST/api/account/passkey/register/optionsRegistration options
POST/api/account/passkey/register/completeComplete registration
POST/api/account/passkey/login/optionsLogin options (with or without userName for passwordless)
POST/api/account/passkey/login/completeComplete login
GET/api/account/passkey/credentialsList own passkeys
DELETE/api/account/passkey/credentials/{id}Delete a passkey
PATCH/api/account/passkey/credentials/{id}Change a passkey label
MethodPathDescription
POST/api/account/magic-link/requestRequest a magic link (self-service, only when enabled)
GET/api/account/magic-link/login?token=…&user=…Magic-link login

External login (OIDC)

MethodPathDescription
GET/api/account/external-loginsList of active LoginProviders (no secrets)
GET/api/account/external-login/{loginProviderId}/start?returnUrl=/Start OIDC flow
GET/api/account/external-login/finishOIDC callback from the external IdP
GET/api/account/external-logout/{loginProviderId}Single-logout signal back to the external IdP

Login flow

1. Frontend: GET /api/account/external-logins → shows provider buttons
2. User clicks "Login with Acme SSO"
3. Browser: GET /api/account/external-login/{loginProviderId}/start?returnUrl=/
4. Backend: ASP.NET Challenge with the dynamically registered OIDC scheme
5. Browser: 302 → external IdP
6. User authenticates with the IdP
7. IdP: 302 → /api/account/external-login/finish
8. Backend: ExternalLoginProcessor runs (look up user or JIT create,
   run UserUpdateScript, set login cookie)
9. Backend: 302 → returnUrl

Sessions

MethodPathDescription
GET/api/account/sessionsActive sessions
DELETE/api/account/sessions/{id}Revoke a session
DELETE/api/account/sessionsRevoke all sessions except current ("logout everywhere")

GDPR / privacy

These live under /api/auth/... (separate from the day-to-day account surface) because they're identity-lifecycle operations:

MethodPathDescription
GET/api/auth/export-dataData export (Article 20) — ZIP
GET/api/auth/deletion-statusStatus of the delete workflow
POST/api/auth/delete-accountRequest account deletion (token email goes out)
POST/api/auth/confirm-deletionConfirm with token → archive stream + mask PII
POST/api/auth/cancel-deletionCancel a pending delete request

Bootstrap (first admin in a realm)

There is no anonymous setup wizard. The first admin in any realm is created either through the recovery CLI (filesystem trust) or via a Control-Plane admin issuing an invite through the realm-create API. The single anonymous endpoint is the bootstrap-invite consumer:

MethodPathDescription
POST/api/account/bootstrap-adminConsume a single-use invite token + set password. Body: { "Token": "<plaintext>", "Password": "<new>" }. On success: user is created, atomically added to the Administratoren group with realm:admin, and auto-signed-in via cookie.

The token comes from one of:

  • dotnet Modgud.Api.dll recover bootstrap-admin --email <e> (without --password) — see Recovery CLI
  • POST /api/admin/realms with an InitialAdmin payload — see Realm API
  • POST /api/admin/realms/{slug}/resend-bootstrap-invite — re-issue a fresh token for the same recipient

Token properties: SHA-256-hashed in the DB, 7-day TTL, single-use (reuse → 400 BootstrapInvite.TokenUsed). Endpoint is rate-limited under the bootstrap policy (10 attempts per IP per 15 minutes).

Response format conventions

  • All responses use PascalCase JSON (PropertyNamingPolicy = null)
  • null fields are omitted (JsonIgnoreCondition.WhenWritingNull)
  • Enums are serialised as strings
  • Errors as ProblemDetails (application/problem+json)

Anti-enumeration responses

Endpoints that could otherwise reveal account existence (forgot-password, email-OTP login request, magic-link request) deliberately return the same response for "valid email" and "no such user" — and apply an artificial delay on the no-user path so timing analysis is no help either. This applies across the whole password-reset / magic-link / email-OTP family.

Auth status codes

StatusMeaning
200 { authenticated: true, ... }Success (cookie set)
200 { requiresTwoFactor: true, mfaMethods: [...] }Step-2 MFA needed
200 { requiresSecureSetup: true, gracePeriod: true, secureSetupDueAt }User still has to set up 2FA, time remaining
200 { requiresSecureSetup: true, gracePeriod: false }Grace period over, blocking
401Not authenticated or wrong credentials
403Authenticated but no permission, or passwordless-only realm
429Rate limit (Email OTP, Magic Link, bootstrap-admin)

Released under the Apache-2.0 License.