Payload CMS authentication: how sign-in works and how to set it up

Idea Labz · Oct 30, 2026 · 14 min read

You’re building on Payload, and several people and systems need to sign in to it. Your team needs the admin panel, your customers need accounts on the website, a mobile app needs to call the API, and some outside system needs to read data without a person at the keyboard. Each of these signs in differently, and the choice you make for each one decides how sessions end, what a leaked credential can do and what you change before launch.

As of October 2026, the latest stable release of Payload is 3.92.1, and one line of config turns any collection into a set of users with login, logout, password resets and sessions built in (Payload’s authentication docs). This guide maps each caller to a sign-in method, then covers the setup, Google login, SSO and two-factor authentication, and the settings to change before launch.

  • How authentication works in Payload
  • Which sign-in method fits each caller
  • What happens when someone logs in
  • How to set up sign-in for your app’s users
  • How a mobile app signs in to Payload
  • API keys for integrations and services
  • Google login, SSO and two-factor authentication
  • What a signed-in user can do: access control
  • Before you go live: a production checklist
  • Frequently asked questions
  • What to do next

How authentication works in Payload

Payload’s authentication lives on collections. Set auth: true on any collection, and every document in it becomes a user who can log in, log out and reset a password (Payload’s authentication docs). Payload adds the email, hash and salt fields for you, generates the login screen for the admin panel, and exposes the auth operations on its REST, GraphQL and Local APIs (Payload’s auth operations docs).

You can have several auth-enabled collections, but only one of them can open the admin panel. The admin.user setting in your Payload config names it, and it’s the users collection unless you say otherwise (Payload’s admin panel docs). Every other auth collection signs in to your own front end and API only.

For example, take a fitness club with three branches. Its staff manage classes, trainers and timetables in the admin panel, so they live in users. Its members book classes on the website and in a mobile app, so they live in a separate members collection and can never reach the admin panel. The club’s door-entry system also checks memberships through the API, and its staff sign in with the club’s Google Workspace accounts. We’ll use this club throughout. If you’re new to Payload’s collections and fields, our guide to Payload covers the content model first.

Which sign-in method fits each caller

Payload ships three ways for a request to prove who it is, HTTP-only cookies, JSON Web Tokens (JWTs) and API keys, and lets you add your own as a custom strategy. They all end the same way, with a user attached to the request as req.user, so the same access rules apply whichever method was used.

CallerMethodHow the credential travelsAt the fitness club
Staff in the admin panelCookieSet by the login screen, sent by the browserBranch managers editing the timetable
Website on the same domain or a subdomainCookieSent by the browser, with credentials: 'include' on cross-origin requestsMembers booking on the club’s site
Website on a different domainCookie with sameSite: 'None', or a JWTCross-site cookie, or an Authorization headerA booking site on a separate domain
Mobile appJWTAuthorization: Bearer <token>, kept in the device’s secure storageThe members’ app
Integration or background serviceAPI keyAuthorization: <collection> API-Key <key>The door-entry system checking a membership
AI assistant over MCPThe MCP plugin’s own API keysBearer key per assistantA manager asking an assistant for this week’s bookings
Outside identity providerCustom strategy, SSO or a pluginThe provider’s session or tokenStaff signing in with Google Workspace

Cookies are the default for browsers. When someone logs in through the REST or GraphQL API, Payload sets an HTTP-only cookie called payload-token, which JavaScript in the page can’t read, and the browser sends it back on every request to the same site (Payload’s cookie strategy docs). Once your front end sits on a different domain, the cookie becomes a third-party cookie, and you either move to a subdomain or configure the cookie and Payload’s csrf and cors lists to allow it. Our guide to Payload and Next.js walks through that two-app setup.

JWTs suit anything that isn’t a browser. The login response also returns the token in its JSON body, and a client can send it back in an Authorization header, as JWT <token> or Bearer <token> (Payload’s JWT strategy docs).

API keys belong to one user each and never expire on their own, which makes them the simplest fit for a service that calls Payload on a schedule (Payload’s API key docs). AI assistants are the exception. They connect through Payload’s MCP plugin, which keeps a separate API Keys collection with per-collection permissions, and our guide to Payload’s MCP plugin covers how to scope them.

What happens when someone logs in

A successful login creates a token, sets the cookie and opens a session. Both the token and the cookie last two hours by default, set by tokenExpiration in seconds on the collection (Payload’s auth defaults in 3.92.1).

Sessions are on by default. Each login adds a session to the user’s document, with its own expiry, and the token carries that session’s ID (Payload’s auth operations docs). That’s what lets you end access before a token runs out:

  • Logout ends the session the request came from. Pass allSessions: true to end every session the user has.
  • A password change by the user ends their other sessions and keeps the one they changed it from.
  • A password change by an admin, or a password reset, ends all of the user’s sessions.

For example, a club member is logged in on a phone and a laptop and loses the phone. Changing the password on the laptop ends the phone’s session at once, without waiting two hours for its token to expire.

You can switch sessions off with useSessions: false. The tokens are then stateless, which saves a database write per login, but they can’t be revoked and stay valid until they expire, even after a password change (Payload’s authentication docs). Keep sessions on for anything with customer accounts.

To keep someone signed in past two hours, a client calls the refresh-token operation before the token expires and gets a new one. A token that has already expired can’t be refreshed, so the user logs in again. The admin panel can do this for you with admin.autoRefresh: true, which keeps staff logged in while the panel is open.

Payload also decides what goes inside the token. Fields marked saveToJWT: true, such as a role, travel in the token and are available on req.user without a database read (Payload’s token data docs). Keep it to small fields that access rules need, because everything in a JWT can be read by whoever holds it.

How to set up sign-in for your app’s users

Your customers get their own auth collection, separate from staff. The steps below build the fitness club’s members collection.

  1. Create the collection
  2. Decide who can sign up
  3. Verify email addresses
  4. Send reset links to your own front end
  5. Read the signed-in member
  6. Scope members to their own data

1. Create the collection

Add a collection with auth switched on and the options your members need. The lockout and expiry settings can differ from your staff’s, and loginWithUsername lets members log in with a username.

export const Members: CollectionConfig = {
  slug: 'members',
  auth: {
    tokenExpiration: 60 * 60 * 24 * 7, // a week, in seconds
    verify: true,
    maxLoginAttempts: 5,
    lockTime: 10 * 60 * 1000, // ten minutes, in milliseconds
  },
  fields: [
    { name: 'name', type: 'text', required: true },
    { name: 'homeBranch', type: 'relationship', relationTo: 'branches' },
  ],
}

2. Decide who can sign up

Signing up is a create operation on the collection, so the collection’s create access rule decides who may do it. Return true to let anyone register from your website, or check something first. In our monorepo template, the users collection is invite-only by default, so a sign-up needs a valid invite token. The same rule switches to public, admin-only or closed sign-up for a client project.

3. Verify email addresses

With verify: true, a new member gets an email with a verification link and can’t log in until they’ve used it (Payload’s authentication email docs).

4. Send reset links to your own front end

The forgot-password email links to the admin panel by default. For members, override forgotPassword.generateEmailHTML so the link opens a reset page on your website, which then calls the reset-password operation with the token (Payload’s authentication email docs). The reset token expires after one hour by default, and Payload sends at most one reset email per user every 15 seconds (Payload’s forgotPassword operation in 3.92.1).

5. Read the signed-in member

A browser client calls GET /api/members/me, which returns the member and their token expiry, or null when nobody is signed in. On the server, payload.auth({ headers }) reads the same cookie or header through the Local API (Payload’s auth operations docs). Our Next.js guide shows the Server Component version.

6. Scope members to their own data

What a member can see comes from access rules, so the club’s bookings collection returns a query that limits a member to their own bookings:

read: ({ req: { user } }) => {
  if (user?.collection === 'users') return true // staff see every booking
  if (user?.collection === 'members') return { member: { equals: user.id } }
  return false
},

How a mobile app signs in to Payload

A mobile app signs in with a JWT in the Authorization header, because React Native has no browser cookie jar to hold Payload’s cookie. Payload accepts Bearer tokens out of the box, so the server needs no changes.

Our monorepo template ships an Expo mobile app that works this way, and its sign-in follows five steps:

  1. The app posts the email and password to /api/users/login and gets back the user, the token and its expiry.
  2. It stores the token with expo-secure-store, in the iOS Keychain or the Android Keystore, set to WHEN_UNLOCKED_THIS_DEVICE_ONLY, so the token can’t be read while the phone is locked and doesn’t sync to other devices.
  3. A shared API client adds Authorization: Bearer <token> to every request and sends no cookies.
  4. The shared client carries a refresh call that posts to /api/users/refresh-token and stores the new token. The template doesn’t refresh on its own, so each project decides when to call it, before a sensitive action or on a timer.
  5. The client exposes one hook for a 401 response, which a project wires to clear its cached data and send the user back to the login screen.

