Endpoint Reference
All endpoints are registered by MapPrimusAuthBroker(). Examples below use the default base path /api/auth.
Every POST request requires an X-Primus-CSRF header. Your frontend is responsible for providing it:
- Call
GET /api/auth/providerson app startup — this sets anXSRF-TOKENcookie in the browser. - Read that cookie value in JavaScript (it is not
HttpOnly— JS access is intentional). - Send that value as the
X-Primus-CSRFheader on every subsequentPOST.
Without this header, the endpoint returns 400 Bad Request without processing the request.
Error handling
| Code | Meaning |
|---|---|
400 | Missing or invalid CSRF token |
401 | Not authenticated, or invalid credentials |
403 | Authenticated but not authorised — domain blocked, MFA mandatory but not enrolled, or insufficient role |
422 | Malformed request body, or a token/code is expired or already used |
429 | Rate limit exceeded — Retry-After header specifies seconds to wait |
500 | Unexpected server error — check application logs |
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"
}
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:
| Code | Meaning |
|---|---|
200 | Login successful — session cookie set |
202 | MFA required — body contains mfaPendingToken; submit to /mfa/verify next |
400 | Missing or invalid CSRF token |
401 | Invalid credentials |
403 | MFA is mandatory but this user is not yet enrolled (mfaSetupRequired: true) |
403 | Email domain not in allowlist |
422 | Validation error (missing fields) |
429 | Rate 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.
| Code | Meaning |
|---|---|
200 | Session revoked, cookie cleared |
400 | Missing CSRF token |
401 | Not 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.
| Code | Meaning |
|---|---|
200 | New session cookie set |
401 | Session 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"
}
| Code | Meaning |
|---|---|
200 | MFA verified — session cookie set |
401 | Invalid TOTP or backup code |
422 | mfaPendingToken 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", "..."]
}
| Code | Meaning |
|---|---|
200 | Enrollment confirmed, backup codes returned |
422 | Wrong 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" }
| Code | Meaning |
|---|---|
200 | MFA disabled |
403 | Cannot disable — RequireAllUsers=true |
401 | Not 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" }
| Code | Meaning |
|---|---|
200 | Code sent — proceed to confirm |
401 | Not authenticated |
422 | Phone 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" }
| Code | Meaning |
|---|---|
200 | SMS MFA enrolled |
401 | Not authenticated |
422 | Wrong 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"
}
| Code | Meaning |
|---|---|
200 | MFA verified — session cookie set |
401 | Invalid code |
422 | mfaPendingToken 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.
| Endpoint | Provider |
|---|---|
GET /api/auth/azure | Azure AD (Microsoft Entra ID) |
GET /api/auth/azure/admin-consent | Azure AD admin consent grant |
GET /api/auth/google | |
GET /api/auth/auth0 | Auth0 |
GET /api/auth/okta | Okta |
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.
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" }
| Code | Meaning |
|---|---|
200 | Code 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" }
| Code | Meaning |
|---|---|
200 | Code valid — session cookie set |
401 | Wrong code |
422 | Code 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" }
| Code | Meaning |
|---|---|
200 | Link 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>
| Code | Meaning |
|---|---|
302 | Token valid — redirects to returnUrl with session cookie set |
422 | Token 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" }
| Code | Meaning |
|---|---|
200 | Code sent |
422 | Phone number missing or malformed |
18. POST /api/auth/sms/verify — Verify SMS OTP
Request body:
{ "phoneNumber": "+15551234567", "code": "123456" }
| Code | Meaning |
|---|---|
200 | Code valid — session cookie set |
401 | Wrong code |
422 | Code 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" }
| Code | Meaning |
|---|---|
200 | Impersonation active — session cookie replaced with target user's identity |
401 | Not authenticated |
403 | Caller does not have Admin or SuperAdmin role |
404 | No user found with targetEmail |
429 | Rate 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.
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.
| Code | Meaning |
|---|---|
200 | Impersonation ended — session cleared, log in again to resume admin access |
400 | Not currently impersonating |
Response 200:
{ "message": "Impersonation ended. Please log in as Admin again." }
Passkey (WebAuthn) Endpoints
22–25. Passkey (WebAuthn)
| # | Endpoint | Description |
|---|---|---|
| 22 | POST /api/auth/passkey/register/begin | Returns a WebAuthn challenge for credential creation. Requires authentication. |
| 23 | POST /api/auth/passkey/register/complete | Verifies the browser's credential creation response and stores the passkey. |
| 24 | POST /api/auth/passkey/login/begin | Returns a WebAuthn challenge for authentication. Unauthenticated — passkey replaces the login flow. |
| 25 | POST /api/auth/passkey/login/complete | Verifies the browser's authentication assertion and issues a session cookie. |
Organization Endpoints
26–30. Organization endpoints
| # | Endpoint | Method | Description |
|---|---|---|---|
| 26 | /api/auth/org/invite | POST | Sends an invitation email to a new org member. |
| 27 | /api/auth/org/invite/accept | GET | Accepts an invitation by token. |
| 28 | /api/auth/org/invite/pending | GET | Lists pending invitations for the current org. |
| 29 | /api/auth/org/memberships | GET | Lists the current user's org memberships. |
| 30 | /api/auth/org/switch | POST | Switches the active org context — re-issues the session cookie with an updated orgId claim. |
Admin Analytics Endpoints
31–33. Admin analytics — requires Admin role
| # | Endpoint | Description |
|---|---|---|
| 31 | GET /api/auth/admin/analytics/logins?from=&to= | Login count summary for a time range. |
| 32 | GET /api/auth/admin/analytics/active-users?since= | List of active user IDs since a given time. |
| 33 | GET /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).
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:
| Interface | Required? | Default | Purpose |
|---|---|---|---|
IPrimusAuthUserStore | Yes (prod) | In-memory (dev), Null/blocks all (prod) | Look up users by email; provision from OIDC claims |
IPrimusAuthCredentialValidator | Only for local login | (none) | Verify email + password |
IPrimusAuthAuditSink | No | LoggerAuditSink | Receive auth events |
IPrimusMfaStore | Only when Mfa.Enabled=true | (none) | Store TOTP secrets and backup codes |
IPrimusOtpStore | Only for email OTP | (none) | Store email OTP codes |
IPrimusSmsOtpStore | Only when SMS OTP is enabled | (none) | Store SMS OTP codes |
IPrimusOrgInvitationStore | Only for org invitations | (none) | Store pending invitations |
Optional extras (only needed for the matching feature):
| Interface | Feature |
|---|---|
IPrimusEmailSender | Email OTP and magic link sending |
IPrimusSmsSender | SMS OTP sending |
IPrimusPasskeyStore | Passkey credential storage |
Next Steps
- Integration Guide — complete setup walkthrough from install to first authenticated request
- Advanced Configuration — custom stores, HA clustering, and data protection
- Database Setup — persist users with EF Core
- MFA Deep Dive — all MFA endpoints explained with enrollment and recovery flows