How Logto's Verification Code System Works: Architecture and Implementation
Logto's verification code system uses a multi-layered pipeline that generates cryptographically secure 6-digit passcodes, enforces rate limits via Sentinel, and validates codes against configurable tenant policies with automatic expiration and retry limits.
Logto's open-source identity platform provides a robust verification code system for secure email and SMS authentication. According to the logto-io/logto source code, this system spans the Management API, a dedicated passcode library, PostgreSQL persistence, and messaging connectors. The architecture follows a strict lifecycle: request generation, secure delivery, and policy-enforced verification.
The Verification Code Lifecycle
The verification code system operates through four distinct phases implemented across multiple layers of the Logto architecture.
Requesting a Verification Code
The Management API exposes the endpoint POST /verification-codes in packages/core/src/routes/verification-code.ts. Incoming requests undergo strict validation via requestVerificationCodePayloadGuard, which ensures the payload contains valid email or phone identifiers along with template specifications.
Before processing, the route invokes withMessageRateGuard to check the recipient against Sentinel activity logs under VerificationCodeSend. This rate-limiting mechanism prevents abuse by restricting how frequently codes can be dispatched to a single destination.
Generating and Storing Passcodes
Code generation occurs in the passcode library at packages/core/src/libraries/passcode.ts. The createPasscode function first executes a cleanup operation, removing any existing unconsumed passcodes associated with the same interaction (jti) or identifier to ensure only one active code exists per session.
Randomization uses customAlphabet('1234567890', 6) to produce cryptographically secure 6-digit numeric strings. The system persists these through insertPasscode in packages/core/src/queries/passcode.ts, which executes an INSERT … RETURNING * SQL statement to retrieve the stored record.
Sending the Code
The sendPasscode function resolves the appropriate messaging connector based on the identifier type—SMS or Email. Located in the same passcode library, this method utilizes the connector's sendMessage implementation to deliver the code.
Logto enriches message templates via buildVerificationCodeContext, which gathers tenant-specific data including application details, organization context, and user information. This context supports dynamic template variables defined in packages/core/src/libraries/passcode.ts lines 176-221.
Verifying and Consuming Codes
Verification occurs at POST /verification-codes/verify, validated by verifyVerificationCodePayloadGuard. The verifyPasscode function queries unconsumed records by jti or identifier and enforces tenant-level verification code policies stored in the sign-in experience configuration.
Default policies defined in packages/schemas/src/consts/verification-code.ts specify a 10-minute expiration window and 10 maximum retry attempts. Failed verifications throw specific RequestError instances such as verification_code.expired or verification_code.code_mismatch. Upon successful validation, consumePasscode marks the record as used to prevent replay attacks.
Security Mechanisms
Logto implements defense-in-depth for verification code security through multiple layers.
Rate Limiting and Sentinel Integration
The withMessageRateGuard middleware in the verification code routes integrates with Logto's Sentinel system to track send frequency. This prevents brute-force attacks by enforcing time-based restrictions on verification code dispatch operations.
Tenant-Level Policy Configuration
Administrators customize verification behavior through the sign-in experience settings. The system queries queries.signInExperiences.findDefaultSignInExperience() to retrieve tenant-specific overrides for expiration duration and retry limits, falling back to defaultVerificationCodePolicy constants when custom values are unspecified.
Implementation Examples
Request a Verification Code via Management API
POST /api/verification-codes
Content-Type: application/json
{
"email": "alice@example.com",
"templateId": "login"
}
This endpoint creates a passcode, stores it in the database, and triggers delivery to the specified email address.
Verify the Received Code
POST /api/verification-codes/verify
Content-Type: application/json
{
"email": "alice@example.com",
"verificationCode": "123456",
"templateId": "login"
}
Successful verification returns 204 No Content. The passcode is immediately consumed and cannot be reused.
Using the Passcode Library Programmatically
import { createPasscodeLibrary } from '@logto/core';
import queries from './queries';
import connectorLibrary from './connector-library';
const { createPasscode, sendPasscode, verifyPasscode } = createPasscodeLibrary(
queries,
connectorLibrary
);
// Generate and send
const passcode = await createPasscode(undefined, TemplateType.Generic, { email: 'bob@example.com' });
await sendPasscode(passcode);
// Later, verify user input
await verifyPasscode(undefined, TemplateType.Generic, '987654', { email: 'bob@example.com' });
Summary
- Logto's verification code system spans API routes, a dedicated passcode library, and PostgreSQL persistence to manage secure one-time codes.
- Passcode generation uses cryptographically secure 6-digit randomization via
createPasscodeinpackages/core/src/libraries/passcode.ts. - Rate limiting via Sentinel's
withMessageRateGuardprevents abuse during the request phase. - Policy enforcement applies default 10-minute expiration and 10-attempt limits, configurable per tenant through sign-in experience settings.
- Verification queries unconsumed codes by interaction ID or identifier, with
consumePasscodeensuring single-use semantics.
Frequently Asked Questions
How long do Logto verification codes remain valid?
By default, verification codes expire after 10 minutes from creation. This duration is defined in packages/schemas/src/consts/verification-code.ts within the defaultVerificationCodePolicy constant. Administrators can override this default through tenant-specific sign-in experience configurations.
What happens if a user enters the wrong verification code multiple times?
Logto enforces a maximum retry limit of 10 attempts per verification code. After exceeding this threshold, the verifyPasscode function in packages/core/src/libraries/passcode.ts throws a RequestError with code verification_code.code_mismatch or similar, rendering the current code invalid and requiring a new code request.
Can I customize the verification code expiration time?
Yes. While the system defaults to 10 minutes, you can configure tenant-level verification code policies through the sign-in experience settings. The system retrieves these custom values via queries.signInExperiences.findDefaultSignInExperience() and applies them during the verifyPasscode execution.
How does Logto prevent brute-force attacks on verification codes?
The system implements two-layer protection: first, the withMessageRateGuard middleware restricts how frequently codes can be sent to a single email or phone number through Sentinel integration. Second, the verification phase enforces strict retry limits and automatic expiration, making automated brute-force attacks computationally infeasible within the valid timeframe.
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 →