Best Practices for Securing APIs in Twenty CRM: NestJS Guard Architecture Explained

Twenty CRM secures its GraphQL and REST APIs using a layered NestJS architecture that combines JWT session tokens, API key validation, OAuth 2.0 SSO, and workspace-level RBAC enforced through centralized authentication context and specialized route guards.

Twenty CRM implements enterprise-grade API security within its core-modules/auth package, utilizing NestJS guards and middleware to protect all public endpoints. Understanding the best practices for securing APIs in Twenty CRM requires examining how the repository combines centralized authentication context with multiple validation strategies to enforce multi-tenant isolation and role-based permissions.

Establish a Centralized Authentication Context

Every incoming request passes through workspace-auth-context.middleware.ts to establish a security context before reaching business logic. This middleware extracts tokens, API keys, or SSO payloads from headers, then invokes auth.service.ts to build a structured auth context describing exactly who is making the call—whether a user, workspace, application, or API key.

The middleware stores this context in workspace-auth-context.storage.ts, an in-memory store that propagates authentication data through the entire request lifecycle. By centralizing auth data in this manner, downstream guards and resolvers can enforce permissions consistently without re-parsing tokens or querying databases repeatedly.

Enforce Endpoint Protection with Specialized Guards

Twenty CRM implements distinct NestJS guards for different authentication scenarios, each residing in packages/twenty-server/src/engine/core-modules/auth/guards/.

JWT Session Authentication

The jwt-auth.guard.ts validates the Authorization: Bearer <jwt> header on every protected request. It delegates signature verification and expiration checks to jwt.auth.strategy.ts. Upon successful validation, the guard injects a user-auth context containing the user ID, workspace IDs, and role assignments into the request object.

import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '@twenty/server/src/engine/core-modules/auth/guards/jwt-auth.guard';
import { AuthUser } from '@twenty/server/src/engine/core-modules/auth/decorators/auth-user.decorator';

@Controller('api/contacts')
export class ContactsController {
  @Get()
  @UseGuards(JwtAuthGuard)               // ← Enforces a valid JWT
  async list(@AuthUser() user) {
    // `user` contains { userId, workspaceIds, roles, permissions }
    return this.contactService.findAllForWorkspace(user.workspaceId);
  }
}

API Key Validation for Service-to-Service Communication

For programmatic access without user sessions, Twenty CRM uses the combination of auth-api-key.decorator.ts and is-api-key-auth-context.guard.ts. This guard validates static or rotating API keys passed via the x-api-key header, enabling secure machine-to-machine authentication for internal services and third-party integrations.

import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';

@Injectable()
export class IntegrationClient {
  constructor(private http: HttpService) {}

  async fetchData() {
    const response = await this.http
      .get('https://api.twentyhq.com/v1/external', {
        headers: { 'x-api-key': process.env.TWENTY_INTERNAL_API_KEY },
      })
      .toPromise();

    return response.data;
  }
}

The receiving endpoint must apply @UseGuards(ApiKeyAuthGuard) to ensure the key is valid and possesses the proper scope for the requested resource.

OAuth 2.0 and SSO Integration

Twenty CRM supports enterprise single sign-on through dedicated guards that delegate to Passport strategies:

Each strategy handles the complete OAuth handshake, validates ID tokens, and provisions users automatically while issuing short-lived JWTs for session management.

import { useMutation } from '@apollo/client';
import { authorizeApp } from '@/modules/auth/graphql/mutations/authorizeApp';
import { GoogleLoginButton } from '@/components/GoogleLoginButton';

export const GoogleSSO = () => {
  const [login] = useMutation(authorizeApp);

  const handleGoogleSuccess = async (googleUser) => {
    const idToken = googleUser.getAuthResponse().id_token;
    await login({ variables: { provider: 'google', token: idToken } });
  };

  return <GoogleLoginButton onSuccess={handleGoogleSuccess} />;
};

The authorizeApp mutation forwards the Google ID token to the server, where GoogleAuthStrategy validates the signature against Google's public keys before establishing the session.