The token never goes into AsyncStorage, localStorage or a JavaScript global. For the fitness club, that’s the members’ app, pointed at the members collection instead of users. Keep the app’s token lifetime in step with how often members open it, because a member who comes back after the token has expired has to log in again.

API keys for integrations and services

An API key lets a service act as one Payload user without logging in. Switch it on with useAPIKey: true on an auth collection, and staff who have admin access can generate, regenerate or revoke a key on each user’s document (Payload’s API key docs). The service then sends the collection’s slug, the words API-Key and the key:

const response = await fetch(`https://club.example.com/api/members/${memberId}`, {
  headers: {
    Authorization: `integrations API-Key ${process.env.PAYLOAD_API_KEY}`,
  },
})

The key behaves differently from a password in four ways:

  • Payload shows a new key once, then only a masked value. Copy it when you generate it.
  • The key doesn’t expire, and changing the user’s password doesn’t disable it. Revoke or regenerate it when the service’s access should end.
  • Changing PAYLOAD_SECRET invalidates every API key, because the secret encrypts them.
  • Access control applies exactly as it does to a person, so the key can do only what its user can do.

Give each integration its own user, in its own collection, with a role that grants exactly what it needs. For example, the club’s door-entry system gets an integrations collection with useAPIKey: true and disableLocalStrategy: true, so it can’t log in with a password at all, and a rule that lets it read members’ membership status only. If its key leaks, the holder can check whether a membership is active and nothing else.

Google login, SSO and two-factor authentication

Payload’s built-in strategy signs people in with an email or username and a password. Google login, other social logins, single sign-on (SSO) and two-factor authentication (2FA) aren’t part of the core package as of 3.92.1. You add them through one of four routes:

RouteWhat you getWhat you maintainFits
Payload Enterprise SSOSign-in through SAML or OAuth 2.0 identity providers, including Microsoft, Okta and Google, with profiles created on first loginAn Enterprise licenceStaff who already sign in through a company identity provider
payload-auth (Better Auth)Social providers, passkeys, 2FA and organisations, for the admin panel and your appA community plugin and Better Auth’s releasesSocial login and 2FA for customers and staff
payload-oauth2An OAuth 2.0 sign-in for any provider, with Google and Zitadel testedA community pluginOne provider, such as Google, on the login screen
Custom strategyYour provider’s session or token mapped to a Payload userThe strategy and the provider integrationTeams already on Auth0, Clerk, Auth.js or a company identity provider

Payload Enterprise SSO is one of Payload’s Enterprise features, sold under an Enterprise licence (Payload’s SSO page).

payload-auth swaps Payload’s authentication for Better Auth, so one session covers the admin panel and your front end, and the admin’s login, sign-up, password reset and 2FA screens are replaced with Better Auth’s (payload-auth). As of October 2026, it supports Payload 3.79.1 and later 3.x releases (payload-auth’s package file).

payload-oauth2 adds an authorisation route and a callback route to an auth collection. A link to /api/users/oauth/google starts the Google sign-in, and the callback finds or creates the matching user, sets Payload’s cookie and redirects back to your app (payload-oauth2 on GitHub).

A custom strategy is a function on the collection that reads the incoming request, checks it against your provider and returns a Payload user or null (Payload’s custom strategies docs).

For example, the fitness club’s staff already use Google Workspace, and the club’s IT lead requires 2-Step Verification on those accounts. Signing staff in with Google, through payload-oauth2 or a custom strategy, gives the admin panel that second factor without Payload storing one. The club’s members sign in with an email and a password, so 2FA for them would come from payload-auth and Better Auth’s two-factor plugin.

What a signed-in user can do: access control

Authentication tells Payload who sent a request. Access control decides what that user may do, through functions on each collection, field and operation that receive req.user and return true, false or a query (Payload’s access control docs). Role-based access control (RBAC) is one field and a set of those functions. Give each user a roles field, check it in every rule, and use access.admin to decide which roles may open the admin panel at all (Payload’s admin panel docs).

Our monorepo template’s users collection adds two guards around sign-in. A beforeLogin hook rejects any user whose active checkbox is off, with a 403, and new users start inactive. A field hook on roles means only an admin can set roles, so a user editing their own profile can’t promote themselves, and the roles are saved to the token for the access rules to read.

