Most PayloadCMS auth examples cover either plain email/password or "toy" OAuth setups that fall apart as soon as you introduce real-world constraints like multiple frontends, native apps, or third‑party identity providers.
Teams then fight the framework, passing session state through places it does not belong, relying on req.user when there is no session yet, or coupling admin auth to application auth in a way that becomes impossible to reason about once the project grows.
In practice, authentication is the part of a headless stack that ages the worst when it is improvised. It is also the part you least want to be debugging at 2am. That is why Payload's move toward explicit custom strategies in v3 is such a big deal: it hands control back to you, but also quietly deman...
Most PayloadCMS auth examples cover either plain email/password or "toy" OAuth setups that fall apart as soon as you introduce real-world constraints like multiple frontends, native apps, or third‑party identity providers.
Teams then fight the framework, passing session state through places it does not belong, relying on req.user when there is no session yet, or coupling admin auth to application auth in a way that becomes impossible to reason about once the project grows.
In practice, authentication is the part of a headless stack that ages the worst when it is improvised. It is also the part you least want to be debugging at 2am. That is why Payload's move toward explicit custom strategies in v3 is such a big deal: it hands control back to you, but also quietly demands that you think about auth as a first‑class design concern.
Rethinking auth
The mental shift is to stop treating Payload as "the thing that logs users in" and instead treat it as "the thing that trusts already‑verified identities." Your identity provider—whether that is Google, a dedicated auth service, or a bespoke OAuth server—owns the ceremony: PKCE, redirects, device flows, magic links, all of it. Payload’s job is much narrower and much clearer, given some verifiable proof of identity on the request (headers, tokens, cookies), resolve that to a user document and optionally create or update that user when it makes sense.
This is exactly what a custom auth strategy is built for. You get a function that receives the incoming request context and must answer one question: "Which user, if any, does this represent?" That sounds simple, but once you commit to that contract, your architecture naturally starts to clean itself up. Frontends become responsible for doing real OAuth flows; Payload becomes responsible for enforcing access rules based on clear, consistent user data.
Production
Where teams usually get burned is at the seams. Admin users and app users turn out not to be the same thing. External integrations need API keys or service accounts that bypass normal login flows. Mobile apps need an auth path that never touches the admin UI at all. If your strategy is "we’ll just log in via the admin and call it a day," you quickly end up with logic scattered across routes, hooks, and ad‑hoc middleware.
A deliberate, strategy‑driven approach forces you to centralise that logic. One strategy might map a signed token from your OAuth provider to a user collection; another might handle internal service tokens; a third might be reserved for admin‑only flows. It is easier to test, easier to reason about, and—crucially—easier to replace when your identity requirements change, which they inevitably do.
How to wire oAuth
The full implementation that prompted this post lives on Rubix Studios’ blog, where a complete Payload v3 setup is broken down from the OAuth flow to the collection config and the custom strategy itself, including how to keep the CMS stateless while still offering a smooth admin experience. If you are already using Payload or planning a migration and want to see what this looks like in a real codebase instead of a contrived snippet, the walkthrough is here: PayloadCMS Custom Auth Strategy - Rubix Studios on rubixstudios.com.au.