Workspace-Level Tenant Isolation

The workspace-auth.guard.ts provides multi-tenant isolation by verifying that the authenticated user or API key belongs to the workspace specified in the request. This prevents cross-workspace data leakage by enforcing strict boundary checks before any data access occurs.

Implement Role-Based Access Control (RBAC)

Permission enforcement occurs inside GraphQL resolvers and service methods using the @AuthUser() decorator defined in auth-user.decorator.ts. This decorator extracts the enriched auth context from the request, allowing fine-grained permission checks against workspace-level roles stored in the database.

@Resolver(() => Contact)
export class ContactResolver {
  @Query(() => [Contact])
  async contacts(@AuthUser() user: AuthUserContext) {
    // Enforce workspace-level permission
    if (!user.hasPermission('contact.read')) {
      throw new ForbiddenException('Insufficient permissions');
    }
    // Proceed with business logic …
  }
}

Permissions are cached per request to minimize database queries while ensuring consistent authorization decisions across the API surface.

Secure Token Lifecycle Management

Twenty CRM implements stateful token management for enhanced security:

  • Refresh tokens are encrypted at rest in the database and rotated immediately upon use, preventing token replay attacks.
  • Access tokens remain short-lived (JWTs) with signatures verified on every request via jwt.auth.strategy.ts.
  • CSRF protection requires the X-CSRF-Token header for all non-GET operations initiated from the web UI, mitigating cross-site request forgery.

Monitor and Rate-Limit Authentication Endpoints

The security layer logs all authentication events—successful logins, token refreshes, and failed attempts—to Redis for real-time analysis and optional forwarding to external audit services. Rate-limiting middleware specifically caps request rates on /auth/login and /auth/token endpoints to neutralize brute-force attacks against user credentials.

The repository validates these security mechanisms through comprehensive testing suites, including jwt.auth.strategy.spec.ts for unit testing token validation and oauth.integration-spec.ts for end-to-end OAuth flow verification.

Summary

  • Centralize authentication context using workspace-auth-context.middleware.ts to ensure consistent identity propagation across all request handlers.
  • Apply specialized guards (JwtAuthGuard, ApiKeyAuthGuard, WorkspaceAuthGuard) to enforce the appropriate authentication method for each endpoint type.
  • Implement RBAC via the @AuthUser() decorator and explicit permission checks within resolvers to prevent privilege escalation.
  • Encrypt and rotate refresh tokens while keeping JWT access tokens short-lived and strictly validated.
  • Enable rate limiting and audit logging on all authentication endpoints to detect and prevent brute-force attacks.

Frequently Asked Questions

How does Twenty CRM prevent cross-workspace data leakage?

Twenty CRM utilizes workspace-auth.guard.ts to enforce multi-tenant isolation at the API boundary. This guard verifies that the authenticated user or API key explicitly belongs to the workspace ID specified in the request path or headers, rejecting any cross-tenant access attempts before they reach business logic.

What authentication methods does Twenty CRM support for API access?

According to the twentyhq/twenty source code, the platform supports four primary authentication methods: JWT session tokens for browser-based users, API keys (x-api-key header) for service-to-service integration, OAuth 2.0 through Google and Microsoft providers, and SAML 2.0 for enterprise identity providers. Each method maps to a specific NestJS guard and Passport strategy combination within the core-modules/auth package.

How are permissions enforced in Twenty CRM GraphQL resolvers?

Permissions are enforced using the @AuthUser() decorator imported from auth-user.decorator.ts. This decorator extracts the auth context populated by the JWT or API key guard, exposing a hasPermission() method that resolvers call to validate workspace-specific entitlements before executing database queries.

Where does Twenty CRM store refresh tokens and how are they secured?

Refresh tokens are stored encrypted in the database via the refresh-tokens-manager services and rotated immediately upon each use. This stateful approach allows Twenty CRM to invalidate compromised tokens instantly while maintaining secure, long-lived sessions for legitimate users.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →