How to Manage Logto Users: Complete API and Database Guide
To manage Logto users, interact with the /api/me endpoints authenticated via JWT tokens, which route through packages/core/src/routes-me/user.ts to perform operations like profile updates, password changes, and custom data management while enforcing security policies through the user library in packages/core/src/libraries/user.ts.
Logto provides a comprehensive user management system built on PostgreSQL that handles everything from profile updates to password policies. As implemented in the logto-io/logto repository, user data resides in a dedicated users table defined in packages/schemas/tables/users.sql, with all operations funneled through structured libraries and REST routes that enforce identifier uniqueness and security constraints.
Understanding the Logto User Architecture
Logto organizes user management into four distinct layers that separate concerns between API exposure, business logic, utilities, and data validation. Each layer handles specific responsibilities to ensure secure and consistent user operations.
Database Schema and Storage
User records are persisted in the users table, defined in packages/schemas/tables/users.sql. This schema stores standard fields like username, primaryEmail, primaryPhone, and encrypted password hashes, alongside metadata such as isSuspended and custom data JSON blobs.
Core Architectural Components
The platform implements a layered architecture with the following key components:
- API Routes (
packages/core/src/routes-me/user.ts): Exposes public/meendpoints for the currently authenticated user, handling GET, PATCH, and password actions. - User Library (
packages/core/src/libraries/user.ts): Central business logic for creating users, checking identifier collisions, retrieving roles, and verifying passwords. - Utilities (
packages/core/src/utils/user.ts): Helper functions for password hashing viaencryptUserPassword, MFA conversion, and data sanitization. - Schemas (
packages/core/src/routes-me/user.tsguards anduser.openapi.json): Type-safe definitions of user fields and validation guards using Zod.
Authentication and Request Flow
Every user operation follows a strict five-step pipeline to ensure security and data integrity.
JWT Extraction and Guard Validation
The router extracts the userId from the JWT via ctx.auth.id. Requests then pass through koaGuard middleware, which validates bodies against Zod schemas using regex patterns like usernameRegEx and emailRegEx defined in the route guards.
Business Logic and Policy Enforcement
Before database operations execute, the system performs three critical checks:
- Suspended Account Check:
assertThat(!user.isSuspended, …)blocks operations on suspended accounts. - Identifier Collision:
checkIdentifierCollisionprevents duplicate usernames, emails, phone numbers, or external identities. - Password Policy:
PasswordPolicyChecker(viacheckPasswordPolicyForUser) enforces configured password rules against new passwords.
All operations execute inside PostgreSQL transactions when needed—for example, during role assignment on user creation—to guarantee consistency.
Managing User Profiles via REST API
The /api/me endpoints provide self-service user management capabilities for the currently authenticated user.
Retrieve Current User Profile
To fetch the current user's profile, send an authenticated GET request to /api/me:
const response = await fetch('/api/me', {
headers: { Authorization: `Bearer ${accessToken}` },
});
const profile = await response.json();
// Returns: id, username, primaryEmail, name, avatar, hasPassword, ...
The implementation in packages/core/src/routes-me/user.ts returns a trimmed view using pick(user, ...userInfoSelectFields) to expose only safe fields.
Update User Information
Update fields like username, email, name, or avatar via PATCH requests. Note that username updates are available in OSS only, while primary email updates are Cloud only:
await fetch('/api/me', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
username: 'new-name', // OSS only
primaryEmail: 'new@example.com', // Cloud only
name: 'Jane Doe',
avatar: 'https://example.com/avatar.png',
}),
});
The route validates the payload using router.patch('/', koaGuard({ body: … }).partial()), performs collision checks via checkIdentifierCollision, and delegates to updateUserById for the database transaction.
Handling Custom User Data
Custom data stores arbitrary JSON blobs associated with the user account. Retrieve custom data with a GET request:
const { customData } = await (await fetch('/api/me/custom-data', {
headers: { Authorization: `Bearer ${accessToken}` },
})).json();
Update custom data using a PATCH request:
await fetch('/api/me/custom-data', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
},
body: JSON.stringify({ theme: 'dark', language: 'en' }),
});
Both endpoints guard against suspended accounts and use updateUserById to persist the JSON blob.
Secure Password Operations
Password management follows strict verification flows to prevent unauthorized changes.
Verify User Password
Before sensitive actions, verify the current password against the stored hash:
await fetch('/api/me/password/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
},
body: JSON.stringify({ password: 'current-pwd' }),
});
This calls verifyUserPassword which delegates to userPasswordVerification.isPasswordValid and updates the verification status accordingly.
Change Password
To set a new password, send a POST request with the new value:
await fetch('/api/me/password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
},
body: JSON.stringify({ password: 'NewStrong!123' }),
});
The implementation validates the new password against the active policy using PasswordPolicyChecker, then encrypts and stores it via buildUserPasswordPayloadFromPassword and updateUserById.
Summary
- Logto stores user data in the
userstable defined inpackages/schemas/tables/users.sql. - All user operations route through
packages/core/src/routes-me/user.tsand delegate topackages/core/src/libraries/user.ts. - Authentication relies on JWT tokens with
userIdextracted fromctx.auth.id. - Security checks include suspended account validation, identifier collision detection via
checkIdentifierCollision, and password policy enforcement. - The
/api/meendpoints support retrieving profiles, updating identifiers, managing custom JSON data, and secure password operations usingverifyUserPasswordandencryptUserPassword.
Frequently Asked Questions
What database table stores Logto user information?
User data resides in the users table, defined in packages/schemas/tables/users.sql. This table includes fields for identifiers (username, email, phone), encrypted passwords, suspension status, and custom data JSON blobs.
How does Logto prevent duplicate usernames or emails?
The system uses the checkIdentifierCollision function in packages/core/src/libraries/user.ts to verify uniqueness before creating or updating users. This check prevents duplicate usernames, emails, phone numbers, or external identities across the tenant.
Where is the password policy enforcement implemented?
Password policies are enforced via PasswordPolicyChecker in the user library. When changing passwords through /api/me/password, the system validates the new password against configured rules before encryption via buildUserPasswordPayloadFromPassword and storage via updateUserById.
Can I extend user data with custom fields?
Yes. Logto supports custom user data through the /api/me/custom-data endpoints. Store arbitrary JSON blobs using PATCH requests, and retrieve them via GET requests. Both operations validate that the account is not suspended before updating the database through updateUserById.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →