This page documents n8n's authentication and user management system, including JWT-based session management, MFA enforcement, browser ID validation, and SSO integration.
For information about project-based authorization and role-based access control (RBAC), see Project-Based Authorization and Sharing. For user-specific features like credentials and workflows, see Workflows API and Service Layer and Credentials API and Security.
n8n implements a comprehensive authentication and user management system. Authentication is built around JWT tokens stored in HTTP-only cookies, supporting multiple authentication methods (email, LDAP, SAML, OIDC). User management is split across several controllers, each responsible for a distinct surface area.
Controllers:
| Controller | Route prefix | Responsibility |
|---|---|---|
AuthController | (root) | Login, logout, signup token |
MeController | /me | Current user profile, password, settings |
UsersController | /users | Admin user listing, deletion, role change |
InvitationController | /invitations | Sending and accepting invitations |
OwnerController | /owner | First-run owner setup |
The authentication middleware (createAuthMiddleware() in AuthService) validates tokens, enforces MFA requirements, and populates req.user for all authenticated requests.
Sources: packages/cli/src/controllers/auth.controller.ts packages/cli/src/controllers/me.controller.ts packages/cli/src/controllers/users.controller.ts packages/cli/src/controllers/invitation.controller.ts packages/cli/src/controllers/owner.controller.ts
Authentication system ā key code entities and their relationships
Sources: packages/cli/src/auth/auth.service.ts packages/cli/src/controllers/auth.controller.ts packages/cli/src/auth/auth-handler.registry.ts packages/cli/src/auth/handlers/email.auth-handler.ts
JWT tokens contain the following payload:
| Field | Type | Description |
|---|---|---|
id | string | User ID |
hash | string | Hash derived from email and bcrypt password |
browserId | string (optional) | Client-generated unique identifier |
usedMfa | boolean (optional) | Whether MFA was used during authentication |
exp | number | Token expiration timestamp |
Sources: packages/cli/src/auth/auth.service.ts20-34
Sources: packages/cli/src/auth/auth.service.ts96-230 packages/cli/src/services/jwt.service.ts
The createAuthMiddleware() method returns an Express middleware function configured with specific options:
Sources: packages/cli/src/auth/auth.service.ts40-55
Sources: packages/cli/src/auth/auth.service.ts96-148
Browser IDs prevent session hijacking by binding JWT tokens to specific client instances. The client generates a UUID and sends it in the browser-id header.
The following endpoints skip browser ID validation:
| Endpoint Pattern | Reason |
|---|---|
/${rest}/push | WebSocket connections can't send custom headers |
/${rest}/binary-data/ | <embed> tags can't send custom headers |
/${rest}/oauth1-credential/callback | OAuth callbacks aren't from frontend |
/${rest}/oauth2-credential/callback | OAuth callbacks aren't from frontend |
/types/*.json | Type files for node/credential definitions |
/mcp-oauth/authorize/ | MCP OAuth authorization flow |
/${rest}/chat/conversations/:sessionId/messages/:messageId/attachments/:index | Chat hub attachment downloads |
Sources: packages/cli/src/auth/auth.service.ts61-94 packages/cli/src/auth/auth.service.ts252-278
When MFA is enforced but a user hasn't set it up, they can only access:
POST /${rest}/mfa/verifyPOST /${rest}/mfa/enableGET /${rest}/mfa/qrGET /${rest}/loginSources: packages/cli/src/auth/auth.service.ts96-148 packages/cli/src/mfa/mfa.service.ts
Sources: packages/cli/src/auth/auth.service.ts280-333
| Variable | Default | Description |
|---|---|---|
N8N_USER_MANAGEMENT_JWT_SECRET | (auto-generated) | Secret for signing JWT tokens |
N8N_USER_MANAGEMENT_JWT_SESSION_DURATION | 168 | JWT session duration in hours (7 days) |
N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS | 0 | JWT refresh timeout |
N8N_AUTH_COOKIE_SECURE | false | Use secure cookies (HTTPS only) |
N8N_MFA_ENABLED | true | Enable MFA feature |
The authentication configuration is managed through:
GlobalConfig.auth: Cookie settings (secure, samesite)GlobalConfig.userManagement: JWT configuration, email settings, authentication methodMfaConfig: MFA enabled flagSources: packages/@n8n/config/src/configs/auth.config.ts packages/@n8n/config/src/configs/user-management.config.ts packages/@n8n/config/src/configs/mfa.config.ts
The FrontendService provides authentication settings to the frontend, with different visibility for authenticated vs. unauthenticated users:
Sources: packages/cli/src/services/frontend.service.ts37-99 packages/cli/src/services/frontend.service.ts528-562
The system supports multiple authentication methods, controlled by GlobalConfig.userManagement.authenticationMethod:
| Method | Configuration | Requirements |
|---|---|---|
email | Default | User password in database |
ldap | sso.ldap.loginEnabled | LDAP license + configuration |
saml | sso.saml.loginEnabled | SAML license + configuration |
oidc | sso.oidc.loginEnabled | OIDC license + configuration |
For OIDC authentication, additional cookies are used:
OIDC_STATE_COOKIE_NAME: State parameter for OAuth flowOIDC_NONCE_COOKIE_NAME: Nonce for security validationSources: packages/cli/src/constants.ts63-65 packages/cli/src/sso.ee/sso-helpers.ts
The InvalidAuthTokenRepository maintains a list of revoked JWT tokens. When a user logs out or their session is forcibly terminated, the token hash is stored here.
Sources: packages/cli/src/auth/auth.service.ts106-108 packages/@n8n/db/src/repositories/invalid-auth-token.repository.ts
Each CLI command (start, worker, webhook) initializes authentication differently:
Full authentication initialization including:
Limited authentication:
Webhook-specific authentication:
Sources: packages/cli/src/commands/start.ts188-264 packages/cli/src/commands/worker.ts74-126 packages/cli/src/commands/webhook.ts44-87
The E2E controller provides endpoints for test environment setup:
| Role | Slug | Description |
|---|---|---|
| Owner | global:owner | Full system access |
| Admin | global:admin | Administrative access |
| Member | global:member | Standard user access |
| Chat User | global:chatUser | Chat-only access |
Sources: packages/cli/src/controllers/e2e.controller.ts191-382
AuthController handles session lifecycle. It is registered at the Express root (no prefix).
Login endpoint ā POST /login
Rate-limited at two layers: 1000 requests per 5 minutes per IP, and 5 requests per minute keyed by emailOrLdapLoginId. Accepts LoginRequestDto with fields emailOrLdapLoginId, password, mfaCode, mfaRecoveryCode.
Login flow ā AuthController.login() and AuthHandlerRegistry
Other endpoints:
| Method | Path | Auth required | Description |
|---|---|---|---|
GET | /login | Yes (allowSkipMFA: true) | Returns PublicUser for current session |
GET | /resolve-signup-token | No | Validates invite token; blocked when SSO active |
POST | /logout | Yes | Invalidates token, clears cookie |
The GET /login handler calls userService.findUserWithAuthIdentities() to load authIdentities (needed to determine signInType), then calls userService.toPublic() with withScopes: true.
Sources: packages/cli/src/controllers/auth.controller.ts
MeController handles operations for the currently authenticated user (/me).
| Method | Path | Description |
|---|---|---|
PATCH | /me | Update email, firstName, lastName |
PATCH | /me/password | Change password (requires current password + optional MFA code) |
POST | /me/survey | Store personalization survey answers |
PATCH | /me/settings | Update user settings (restricted fields) |
updateCurrentUser() enforces additional checks when the email field changes:
BadRequestError ā SAML users cannot change emailmfaCode in payload; validated via MfaService.validateMfa()currentPassword; compared via PasswordUtility.compare()SSO identity check uses UserService.findSsoIdentity(): if any non-email AuthIdentity exists, profile updates are blocked with a message like "LDAP user may not change their profile information".
PATCH /me/settings uses UserSelfSettingsUpdateRequestDto, which explicitly excludes allowSSOManualLogin and userActivated to prevent privilege escalation. The admin-only counterpart (PATCH /users/:id/settings) uses SettingsUpdateRequestDto and requires the user:update global scope.
Sources: packages/cli/src/controllers/me.controller.ts packages/cli/src/controllers/__tests__/me.controller.test.ts
UsersController handles user administration at /users. All endpoints require a global scope.
| Method | Path | Scope | Description |
|---|---|---|---|
GET | /users | user:list | List users with filter/select/sort/skip/take |
GET | /users/:id/password-reset-link | user:resetPassword | Generate password reset URL |
POST | /users/:id/invite-link | user:generateInviteLink | Generate 90-day JWT invite link |
PATCH | /users/:id/settings | user:update | Update any user's settings |
DELETE | /users/:id | user:delete | Delete user, optionally transferring resources |
PATCH | /users/:id/role | user:changeRole + feat:advancedPermissions | Change global role |
GET /users accepts UsersListFilterDto query params: filter, select, skip, take, sortBy, expand. UserRepository.buildUserQuery() builds the TypeORM query. After fetching, UsersController.removeSupplementaryFields() applies field restrictions based on whether the requesting user holds user:create scope ā users without it only see base fields (id, email, firstName, lastName) for other users; they see full detail for their own record.
filter: { email, firstName, lastName, isOwner, mfaEnabled, fullText, ids, isPending }
select: [ "id", "email", "firstName", "lastName", "role", ... ]
expand: [ "projectRelations" ]
sortBy: [ "role:asc", "firstName:desc", ... ]
DELETE /users/:id accepts optional query param transferId (a project ID). If provided:
WorkflowService.transferAll() and CredentialsService.transferAll(), plus FolderService.transferAllFoldersToProject() ā all inside a single transaction.AuthIdentity, personal Project, and User records are deleted in a transaction.The global owner cannot be deleted. Admins cannot delete the owner.
PATCH /users/:id/role requires feat:advancedPermissions license. UserService.changeUserRole() handles side effects:
global:chatUser: removes project relations, revokes API keys, demotes personal project ownership to project:viewerglobal:chatUser: restores project:personalOwner on personal projectSources: packages/cli/src/controllers/users.controller.ts packages/cli/src/services/user.service.ts packages/cli/test/integration/users.api.test.ts
InvitationController handles user invitation flows at /invitations.
POST /invitationsRequires user:create global scope. Blocked when SSO is active (isSsoCurrentAuthenticationMethod()). Checks user quota via License.isWithinUsersLimit(). Inviting global:admin requires feat:advancedPermissions license.
Delegates to UserService.inviteUsers(), which:
UserRepository.createUserWithProject()) in a transaction for new emailsUserManagementMailer.invite()UserRequest.InviteResponse[] with per-user emailSent and optional inviteAcceptUrlInvite link format depends on a PostHog feature flag (061_tamper_proof_invite_links):
/signup?token=<jwt>) via JwtService.sign() with expiresIn: '90d'/signup?inviterId=<id>&inviteeId=<id>)The inviteLinksEmailOnly config flag (GlobalConfig.userManagement.inviteLinksEmailOnly) controls whether the accept URL is returned in the API response (it is omitted if email was sent successfully, or always omitted if inviteLinksEmailOnly is true).
Two endpoints exist for backward compatibility:
POST /invitations/accept (JWT-based, current): Expects token field in AcceptInvitationRequestDto. Calls UserService.getInvitationIdsFromPayload({ token }) to verify the JWT and extract inviterId/inviteeId.
POST /invitations/:id/accept (legacy): Expects inviterId in body; inviteeId comes from the :id URL param.
Both paths converge on processInvitationAcceptance():
Invitation acceptance flow
Sources: packages/cli/src/controllers/invitation.controller.ts packages/cli/src/services/user.service.ts185-343
OwnerController at /owner handles the first-run setup flow.
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /owner/setup | None (skipAuth: true) | Promotes the owner shell to a full account |
POST | /owner/dismiss-banner | banner:dismiss scope | Dismisses UI banners |
POST /owner/setup accepts OwnerSetupRequestDto (email, firstName, lastName, password). It delegates to OwnershipService.setupOwner(), then issues an auth cookie via AuthService.issueCookie(). Input validation rejects XSS patterns in name fields.
Sources: packages/cli/src/controllers/owner.controller.ts packages/cli/test/integration/owner.api.test.ts
UserService is the primary service for user operations. Key methods:
| Method | Description |
|---|---|
toPublic(user, options) | Strips sensitive fields (password, mfaSecret, mfaRecoveryCodes, updatedAt, authIdentities); optionally adds globalScopes, inviteAcceptUrl, featureFlags |
update(userId, data) | Partial update saved via UserRepository |
updateSettings(userId, newSettings) | Merges settings into user.settings |
inviteUsers(owner, invitations) | Orchestrates user creation + email sending |
changeUserRole(user, newRole) | Changes role with side effects in a transaction |
findUserWithAuthIdentities(userId) | Loads User with role and authIdentities relations |
findSsoIdentity(userId) | Returns non-email AuthIdentity if present |
getInvitationIdsFromPayload(payload) | Parses invite link (JWT token or legacy query params) |
toPublic() options:
withInviteUrl: boolean ā add inviteAcceptUrl (requires inviterId)
inviterId: string ā used to build invite URL
withScopes: boolean ā add globalScopes via getGlobalScopes()
posthog: PostHogClient ā add featureFlags (with 1.5s timeout)
mfaAuthenticated: boolean ā set mfaAuthenticated field
Sources: packages/cli/src/services/user.service.ts packages/cli/src/services/__tests__/user.service.test.ts
JwtService wraps the jsonwebtoken library and is the single source of JWT signing/verification.
The JWT secret is determined at startup in the constructor:
GlobalConfig.userManagement.jwtSecret if setInstanceSettings.encryptionKey: take every other character, then SHA-256 hashThis means the secret is stable across restarts if the encryption key is stable, but changing the encryption key invalidates all existing tokens.
| Method | Description |
|---|---|
sign(payload, options) | Signs with jwtSecret; returns token string |
verify<T>(token, options) | Verifies and returns typed payload |
decode<T>(token) | Decodes without verification |
Sources: packages/cli/src/services/jwt.service.ts
NoXss DecoratorThe @NoXss() decorator (from @n8n/db) is applied to DTO fields that accept user-supplied strings. It rejects values containing HTML tags (<) or URL-like patterns that could be used for XSS or injection.
It is applied in:
PersonalizationSurveyAnswersV4 (survey-answers.dto.ts) ā all free-text fields@n8n/api-types DTOs for user profile fields (firstName, lastName, etc.)Example usage in PersonalizationSurveyAnswersV4:
@NoXss()
@IsString()
automationGoalDevopsOther?: string | null;
Owner setup rejects inputs like "John <script" or "John <a" ā these are caught during DTO validation before the controller method runs.
Sources: packages/cli/src/controllers/survey-answers.dto.ts packages/cli/test/integration/owner.api.test.ts132-142
user-management:reset CommandThe Reset CLI command (packages/cli/src/commands/user-management/reset.ts) resets the database to a clean single-owner state:
global:owneremail, firstName, lastName, password ā null)SharedCredentials record) by creating owner sharesSources: packages/cli/src/commands/user-management/reset.ts packages/cli/test/integration/commands/reset.cmd.test.ts
N8N_AUTH_COOKIE_SECURE=true, cookies are only sent over HTTPSlax prevents CSRF attacksRefresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.