How Plane's Authentication System Works: Backend and Frontend Implementation
Plane's authentication system combines Django backend middleware for API key validation and CSRF protection with a React/Next.js frontend service that manages session-based login, magic links, OAuth, and password resets.
Plane (makeplane/plane) is an open-source project management platform built with a layered authentication architecture spanning both backend and frontend components. The system supports multiple authentication methods—traditional email/password, magic links, OAuth providers, and API keys—while centralizing error handling and security tokens through specific middleware and service layers.
Architecture Overview
The authentication system operates across five distinct layers, each with specific responsibilities:
| Layer | Responsibility | Core Implementation |
|---|---|---|
| API Authentication | Validates API-key tokens, issues CSRF tokens, handles password-reset, magic-link and sign-out flows | APIKeyAuthentication middleware in apps/api/plane/app/middleware/api_authentication.py |
| Auth Service | Wraps HTTP calls to backend endpoints, provides helpers for CSRF, email verification, password reset, and sign-out | AuthService class in packages/services/src/auth/auth.service.ts |
| Authentication UI | Renders login, sign-up, magic-link, OAuth and password-reset screens; maps backend error codes to messages | Helper enums and authErrorHandler in apps/web/helpers/authentication.helper.tsx |
| OAuth Integration | Optional third-party login (Google, GitHub, GitLab) when configured | UI components in apps/admin/components/authentication/authentication-method-card.tsx |
| Session Management | Maintains user state via Django session cookies or API keys for external clients | Django's authentication middleware and explicit APIKeyAuthentication |
API Authentication Layer
The backend authentication layer in Plane handles credential validation through Django middleware and dedicated endpoints.
API Key Validation
The APIKeyAuthentication middleware processes the X-Api-Key header on incoming requests. When an external service sends a request with this header, the middleware looks up the corresponding APIToken object, checks expiration and activity status, updates the last_used timestamp, and returns the associated user.
# Conceptual flow based on apps/api/plane/app/middleware/api_authentication.py
class APIKeyAuthentication:
def authenticate(self, request):
api_key = request.headers.get("X-Api-Key")
token = APIToken.objects.get(token=api_key)
if token.is_active and not token.is_expired:
token.update_last_used()
return token.user
CSRF Token Management
Before any state-changing POST request (sign-in, password reset, magic link generation), the frontend must obtain a CSRF token from the /auth/get-csrf-token/ endpoint. This token must be included as the X-CSRFTOKEN header in subsequent requests to prevent cross-site request forgery attacks.
Frontend Authentication Service
The frontend layer abstracts all authentication logic into a centralized service layer that React components consume.
AuthService Implementation
The AuthService class extends APIService and implements methods for all authentication flows. Located in packages/services/src/auth/auth.service.ts, this service handles:
- CSRF Retrieval:
requestCSRFToken()fetches tokens from/auth/get-csrf-token/ - Email Verification:
emailCheck()validates addresses via/auth/email-check/ - Magic Link Generation:
generateUniqueCode()requests short-lived codes from/auth/magic-generate/ - Password Reset: Methods for initiating and completing password reset flows
- Sign-Out:
signOut(baseUrl)constructs and submits a sign-out form to/auth/sign-out/
// Example: Requesting CSRF token and signing in
import { AuthService } from "@plane/services/auth";
import { authErrorHandler, EAuthenticationErrorCodes } from "./authentication.helper";
const auth = new AuthService();
async function signIn(email: string, password: string) {
// Retrieve CSRF token first
const { csrf_token } = await auth.requestCSRFToken();
try {
await auth.post("/auth/login/", { email, password }, {
headers: { "X-CSRFTOKEN": csrf_token },
});
// Django sets session cookie automatically; redirect to dashboard
} catch (err: any) {
const errorInfo = authErrorHandler(
err?.response?.data?.code as EAuthenticationErrorCodes,
email
);
if (errorInfo) {
showBanner(errorInfo.title, errorInfo.message);
}
}
}
Error Handling and UI Mapping
Authentication errors are encoded as numeric strings defined in the EAuthenticationErrorCodes enum. The authErrorHandler function in apps/web/helpers/authentication.helper.tsx maps these backend error codes to user-friendly banner messages, ensuring consistent error presentation across the application.
Authentication Flows
Email and Password Authentication
The standard login flow begins with the frontend rendering EAuthModes.SIGN_IN from the authentication helper enums. The user submits credentials, which the frontend sends to /auth/login/ with the CSRF token. Upon successful validation, Django creates a session cookie that subsequent requests include automatically.
Magic Link Login
For passwordless authentication, the flow uses AuthService.generateUniqueCode() to request a short-lived code from /auth/magic-generate/. The backend sends this code via email. When the user enters the code, the backend validates it and issues a session cookie, completing the authentication without requiring a password.
OAuth Integration
When users select Google, GitHub, or GitLab authentication, the frontend redirects to the provider's OAuth endpoint. After authorization, the backend callback creates or updates the user record and issues a session cookie. The OAuth UI components reside in apps/admin/components/authentication/authentication-method-card.tsx.
API Key Access
External services and API-only clients bypass session cookies by including the X-Api-Key header in requests. The APIKeyAuthentication middleware validates these tokens against the APIToken model, enabling programmatic access to Plane's API without interactive login flows.
// Example: Validating an API key in a custom client
import axios from "axios";
async function fetchProjects(apiKey: string) {
const response = await axios.get("/api/projects/", {
baseURL: "https://app.plane.so",
headers: { "X-Api-Key": apiKey },
});
return response.data;
}
Sign-Out Process
The sign-out flow requires special handling to ensure CSRF protection. The AuthService.signOut() method fetches a fresh CSRF token, constructs a hidden HTML form that posts to /auth/sign-out/, and programmatically submits it. This approach ensures Django properly invalidates the session cookie and clears the authentication state.
Summary
- Plane's authentication system spans Django backend middleware and React/Next.js frontend services to support multiple login methods.
- API keys are validated via the
APIKeyAuthenticationmiddleware inapps/api/plane/app/middleware/api_authentication.py, which checks token validity and updates usage timestamps. - CSRF protection requires tokens obtained from
/auth/get-csrf-token/before any POST request, handled byAuthService.requestCSRFToken(). - Error handling is centralized through
EAuthenticationErrorCodesenums and theauthErrorHandlerfunction inapps/web/helpers/authentication.helper.tsx. - Authentication methods include traditional email/password, magic links, OAuth providers, and API keys, with session cookies for web clients and header-based tokens for API access.
Frequently Asked Questions
How does Plane handle CSRF protection?
Plane requires a CSRF token for all state-changing POST requests. The frontend calls AuthService.requestCSRFToken() to retrieve a token from the /auth/get-csrf-token/ endpoint, then includes it as the X-CSRFTOKEN header in subsequent requests. This prevents cross-site request forgery attacks while maintaining session security.
What is the difference between session-based and API key authentication in Plane?
Session-based authentication uses Django's built-in session cookies set after login via email/password, magic links, or OAuth, automatically included with each request. API key authentication uses the X-Api-Key header validated by the APIKeyAuthentication middleware, designed for external services and programmatic access without interactive login flows.
How does the magic link authentication flow work?
The frontend calls AuthService.generateUniqueCode() which requests a short-lived code from the /auth/magic-generate/ endpoint. The backend emails this code to the user, who enters it in the UI. Upon validation, the backend issues a session cookie, completing authentication without requiring a password.
Where are authentication errors handled in the Plane frontend?
Authentication errors are handled in apps/web/helpers/authentication.helper.tsx, which defines the EAuthenticationErrorCodes enum and the authErrorHandler function. This utility maps backend error codes to user-friendly messages, ensuring consistent error display across login, sign-up, password reset, and magic link flows.
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 →