Skip to main content

Admin Operations

Build a complete admin panel for user management. By the end of this guide you will have these endpoints working:

User management:

EndpointMethodPurpose
/auth/admin/signupPOSTCreate a user with overrides (bypass verification, generate password)
/auth/admin/signup-socialPOSTImport a social user (migration from Cognito, Auth0, etc.)
/auth/admin/usersGETList users with filters and pagination
/auth/admin/users/:subGETGet a single user by UUID
/auth/admin/users/:subDELETEDelete a user (cascade cleanup)
/auth/admin/users/:sub/disablePOSTLock a user account
/auth/admin/users/:sub/enablePOSTUnlock a user account

Password management:

EndpointMethodPurpose
/auth/admin/set-passwordPOSTSet a user's password directly
/auth/admin/reset-password/initiatePOSTSend a password reset code via email or SMS
/auth/admin/users/:sub/force-password-changePOSTForce password change on next login

Session management:

EndpointMethodPurpose
/auth/admin/users/:sub/sessionsGETList all active sessions for a user
/auth/admin/users/:sub/logout-allPOSTSign out a user from all devices

MFA management:

EndpointMethodPurpose
/auth/admin/users/:sub/mfa/statusGETCheck a user's MFA enrollment
/auth/admin/users/:sub/mfa/devicesGETList a user's MFA devices
/auth/admin/mfa/devices/:deviceIdDELETERemove an MFA device
/auth/admin/users/:sub/mfa/devices/:deviceId/preferredPOSTSet preferred MFA device
/auth/admin/mfa/exemptionPOSTGrant or revoke MFA exemption

Audit:

EndpointMethodPurpose
/auth/admin/audit/historyGETGet authentication audit history for a user
Sample apps

Admin routes are fully implemented in the nauth example apps — see the Express and Fastify examples for admin routes, and the Angular example for admin.component.ts.

Admin operations require an authorization provider

nauth-toolkit authenticates but ships no role model, so it cannot decide who is an admin. requireAuth() only proves the caller has a valid JWT — on its own, that makes every endpoint below reachable by any signed-in user.

Supply an IAuthorizationProvider. It is enforced inside the services, so it protects a controller you write and a background script just as it protects the shipped routes. Mounting the admin route group without one is refused at startup.

Prerequisites​

Exposing the admin API: pick one approach​

There are two ways to get the admin endpoints onto your app. Choose one — you do not need both.

ApproachUse it when
Option A: Mount the shipped routesDefault. The endpoints ship with the toolkit — mount them and they are live.
Option B: Write the routes yourselfYou need custom paths, extra guards, or a different response shape.

Both call the same services, so every operation documented in the rest of this guide behaves identically either way.

Option A: Mount the shipped routes​

The admin endpoints ship with the toolkit. Mount them on their own prefix so the surface can be firewalled off separately from your public auth routes.

src/config/auth.config.ts
routes: [
{ prefix: 'auth' },
{ prefix: 'admin', groups: ['admin'] },
],
src/auth/auth.module.ts
AuthModule.forRoot({ ...authConfig, authorization: RoleAuthorizer })

Route Groups lists every route the admin group contains. Drop individual endpoints you do not want exposed:

{ prefix: 'admin', groups: ['admin'], exclude: ['adminSetPassword', 'adminDeleteUser'] }
Paths differ from the hand-written examples in Option B

