# Logto Security Best Practices: Enterprise-Grade Hardening in the Source Code

> Discover Logto security best practices for enterprise-grade hardening. Learn about defense-in-depth, HTTP headers, MFA, token management, and rate limiting for robust application security.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: best-practices
- Published: 2026-06-30

---

**Logto implements defense-in-depth security through Helmet-based HTTP headers, automatic secret rotation, configurable MFA policies, scoped Personal Access Tokens, and built-in rate limiting, all enforced at the middleware and database layers.**

Logto is an open-source identity and access management platform that embeds enterprise security controls directly into its architecture. Understanding Logto security best practices requires examining how the codebase implements OWASP guidelines, secret management, and tenant isolation. This analysis explores the concrete implementations in the `logto-io/logto` repository that protect authentication flows and sensitive data.

## HTTP Security Headers and Content Security Policy

Logto uses **Helmet** middleware to enforce OWASP-recommended security headers across all applications (core, console, and experience).

### Centralized Header Configuration

The [`koa-security-headers.ts`](https://github.com/logto-io/logto/blob/main/koa-security-headers.ts) middleware in [`packages/core/src/middleware/koa-security-headers.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/middleware/koa-security-headers.ts) defines baseline security settings through the `basicSecurityHeaderSettings` object. This configuration enforces **Strict-Transport-Security (HSTS)** with long max-age values and enables **Cross-Origin Opener Policy (COOP)** and **Cross-Origin Embedder Policy (COEP)** protections.

### Dynamic CSP Per Tenant

Logto tailors Content-Security-Policy directives per application context. The implementation defines specific source arrays including `experienceScriptSource` for trusted JavaScript origins, `experienceConnectSource` for API endpoints and CDNs, and `avatarCropImageSources` for image upload functionality. Production guards ensure development-only relaxations—such as Google One-Tap iframe support—never reach production builds.

### Extending CSP for Custom Scripts

When organizations need to whitelist additional script sources, they can leverage the internal `createSecurityHeaderSettings` factory:

```typescript
// src/tenant/custom-csp.ts
import { getTenantId } from '@logto/core';
import { createSecurityHeaderSettings } from '@logto/core/src/middleware/koa-security-headers';

export const getCustomHeader = (tenantId: string) => {
  const { getExperienceSecurityHeaderSettings } =
    createSecurityHeaderSettings(tenantId);

  // Append a trusted script host for this tenant only
  const customCsp = {
    scriptSrc: ['https://trusted.example.com/'],
  };

  return getExperienceSecurityHeaderSettings(customCsp);
};

```

This approach merges custom sources with secure defaults while respecting the `isProduction` environment checks defined in lines 15-22 of the middleware file.

## Secret Management and Cryptographic Key Rotation

Logto never hard-codes secrets. Instead, it persists encrypted configuration in the database and implements automated rotation mechanisms.

### OIDC Cookie Key Storage

Cookie signing keys are stored in the `logto_config` table under the `oidc.cookieKeys` key, as defined in [`packages/schemas/src/types/logto-config/index.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/logto-config/index.ts). The system maintains multiple valid keys simultaneously to ensure zero-downtime rotation.

### Automated Rotation via CLI

Administrators rotate keys using the built-in CLI:

```bash

# Rotate cookie keys – generates a fresh key, keeps the previous one for graceful rollout

pnpm cli config rotate-cookie-keys

```

This command executes migration logic similar to [`1.0.0_rc.1-1676190092-migrate-admin-data.ts`](https://github.com/logto-io/logto/blob/main/1.0.0_rc.1-1676190092-migrate-admin-data.ts), which safely inserts new keys while preserving existing sessions.

### Configuration Library

The `LogtoConfigLibrary` in [`packages/core/src/libraries/logto-config.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/logto-config.ts) handles encrypted reads and writes for sensitive values including JWT signing keys and OIDC private keys, ensuring secrets never exist in environment variables or code repositories.

## Multi-Factor Authentication Implementation

Logto supports TOTP, WebAuthn, and backup codes through a flexible policy engine embedded in the sign-in experience schema.

### MFA Policy Configuration

The JSONB schema in [`packages/schemas/src/foundations/jsonb-types/sign-in-experience.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/foundations/jsonb-types/sign-in-experience.ts) (lines 190-210) defines three policy levels: **Mandatory** requires MFA on every authentication, **Optional** allows user-configured MFA, and **Adaptive** enables risk-based step-up authentication.

### Enforcing Mandatory MFA

Administrators configure tenant-wide MFA requirements via the Admin API:

```typescript
await fetch(`${tenantEndpoint}/api/tenants/${tenantId}/sign-in-experience`, {
  method: 'PATCH',
  headers: { Authorization: `Bearer ${adminToken}` },
  body: JSON.stringify({
    mfa: {
      // Force MFA on every sign‑in
      policy: 'mandatory',
      factors: ['totp', 'webauthn']
    },
  }),
});

```

The `require_mfa_verification` check in the authentication flow validates these policies before issuing tokens.

## Personal Access Tokens and Scoped Authorization

Logto implements scoped API access through Personal Access Tokens (PATs) with strict isolation guarantees.

### Token Architecture

PATs are stored in the `personal_access_tokens` table with **Row-Level Security (RLS)** policies that isolate tenant data. The token type is explicitly declared as `urn:logto:token-type:personal_access_token` in [`packages/core/src/oidc/grants/token-exchange/types.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/grants/token-exchange/types.ts).

### Programmatic Token Creation

```typescript
import { createPersonalAccessToken } from '@logto/core/src/queries/personal-access-token';

const pat = await createPersonalAccessToken({
  name: 'CI‑pipeline',
  expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
  scopes: ['read:users', 'write:applications']
});
console.log('PAT value (store securely):', pat.value);

```

The database schema and RLS setup are defined in [`packages/schemas/alterations/1.20.0-1723448981-personal-access-tokens.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/alterations/1.20.0-1723448981-personal-access-tokens.ts).

## Rate Limiting and Brute-Force Protection

The **Sentinel** middleware protects authentication endpoints from abuse through IP and device-based throttling.

### Message Rate Guarding

The [`message-rate-guard.ts`](https://github.com/logto-io/logto/blob/main/message-rate-guard.ts) file in `packages/core/src/sentinel/` implements limits on login attempts, blocking abusive patterns before they reach the authentication logic. This prevents credential stuffing and brute-force attacks against the OIDC endpoints.

## Summary

- **HTTP Security**: Helmet-based headers with tenant-specific CSP, HSTS, and COOP/COEP protections enforced in [`koa-security-headers.ts`](https://github.com/logto-io/logto/blob/main/koa-security-headers.ts)
- **Secret Rotation**: Automated cookie key rotation via CLI and encrypted storage in `logto_config` table accessed through `LogtoConfigLibrary`
- **MFA Policies**: Flexible multi-factor configuration through `sign-in-experience` JSONB schema supporting mandatory, optional, and adaptive modes
- **Token Security**: Scoped PATs with RLS isolation and explicit token type declarations (`urn:logto:token-type:personal_access_token`)
- **Attack Prevention**: Built-in rate limiting through Sentinel middleware protecting against brute-force attempts

## Frequently Asked Questions

### How does Logto handle Content Security Policy violations?

Logto implements CSP through the [`koa-security-headers.ts`](https://github.com/logto-io/logto/blob/main/koa-security-headers.ts) middleware, which configures Helmet with strict directives for `script-src`, `style-src`, and `connect-src`. The system dynamically adjusts allowed sources based on tenant configuration while maintaining production guards that prevent unsafe development settings from deploying to production environments.

### What is the recommended approach for rotating OIDC signing keys?

Use the CLI command `pnpm cli config rotate-cookie-keys` to generate new keys while maintaining backward compatibility. This updates the `oidc.cookieKeys` array in the `logto_config` table, allowing existing sessions to remain valid while new sessions use the fresh key. The rotation logic follows the migration pattern established in [`1.0.0_rc.1-1676190092-migrate-admin-data.ts`](https://github.com/logto-io/logto/blob/main/1.0.0_rc.1-1676190092-migrate-admin-data.ts).

### How does Logto protect against brute-force authentication attacks?

Logto employs the Sentinel middleware ([`message-rate-guard.ts`](https://github.com/logto-io/logto/blob/main/message-rate-guard.ts)) to limit login attempts per IP address and device fingerprint. This rate-limiting layer intercepts suspicious traffic patterns before they reach the OIDC grant handlers, effectively mitigating credential stuffing and automated brute-force attempts.

### Are Personal Access Tokens isolated between tenants?

Yes. PATs implement Row-Level Security (RLS) policies at the database level as defined in [`1.20.0-1723448981-personal-access-tokens.ts`](https://github.com/logto-io/logto/blob/main/1.20.0-1723448981-personal-access-tokens.ts), ensuring tokens created in one tenant cannot access resources in another. The tokens use the specific type URI `urn:logto:token-type:personal_access_token` and store only hashed identifiers in the database, with the full value displayed only once during creation.