Skip to main content
Version: Current

Endpoint Reference

All endpoints are registered by MapPrimusAuthBroker(). Examples below use the default base path /api/auth.

CSRF — frontend responsibility

Every POST request requires an X-Primus-CSRF header. Your frontend is responsible for providing it:

  1. Call GET /api/auth/providers on app startup — this sets an XSRF-TOKEN cookie in the browser.
  2. Read that cookie value in JavaScript (it is not HttpOnly — JS access is intentional).
  3. Send that value as the X-Primus-CSRF header on every subsequent POST.

Without this header, the endpoint returns 400 Bad Request without processing the request.

Error handling

CodeMeaning
400Missing or invalid CSRF token
401Not authenticated, or invalid credentials
403Authenticated but not authorised — domain blocked, MFA mandatory but not enrolled, or insufficient role
422Malformed request body, or a token/code is expired or already used
429Rate limit exceeded — Retry-After header specifies seconds to wait
500Unexpected server error — check application logs
Identical 401 for local login

For POST /api/auth/login, the broker returns 401 for both “email not found” and “wrong password”. This is intentional — returning different messages would let an attacker determine which email addresses are registered in your system (user enumeration). Your frontend should always show a generic message such as “Incorrect email or password”.


Core Endpoints

1. GET /api/auth/providers — List providers and seed CSRF cookie

Call this once on app startup to know which login options to render. Also sets the XSRF-TOKEN cookie required before any POST request.

Response 200:

{
"providers": ["local", "azure"],
"loginUrls": {
"local": "/api/auth/login",
"azure": "/api/auth/azure"
},
"saml": []
}

2. GET /api/auth/me — Current authenticated user

Returns the authenticated user’s claims. Requires a valid session cookie.

Response 200:

{
"email": "alice@example.com",
"role": "Admin",
"provider": "local"
}
Where does role come from?

The role value is set by your IPrimusAuthUserStore implementation — it is whatever you store in PrimusAuthUser.Role for that user. It is written into the session at login time and is not re-read from your database on every request. If a user’s role changes, call POST /api/auth/refresh to re-issue the session with updated values.

Response 401: No valid session cookie.


3. POST /api/auth/login — Local email/password login

Request body:

{ "email": "alice@example.com", "password": "correct-password" }

Response codes:

CodeMeaning
200Login successful — session cookie set
202MFA required — body contains mfaPendingToken; submit to /mfa/verify next
400Missing or invalid CSRF token
401Invalid credentials
403MFA is mandatory but this user is not yet enrolled (mfaSetupRequired: true)
403Email domain not in allowlist
422Validation error (missing fields)
429Rate limit exceeded — Retry-After header included

202 body (MFA pending):

{
"mfaRequired": true,
"mfaPendingToken": "eyJ...",
"message": "Complete the MFA challenge at POST /api/auth/mfa/verify"
}

403 body (MFA enrollment required):

{
"mfaSetupRequired": true,
"message": "MFA enrollment is mandatory. Please enroll before continuing."
}

4. POST /api/auth/logout — Revoke session

Clears the session cookie and revokes the session on the server. Requires a valid CSRF token.

CodeMeaning
200Session revoked, cookie cleared
400Missing CSRF token
401Not authenticated

5. POST /api/auth/refresh — Rotate session

Rotates the session JTI and re-issues the session cookie. Use this after a role change to update the session claims.

CodeMeaning
200New session cookie set
401Session not found, expired, or JTI revoked

MFA Endpoints

MFA endpoints are only active when PrimusAuth:Mfa:Enabled = true.

6. POST /api/auth/mfa/verify — Complete MFA challenge

Call this after receiving a 202 from /api/auth/login. Accepts either a TOTP code or a backup code in the same code field.

Request body:

{
"mfaPendingToken": "eyJ...",
"code": "123456"
}
CodeMeaning
200MFA verified — session cookie set
401Invalid TOTP or backup code
422mfaPendingToken expired (5 min TTL), already used, or malformed

7. POST /api/auth/mfa/enroll — Begin TOTP enrollment

Requires authentication. Returns a secret and QR code URI to scan in an authenticator app.

Response 200:

{
"secret": "BASE32SECRET",
"otpauthUri": "otpauth://totp/YourApp:alice?secret=BASE32SECRET&issuer=YourApp",
"enrollToken": "eyJ...",
"issuer": "YourApp"
}

8. POST /api/auth/mfa/enroll/confirm — Confirm enrollment

Verify the first TOTP code from the authenticator app to complete setup. Returns backup codes — these are shown once only.