The shipped admin bundle mounts on its own prefix, so its paths are /admin/users/:sub. The hand-written examples in Option B nest under /auth/admin/*, matching examples/demo-nestjs. Both work — the shipped bundle keeps admin separable at the network layer. See Shipped Routes for the full table.

API keys cannot reach the shipped admin routes at all: every one sets apiKey: 'deny', which the guard enforces before the handler runs. The viaApiKey flag on the authorization context is for admin routes you write yourself and mark allowApiKey.

Option B: Write the routes yourself​

Skip this section if you mounted the shipped routes in Option A.

The admin API uses two services from nauth-toolkit:

  • adminAuthService — user CRUD, passwords, sessions
  • mfaService — MFA status, devices, exemptions

Both are available on the nauth instance (Express/Fastify) or injectable via the NestJS module.

User management routes​

src/auth/admin.controller.ts
import {
Controller, Get, Post, Delete, Body, Param, Query,
UseGuards, HttpCode, HttpStatus,
} from '@nestjs/common';
import { AdminAuthService, AuthGuard } from '@nauth-toolkit/nestjs';

// WARNING: AuthGuard only verifies the JWT is valid (authentication).
// You MUST add your own authorization guard to restrict access to admins.
// Example: @UseGuards(AuthGuard, YourAdminGuard)
@UseGuards(AuthGuard)
@Controller('auth/admin')
export class AdminController {
constructor(private readonly adminAuthService: AdminAuthService) {}

@Post('signup')
@HttpCode(HttpStatus.CREATED)
async createUser(@Body() dto: any) {
return await this.adminAuthService.signup(dto);
}

@Post('signup-social')
@HttpCode(HttpStatus.CREATED)
async importSocialUser(@Body() dto: any) {
return await this.adminAuthService.signupSocial(dto);
}

@Get('users')
async listUsers(@Query() query: any) {
return await this.adminAuthService.getUsers(query);
}

@Get('users/:sub')
async getUser(@Param('sub') sub: string) {
return await this.adminAuthService.getUserById({ sub });
}

@Delete('users/:sub')
@HttpCode(HttpStatus.OK)
async deleteUser(@Param('sub') sub: string) {
return await this.adminAuthService.deleteUser({ sub });
}

@Post('users/:sub/disable')
@HttpCode(HttpStatus.OK)
async disableUser(@Param('sub') sub: string, @Body() body: any) {
return await this.adminAuthService.disableUser({ sub, reason: body?.reason });
}

@Post('users/:sub/enable')
@HttpCode(HttpStatus.OK)
async enableUser(@Param('sub') sub: string) {
return await this.adminAuthService.enableUser({ sub });
}

@Post('set-password')
@HttpCode(HttpStatus.OK)
async setPassword(@Body() dto: any) {
return await this.adminAuthService.setPassword(dto);
}

@Post('reset-password/initiate')
@HttpCode(HttpStatus.OK)
async resetPassword(@Body() dto: any) {
return await this.adminAuthService.resetPassword(dto);
}

@Post('users/:sub/force-password-change')
@HttpCode(HttpStatus.OK)
async forcePasswordChange(@Param('sub') sub: string) {
return await this.adminAuthService.setMustChangePassword({ sub });
}

@Get('users/:sub/sessions')
async getUserSessions(@Param('sub') sub: string) {
return await this.adminAuthService.getUserSessions({ sub });
}

@Post('users/:sub/logout-all')
@HttpCode(HttpStatus.OK)
async logoutAll(@Param('sub') sub: string, @Body() body: any) {
return await this.adminAuthService.logoutAll({ sub, forgetDevices: body?.forgetDevices });
}
}

Register the controller in your auth module.

Admin MFA routes​

src/auth/admin-mfa.controller.ts
import {
Controller, Get, Post, Delete, Body, Param,
UseGuards, HttpCode, HttpStatus, Inject,
} from '@nestjs/common';
import { AuthGuard, MFAService } from '@nauth-toolkit/nestjs';

// WARNING: Add your own authorization guard alongside AuthGuard.
@UseGuards(AuthGuard)
@Controller('auth/admin')
export class AdminMfaController {
constructor(
@Inject(MFAService)
private readonly mfaService: MFAService,
) {}

@Get('users/:sub/mfa/status')
async getMfaStatus(@Param('sub') sub: string) {
return await this.mfaService.adminGetMfaStatus({ sub });
}

@Get('users/:sub/mfa/devices')
async getMfaDevices(@Param('sub') sub: string) {
return await this.mfaService.adminGetUserDevices({ sub });
}

@Delete('mfa/devices/:deviceId')
@HttpCode(HttpStatus.OK)
async removeDevice(@Param('deviceId') deviceId: string) {
return await this.mfaService.adminRemoveDevice({ deviceId: Number(deviceId) });
}

@Post('users/:sub/mfa/devices/:deviceId/preferred')
@HttpCode(HttpStatus.OK)
async setPreferredDevice(@Param('sub') sub: string, @Param('deviceId') deviceId: string) {
return await this.mfaService.adminSetPreferredDevice({ sub, deviceId: Number(deviceId) });
}

@Post('mfa/exemption')
@HttpCode(HttpStatus.OK)
async setMfaExemption(@Body() dto: any) {
return await this.mfaService.setMFAExemption(dto);
}
}

Register the controller in your auth module.

Admin audit route​

src/auth/admin.controller.ts
// Add to your AdminController:

@Get('audit/history')
async getAuditHistory(@Query() query: any) {
return await this.auditService.getUserAuthHistory(query);
}

Inject AuthAuditService (or access it via the nauth instance) in the constructor.

Step 1: User Management​

Create a user​

POST /auth/admin/signup

Request body:

{
"email": "john@example.com",
"password": "SecurePass123!",
"firstName": "John",
"lastName": "Doe",
"isEmailVerified": true,
"mustChangePassword": false
}

Response:

{
"user": {
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"isEmailVerified": true,
"isPhoneVerified": false,
"isActive": true,
"isLocked": false,
"mfaEnabled": false,
"createdAt": "2025-01-15T10:30:00.000Z"
}
}

Create with auto-generated password​

{
"email": "john@example.com",
"generatePassword": true,
"isEmailVerified": true,
"mustChangePassword": true
}

Response — includes the one-time generated password:

{
"user": { "sub": "...", "email": "john@example.com" },
"generatedPassword": "Kx9#mP2$vN7@qR4!"
}

The generated password is returned once and never stored in plain text. Deliver it securely to the user.

When mustChangePassword: true, the user's next login returns a FORCE_CHANGE_PASSWORD challenge — they must set a new password before receiving tokens. See Basic Auth Flows > Other challenge types for challenge handling.

All request fields​

FieldTypeRequiredDefaultDescription
emailstringYes—User email address
passwordstringConditional—Required unless generatePassword: true
usernamestringNo—Unique username (3-50 chars)
firstNamestringNo—First name
lastNamestringNo—Last name
phonestringNo—Phone in E.164 format (+14155552671)
metadataobjectNo—Custom fields
isEmailVerifiedbooleanNofalseSkip email verification
isPhoneVerifiedbooleanNofalseSkip phone verification
mustChangePasswordbooleanNofalseForce password change on next login
generatePasswordbooleanNofalseAuto-generate a secure password

Import a social user​

Use this to migrate users from Cognito, Auth0, Firebase, or any OAuth provider.

POST /auth/admin/signup-social

Request body:

{
"email": "jane@example.com",
"provider": "google",
"providerId": "google_12345",
"providerEmail": "jane@gmail.com",
"firstName": "Jane",
"lastName": "Smith"
}

Response:

{
"user": {
"sub": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Smith",
"isEmailVerified": true,
"hasSocialAuth": true,
"socialProviders": ["google"]
},
"socialAccount": {
"provider": "google",
"providerId": "google_12345",
"providerEmail": "jane@gmail.com"
}
}

For hybrid accounts (social + password), include the password field. The user can then log in with either method.

FieldTypeRequiredDescription
emailstringYesUser email (auto-verified for social imports)
providerstringYes'google' | 'apple' | 'facebook'
providerIdstringYesProvider's unique user ID
providerEmailstringNoEmail from the provider (may differ from user email)
passwordstringNoFor hybrid social + password accounts
socialMetadataobjectNoRaw OAuth profile data (stored for audit)
firstName, lastName, username, phone, metadata—NoSame as admin signup
isPhoneVerifiedbooleanNoSkip phone verification
mustChangePasswordbooleanNoOnly relevant if password is provided

List users​

GET /auth/admin/users?page=1&limit=20&isEmailVerified=true&sortBy=createdAt&sortOrder=DESC

Response:

{
"users": [
{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"isEmailVerified": true,
"isPhoneVerified": false,
"isActive": true,
"isLocked": false,
"mfaEnabled": true,
"hasSocialAuth": false,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-02-01T14:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 156,
"totalPages": 8
}
}

Filter parameters​

ParameterTypeDescription
pagenumberPage number (1-indexed, default: 1)
limitnumberRecords per page (default: 10, max: 100)
emailstringPartial match on email
phonestringPartial match on phone
isEmailVerifiedbooleanFilter by email verification status
isPhoneVerifiedbooleanFilter by phone verification status
hasSocialAuthbooleanFilter by social auth presence
isLockedbooleanFilter by account lock status
mfaEnabledbooleanFilter by MFA enrollment
createdAt[operator]stringDate comparison: gt, gte, lt, lte, eq
createdAt[value]stringISO 8601 date
updatedAt[operator]stringSame operators as createdAt
updatedAt[value]stringISO 8601 date
sortBystring'email' | 'createdAt' | 'updatedAt' | 'username' | 'phone'
sortOrderstring'ASC' | 'DESC' (default: 'DESC')

Get a single user​

GET /auth/admin/users/:sub

Returns the same user object as listed above.

Delete a user​

DELETE /auth/admin/users/:sub

Response — cascade cleanup counts:

{
"success": true,
"deletedUserId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"deletedRecords": {
"sessions": 3,
"verificationTokens": 1,
"mfaDevices": 2,
"trustedDevices": 1,
"socialAccounts": 1,
"loginAttempts": 12,
"challengeSessions": 0,
"auditLogs": 47
}
}

Disable a user​

Locks the account permanently and revokes all active sessions immediately. The next login attempt fails with AUTH_ACCOUNT_LOCKED.

POST /auth/admin/users/:sub/disable

Request body (optional):

{
"reason": "Account compromised"
}

Response:

{
"success": true,
"user": {
"sub": "a1b2c3d4-...",
"isActive": true,
"isLocked": true
},
"revokedSessions": 3
}

The call writes isLocked, lockReason, lockedAt and lockedUntil. It does not change isActive, which stays true. See isActive vs isLocked.

Enable a user​

Clears the lock set by disable and resets the stored failed-attempt counter on the user row.

POST /auth/admin/users/:sub/enable

Response:

{
"success": true,
"user": {
"sub": "a1b2c3d4-...",
"isActive": true,
"isLocked": false
}
}

isActive vs isLocked​

Both fields appear on every user response and both block login, but only one of them has an API behind it.

isLockedisActive
Set bydisable onlytrue at signup; nothing sets it false
Cleared byenable onlyNothing
Login errorAUTH_ACCOUNT_LOCKEDAUTH_ACCOUNT_INACTIVE
Filterable in list usersYesNo

isLocked is the one you manage, and disable always makes it permanent: it writes lockedUntil: null, which never expires and needs enable to clear.

The failed-login lockout is a separate mechanism and does not appear on the user row at all. It is keyed by IP address in the storage adapter, and a caller who trips it gets RATE_LIMIT_LOGIN, not AUTH_ACCOUNT_LOCKED. enable therefore does not release it; it expires on its own.

isActive is a legacy account-lifecycle flag. It is still enforced at login, but no route, service method or DTO in the toolkit sets it to false, so it only ever matters if you write to the column directly from your own code. For ordinary account suspension use disable.

Step 2: Password Management​

Set password directly​

Sets a new password without requiring the old one. Optionally revokes all sessions.

POST /auth/admin/set-password

Request body:

{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"newPassword": "NewSecurePass123!",
"mustChangePassword": true,
"revokeSessions": true
}

Response:

{
"success": true,
"mustChangePassword": true,
"sessionsRevoked": 3
}

Initiate password reset​

Sends a reset code via email or SMS. The user completes the reset via POST /auth/reset-password/confirm below — not /auth/forgot-password/confirm, which is the self-service flow and validates a different code.

POST /auth/admin/reset-password/initiate

Request body:

{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"deliveryMethod": "email",
"baseUrl": "https://myapp.com/reset-password",
"revokeSessions": true,
"reason": "User requested via support ticket"
}

Response:

{
"success": true,
"destination": "j***@example.com",
"deliveryMedium": "email",
"expiresIn": 3600,
"sessionsRevoked": 3
}
FieldTypeDefaultDescription
substring—User UUID (required)
deliveryMethodstring'email''email' or 'sms'
baseUrlstring—Base URL for building the reset link (optional)
codeExpiresInnumber3600Code expiry in seconds (300–86400)
revokeSessionsbooleanfalseRevoke all sessions immediately
reasonstring—Audit trail reason (max 500 chars)

When baseUrl is set, the link also carries an email query param (e.g. ?code=123456&email=user%40example.com) so the consumer app's reset page can prepopulate the identifier field instead of asking the user to retype it — the admin already has the address on hand, so this adds no exposure the self-service flow doesn't already have. The self-service forgot-password link (AuthService.forgotPassword) does not include email.

Confirm password reset​

Public endpoint — the user calls this directly with the code from the email/SMS, no admin auth involved. It only accepts codes generated by Initiate password reset above; a self-service forgot-password code is rejected here, and vice versa.

POST /auth/reset-password/confirm

Request body:

{
"identifier": "user@example.com",
"code": "123456",
"newPassword": "NewSecurePass123!"
}

Response:

{
"success": true
}
FieldTypeDescription
identifierstringEmail, username, or phone — matches the target user (required)
codestring6–10 digit code from the reset email/SMS (required)
newPasswordstring8–128 chars (required)

:::warning Wrong-endpoint failures look identical Posting this code to /auth/forgot-password/confirm instead returns the same PASSWORD_RESET_CODE_INVALID error as a genuinely wrong code — there's no distinct error for "right code, wrong endpoint." If a frontend page handles both admin-sent and self-service reset links, pick the endpoint from something in the link itself — the admin link's email query param is one such marker (the self-service link never has one), or append your own to the admin baseUrl. :::

Force password change​

Flags the user to change their password on the next login. The next login returns a FORCE_CHANGE_PASSWORD challenge.

POST /auth/admin/users/:sub/force-password-change

Response:

{
"success": true
}

Step 3: Session Management​

Get user sessions​

GET /auth/admin/users/:sub/sessions

Response:

{
"sessions": [
{
"sessionId": "sess_abc123",
"deviceId": "dev_xyz789",
"deviceName": "Chrome on MacOS",
"deviceType": "desktop",
"platform": "macOS",
"browser": "Chrome 120",
"ipAddress": "203.0.113.42",
"ipCountry": "US",
"ipCity": "San Francisco",
"lastActivityAt": "2025-02-15T10:30:00.000Z",
"createdAt": "2025-02-01T08:00:00.000Z",
"expiresAt": "2025-03-01T08:00:00.000Z",
"isTrustedDevice": true,
"isCurrent": false,
"authMethod": "password",
"authProvider": null
}
]
}

Logout all sessions​

Signs the user out from all devices. Optionally revokes trusted device status.

POST /auth/admin/users/:sub/logout-all

Request body (optional):

{
"forgetDevices": true
}

Response:

{
"revokedCount": 5
}

Step 4: Admin MFA Management​

Get MFA status​

GET /auth/admin/users/:sub/mfa/status

Response:

{
"enabled": true,
"required": false,
"configuredMethods": ["totp", "passkey"],
"availableMethods": ["email", "sms", "totp", "passkey"],
"preferredMethod": "totp"
}

List MFA devices​

GET /auth/admin/users/:sub/mfa/devices

Response:

{
"devices": [
{
"id": 1,
"type": "totp",
"name": "Google Authenticator",
"isPreferred": true,
"isActive": true,
"createdAt": "2025-01-10T12:00:00.000Z"
},
{
"id": 2,
"type": "passkey",
"name": "MacBook Pro Touch ID",
"isPreferred": false,
"isActive": true,
"createdAt": "2025-01-15T09:00:00.000Z"
}
]
}

Remove an MFA device​

DELETE /auth/admin/mfa/devices/:deviceId

Response:

{
"removedDeviceId": 2,
"message": "Device removed successfully"
}

Set preferred MFA device​

POST /auth/admin/users/:sub/mfa/devices/:deviceId/preferred

Response:

{
"message": "Preferred device updated"
}

Grant or revoke MFA exemption​

Exempt a user from MFA requirements (e.g., service accounts).

POST /auth/admin/mfa/exemption

Request body:

{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"exempt": true,
"reason": "Service account — no human login",
"grantedBy": "admin-user-uuid"
}

Response:

{
"message": "MFA exemption granted"
}

Step 5: Frontend SDK​

The frontend SDK provides client.admin.* methods that call the admin endpoints above. This works with any frontend framework.

Configure​

Enable admin operations by adding the admin block to your client config:

import { NAuthClient } from '@nauth-toolkit/client';

const auth = new NAuthClient({
baseUrl: 'https://api.example.com',
authPathPrefix: '/auth',
tokenDelivery: 'cookies',
admin: {
pathPrefix: '/admin', // Matches your backend route prefix
},
});

The SDK builds admin URLs as: baseUrl + authPathPrefix + admin.pathPrefix + endpoint path. With the config above, getUsers() calls https://api.example.com/auth/admin/users.

Configuration options​

OptionTypeDefaultDescription
pathPrefixstring'/admin'Prefix prepended to all admin endpoint paths
endpointsobject—Override individual endpoint paths (see below)
headersobject—Additional headers sent with every admin request

Default endpoint paths​

All paths are relative to authPathPrefix + admin.pathPrefix:

KeyDefault PathMethod
signup/signupPOST
signupSocial/signup-socialPOST
getUsers/usersGET
getUser/users/:subGET
deleteUser/users/:subDELETE
disableUser/users/:sub/disablePOST
enableUser/users/:sub/enablePOST
forcePasswordChange/users/:sub/force-password-changePOST
setPassword/set-passwordPOST
resetPasswordInitiate/reset-password/initiatePOST
getUserSessions/users/:sub/sessionsGET
logoutAll/users/:sub/logout-allPOST
getMfaStatus/users/:sub/mfa/statusGET
getMfaDevices/users/:sub/mfa/devicesGET
removeMfaDeviceById/mfa/devices/:deviceIdDELETE
setPreferredMfaDevice/users/:sub/mfa/devices/:deviceId/preferredPOST
setMfaExemption/mfa/exemptionPOST
getAuditHistory/audit/historyGET

Override individual paths if your backend uses different URLs:

admin: {
pathPrefix: '/admin',
endpoints: {
getUsers: '/users/list', // Override just this one
},
},

User management​

// Create a user
const { user, generatedPassword } = await auth.admin.createUser({
email: 'john@example.com',
generatePassword: true,
isEmailVerified: true,
mustChangePassword: true,
});

// Import a social user
const { user, socialAccount } = await auth.admin.importSocialUser({
email: 'jane@example.com',
provider: 'google',
providerId: 'google_12345',
providerEmail: 'jane@gmail.com',
});

// List users with filters
const { users, pagination } = await auth.admin.getUsers({
page: 1,
limit: 20,
isEmailVerified: true,
mfaEnabled: false,
sortBy: 'createdAt',
sortOrder: 'DESC',
});

// Date range filter
const { users } = await auth.admin.getUsers({
createdAt: { operator: 'gte', value: new Date('2025-01-01') },
});

// Get a single user
const user = await auth.admin.getUser('a1b2c3d4-...');

// Delete a user
const { deletedRecords } = await auth.admin.deleteUser('a1b2c3d4-...');

// Disable / enable
const { revokedSessions } = await auth.admin.disableUser('a1b2c3d4-...', 'Suspicious activity');
await auth.admin.enableUser('a1b2c3d4-...');

Password management​

// Set password directly (temporary, and revokes sessions, by default)
await auth.admin.setPassword('a1b2c3d4-...', 'NewSecurePass123!');

// Set a permanent password and leave sessions alone
await auth.admin.setPassword('a1b2c3d4-...', 'NewSecurePass123!', {
mustChangePassword: false,
revokeSessions: false,
});

// Initiate password reset (sends email/SMS)
const { destination } = await auth.admin.initiatePasswordReset({
sub: 'a1b2c3d4-...',
deliveryMethod: 'email',
baseUrl: 'https://myapp.com/reset-password',
revokeSessions: true,
});

// Force password change on next login
await auth.admin.forcePasswordChange('a1b2c3d4-...');

The user completes the reset with client.resetPasswordWithCode() — a top-level method, not under admin.*, since the user calls it directly and it requires no admin auth:

// On the page the reset link/email points to:
await auth.resetPasswordWithCode('user@example.com', '123456', 'NewSecurePass123!');
warning

resetPasswordWithCode() only completes an admin-initiated reset (it posts to /auth/reset-password/confirm). A self-service "forgot password" code — from auth.forgotPassword() — is completed with auth.confirmForgotPassword() instead; the two codes are not interchangeable. See Confirm password reset above.

Session management​

// Get all sessions
const { sessions } = await auth.admin.getUserSessions('a1b2c3d4-...');

// Logout all sessions + revoke trusted devices
const { revokedCount } = await auth.admin.logoutAllSessions('a1b2c3d4-...', true);

MFA management​

// Check MFA status
const status = await auth.admin.getMfaStatus('a1b2c3d4-...');
console.log(status.enabled, status.configuredMethods);

// List MFA devices
const { devices } = await auth.admin.getMfaDevices('a1b2c3d4-...');

// Remove a device
await auth.admin.removeMfaDeviceById(123);

// Set preferred device
await auth.admin.setPreferredMfaDevice('a1b2c3d4-...', 123);

// Grant MFA exemption
await auth.admin.setMfaExemption('a1b2c3d4-...', true, 'Service account');

// Revoke MFA exemption
await auth.admin.setMfaExemption('a1b2c3d4-...', false);

Audit history​

const history = await auth.admin.getAuditHistory({
sub: 'a1b2c3d4-...',
page: 1,
limit: 50,
eventType: 'LOGIN_SUCCESS',
});

Error Codes​

Common errors returned by admin endpoints:

Error CodeDescriptionResolution
NOT_FOUNDUser not foundCheck the sub UUID
EMAIL_EXISTSEmail already registeredUse a different email
USERNAME_EXISTSUsername already takenUse a different username
PHONE_EXISTSPhone already registeredUse a different phone number
WEAK_PASSWORDPassword doesn't meet policyUse a stronger password
SOCIAL_ACCOUNT_EXISTSProvider + providerId already linkedThis social account is already imported
VALIDATION_FAILEDInvalid request formatCheck required fields and formats

What's Next​

  • Basic Auth Flows — Core authentication (signup, login, challenges)
  • MFA — Multi-factor authentication setup
  • Audit Logs — Query user activity history and display login events
  • Lifecycle Hooks — Hook into auth events for custom logic (e.g., send welcome email after admin signup)
  • Challenge System — How FORCE_CHANGE_PASSWORD and other challenges work