Roles get more involved when one Payload serves several businesses or brands, because each user’s access is then scoped to their tenant as well. Our guide to multi-tenancy in Payload covers how tenancy and roles combine.

Before you go live: a production checklist

Several of Payload’s defaults suit local development and need a decision before launch. The defaults below are as of Payload 3.92.1.

  • Set a long, random PAYLOAD_SECRET, different in every environment. It signs every token and encrypts every API key. Our template’s checklist asks for at least 32 characters.
  • Set cookies.secure: true in production. The default is false, and Payload adds the Secure flag on its own only when sameSite is 'None' (Payload’s cookie code in 3.92.1). Leave it false only for http://localhost.
  • List your front-end domains in csrf, and set serverURL. With an empty list, Payload accepts the cookie from any origin. With a list, a cookie sent from any other origin is treated as signed out (Payload’s cookie strategy docs). If a front end sends cookies across domains, list its origin in cors too, without a wildcard.
  • Review the lockout. The default locks an account for 10 minutes after 5 failed logins. Set maxLoginAttempts and lockTime per collection, and don’t set the attempts to 0, which switches lockout off.
  • Set tokenExpiration for each collection, so staff and customers don’t share the two-hour default by accident.
  • Turn on verify for any collection with public sign-up.
  • Rate-limit at your host or proxy. Payload 3.92.1 has per-account lockout and the 15-second gap between reset emails, but no limit on requests per IP address, so put one in front of /api/*/login and /api/*/forgot-password.
  • Disable GraphQL if you don’t use it, with graphQL.disable: true, and set maxComplexity if you do (Payload’s docs on preventing API abuse).
  • Keep admin.autoLogin behind a development check, so a test login never ships.
  • Log sign-ins if you need an audit trail. Payload doesn’t log sign-ins by default. An afterLogin hook that writes to a log collection records who signed in and when, and afterChange hooks can record what they changed.
  • Stay on the latest stable release. Payload publishes its security advisories on GitHub, and our review of Payload covers how often they arrive and how to keep up.

Frequently asked questions

Does Payload CMS have built-in authentication?

Yes. Set auth: true on any collection, and Payload adds login, logout, password resets, email verification, account lockout and sessions on its REST, GraphQL and Local APIs. The admin panel uses the same system.

Can Payload handle sign-in for my website’s users?

Yes. Give your customers their own auth-enabled collection, and only the collection named in admin.user can open the admin panel. Your website signs them in with a cookie, and a mobile app with a JWT.

Does Payload support Google login and OAuth?

Not in the core package, as of Payload 3.92.1. Add Google or another OAuth 2.0 provider with payload-oauth2, payload-auth or a custom auth strategy, or use the SSO in Payload Enterprise.

Does Payload CMS support two-factor authentication?

Not in the core package. Add it with payload-auth, which uses Better Auth’s two-factor plugin, or sign users in through an identity provider that enforces it, such as Google Workspace.

How long does a Payload login last?

Two hours by default, for both the token and the cookie. Change it with tokenExpiration, in seconds, on each auth collection, or keep people signed in with the refresh-token operation.

What happens if I change PAYLOAD_SECRET?

Every API key stops working, because the secret encrypts them, and tokens signed with the old secret no longer verify, so everyone logs in again. Regenerate your integrations’ keys at the same time.

Does Payload have rate limiting?

As of 3.92.1, Payload locks an account after repeated failed logins (5 attempts and 10 minutes by default) and spaces password-reset emails, but it doesn’t limit requests per IP address. Add that at your host, proxy or CDN.

Can I use Better Auth or Auth.js with Payload?

Yes. payload-auth replaces Payload’s auth with Better Auth for the admin panel and your app. For Auth.js or another provider, a custom strategy reads the provider’s session and returns the matching Payload user.

What to do next

Map your callers before you write any config, from staff and customers to apps, integrations and assistants, and pick the method each one uses. Give customers their own collection, turn on verification and sessions, and give every integration its own user with an API key and a narrow role. Decide early whether you need Google login, SSO or 2FA, because the route you pick changes how your users are stored. Payload’s auth example is a working starting point, and if you’d rather have the sign-in shaped and built for you, that’s part of our Payload development work.

References

All links checked and active as of 30 October 2026.

READY TO MAKE A REAL CHANGE?

Let's build it together