Request body:

{
"enrollToken": "eyJ...",
"code": "123456"
}

Response 200:

{
"backupCodes": ["ABCD1234", "EFGH5678", "IJKL9012", "..."]
}
CodeMeaning
200Enrollment confirmed, backup codes returned
422Wrong TOTP code, or enrollToken expired/malformed

9. POST /api/auth/mfa/disable — Disable MFA

Disables TOTP MFA for the authenticated user. Requires the current TOTP code.

Request body:

{ "code": "123456" }
CodeMeaning
200MFA disabled
403Cannot disable — RequireAllUsers=true
401Not authenticated

10. POST /api/auth/mfa/sms/enroll — Begin SMS MFA enrollment

Requires authentication. Sends a one-time verification code to the provided phone number.

Request body:

{ "phoneNumber": "+15551234567" }
CodeMeaning
200Code sent — proceed to confirm
401Not authenticated
422Phone number missing or malformed

11. POST /api/auth/mfa/sms/enroll/confirm — Confirm SMS enrollment

Verify the code sent during enrollment to activate SMS MFA.

Request body:

{ "code": "123456" }
CodeMeaning
200SMS MFA enrolled
401Not authenticated
422Wrong code, expired, or already used

12. POST /api/auth/mfa/sms/verify — Verify SMS code during login

Call this after receiving a 202 from /api/auth/login when SMS MFA is the active method.

Request body:

{
"mfaPendingToken": "eyJ...",
"code": "123456"
}
CodeMeaning
200MFA verified — session cookie set
401Invalid code
422mfaPendingToken expired (5 min TTL) or already used

OIDC SSO Endpoints

Each provider initiates an OIDC redirect. After callback, the Broker calls IPrimusAuthUserStore.FindByEmailAsync() to look up the user, then AutoProvisionUserAsync() for first-time / JIT provisioning, and issues a session cookie.

EndpointProvider
GET /api/auth/azureAzure AD (Microsoft Entra ID)
GET /api/auth/azure/admin-consentAzure AD admin consent grant
GET /api/auth/googleGoogle
GET /api/auth/auth0Auth0
GET /api/auth/oktaOkta
GET /api/auth/saml/{scheme}SAML 2.0 (any configured IdP)

Each redirect endpoint accepts an optional ?returnUrl= query parameter validated against allowed origins. Open redirects are not possible.

OIDC + RequireAllUsers

When Mfa.RequireAllUsers = true, the OIDC callback checks MFA enrolment. If not enrolled, authentication fails with mfa_setup_required. Configure a OnRemoteFailure handler to redirect to your MFA enrolment page.

For per-provider setup guides, see:


Passwordless Endpoints

13. POST /api/auth/email-otp/send — Send email OTP

Sends a one-time code to the provided email. Always returns 200 — the response is identical whether or not the address is registered, preventing email enumeration.

Request body:

{ "email": "alice@example.com" }
CodeMeaning
200Code sent (or silently dropped if email not found)

14. POST /api/auth/email-otp/verify — Verify email OTP

Request body:

{ "email": "alice@example.com", "code": "748291" }
CodeMeaning
200Code valid — session cookie set
401Wrong code
422Code expired or already used

15. POST /api/auth/magic-link/send — Send magic link

Sends a one-click login link to the provided email. Always returns 200.

Request body:

{ "email": "alice@example.com" }
CodeMeaning
200Link sent (or silently dropped if email not found)

16. GET /api/auth/magic-link/verify — Consume magic link

Called by the browser when the user clicks the link. Validates the token and redirects.

Query parameter: ?token=<signed-token>

CodeMeaning
302Token valid — redirects to returnUrl with session cookie set
422Token expired (default TTL: 15 min) or already used

17. POST /api/auth/sms/send — Send SMS OTP

Sends a one-time code to the provided phone number.

Request body:

{ "phoneNumber": "+15551234567" }
CodeMeaning
200Code sent
422Phone number missing or malformed

18. POST /api/auth/sms/verify — Verify SMS OTP

Request body:

{ "phoneNumber": "+15551234567", "code": "123456" }
CodeMeaning
200Code valid — session cookie set
401Wrong code
422Code expired or already used

Session Management

19. GET /api/auth/sessions — List active sessions

Returns all active sessions for the authenticated user. Use this to power a “Log out other devices” UI.

Response 200:

[
{
"sessionId": "sess_01HX...",
"createdAt": "2026-03-14T10:00:00Z",
"lastSeenAt": "2026-03-14T11:30:00Z",
"userAgent": "Mozilla/5.0 ...",
"ipAddress": "192.168.1.1"
}
]

Impersonation

20. POST /api/auth/impersonate — Start impersonation session

Requires Admin or SuperAdmin role. Overwrites the caller's session cookie with the target user's identity. An act claim records the original admin's email. The action is recorded by IPrimusAuthAuditSink as ImpersonateStart.

Request body:

{ "targetEmail": "user@example.com" }
CodeMeaning
200Impersonation active — session cookie replaced with target user's identity
401Not authenticated
403Caller does not have Admin or SuperAdmin role
404No user found with targetEmail
429Rate limit exceeded

Response 200:

{ "message": "Impersonating user@example.com" }

21. POST /api/auth/revert — End impersonation

Ends the impersonation session. Clears the session entirely — the admin must log in again. The action is recorded by IPrimusAuthAuditSink as ImpersonateEnd.

Revert signs you out completely

This endpoint calls SignOut and clears your session. It does not restore your original admin session. You must log in again after reverting. This is intentional — storing multiple concurrent session states introduces significant complexity and risk.

CodeMeaning
200Impersonation ended — session cleared, log in again to resume admin access
400Not currently impersonating

Response 200:

{ "message": "Impersonation ended. Please log in as Admin again." }

Passkey (WebAuthn) Endpoints

22–25. Passkey (WebAuthn)
#EndpointDescription
22POST /api/auth/passkey/register/beginReturns a WebAuthn challenge for credential creation. Requires authentication.
23POST /api/auth/passkey/register/completeVerifies the browser's credential creation response and stores the passkey.
24POST /api/auth/passkey/login/beginReturns a WebAuthn challenge for authentication. Unauthenticated — passkey replaces the login flow.
25POST /api/auth/passkey/login/completeVerifies the browser's authentication assertion and issues a session cookie.

Organization Endpoints

26–30. Organization endpoints
#EndpointMethodDescription
26/api/auth/org/invitePOSTSends an invitation email to a new org member.
27/api/auth/org/invite/acceptGETAccepts an invitation by token.
28/api/auth/org/invite/pendingGETLists pending invitations for the current org.
29/api/auth/org/membershipsGETLists the current user's org memberships.
30/api/auth/org/switchPOSTSwitches the active org context — re-issues the session cookie with an updated orgId claim.

Admin Analytics Endpoints

31–33. Admin analytics — requires Admin role
#EndpointDescription
31GET /api/auth/admin/analytics/logins?from=&to=Login count summary for a time range.
32GET /api/auth/admin/analytics/active-users?since=List of active user IDs since a given time.
33GET /api/auth/admin/analytics/events?userId=&eventType=&limit=Raw audit event query — filterable by user, event type, and count.

Password Policy Endpoint

34. POST /api/auth/password/validate — Check password strength

Validates a candidate password against the configured PasswordPolicy without changing the user's password. Useful for real-time strength feedback in your UI.

Request body:

{ "password": "MyP@ssw0rd!" }

Response 200 — valid:

{ "valid": true, "score": 4 }

Response 422 — policy failure:

{
"valid": false,
"errors": ["Too short (minimum 8 characters)", "Missing uppercase letter"]
}

Diagnostics Endpoint

35. GET /api/auth/__auth/diagnostics — Dev only

Returns internal broker status information. Only registered when ASPNETCORE_ENVIRONMENT is Development (hardcoded in the SDK — not configurable via appsettings).

Never run in production

This endpoint exposes internal configuration and user store information. It is automatically disabled in all non-Development environments.


Required Service Interfaces

You must register implementations of the interfaces your features need. Most have built-in defaults:

InterfaceRequired?DefaultPurpose
IPrimusAuthUserStoreYes (prod)In-memory (dev), Null/blocks all (prod)Look up users by email; provision from OIDC claims
IPrimusAuthCredentialValidatorOnly for local login(none)Verify email + password
IPrimusAuthAuditSinkNoLoggerAuditSinkReceive auth events
IPrimusMfaStoreOnly when Mfa.Enabled=true(none)Store TOTP secrets and backup codes
IPrimusOtpStoreOnly for email OTP(none)Store email OTP codes
IPrimusSmsOtpStoreOnly when SMS OTP is enabled(none)Store SMS OTP codes
IPrimusOrgInvitationStoreOnly for org invitations(none)Store pending invitations

Optional extras (only needed for the matching feature):

InterfaceFeature
IPrimusEmailSenderEmail OTP and magic link sending
IPrimusSmsSenderSMS OTP sending
IPrimusPasskeyStorePasskey credential storage

Next Steps