payload-auth-plugin

repository·main·Indexed 43 days ago

https://github.com/updot/payload-auth-plugin

Authentication plugin for Payload CMS (>= 3.0) that integrates Authsmith capabilities. It supports OIDC (Google, GitLab, Apple, etc.), OAuth 2.0 (GitHub, Discord, Facebook, etc.), Password, and experimental Passkey (WebAuthn/FIDO2) authentication providers.

Tokens
69.1K
Snippets
191
Records
255
Agent score
54%

What's inside payload-auth-plugin

  1. Quick Start: Install and run the Payload Website Template

    main

    The template is designed as a full-stack application where the Payload backend and a Next.js frontend are served together.

    Core Backend Components

    • Collections: Includes Users (auth-enabled), Posts (draft-enabled, layout builder enabled), Pages (draft-enabled, layout builder enabled), Media (uploads enabled with pre-configured sizes), and Categories (nested taxonomy).
    • Globals: Includes Header and Footer for managing site-wide navigation and footer data.
    • Access Control: Pre-configured to allow everyone to see published posts and pages, while restricting creation/editing to users.

    Core Frontend Components

    • Next.js App Router: Powers the production-ready website.
    • Layout Builder: Uses pre-designed blocks (Hero, Content, Media, Call To Action, Archive) to build unique page layouts.
    • Lexical Editor: Provides a rich text experience supporting blocks, media, and links.
    • Previewing: Supports both Draft Preview (using Versions with drafts: true) and Live Preview (with SSR support).
    • On-demand Revalidation: Uses Next.js revalidation triggered by afterChange hooks to update the static frontend when content is published.
    # Method 1: Recommended (via Payload Cloud)
    # Go to Payload Cloud and clone this template: https://payloadcms.com/new/clone/website
    
    # Method 2: Using create-payload-app CLI
    pnpx create-payload-app my-project -t website
    
    # Method 3: Using git sparse-checkout
    git clone -n --depth=1 --filter=tree:0 https://github.com/payloadcms/payload my-project && cd my-project && git sparse-checkout set --no-cone templates/website && git checkout && rm -rf .git && git init && git add . && git mv -f templates/website/{.,}* . && git add . && git commit -m "Initial commit"
    
    # Local Development Steps
    cd my-project
    cp .env.example .env
    pnpm install && pnpm dev
  2. Overview of Password Auth Provider

    main

    The PasswordProvider enables classic email and password authentication within a Payload application. It manages the full lifecycle of credential-based authentication, including:

    • Sign-up: Creates a new User document.
    • Sign-in: Validates credentials and issues a session cookie.
    • Forgot password: Sends a one-time recovery code via email.
    • Recover password: Uses the recovery code to set a new password.
    • Reset password: Directly resets a password when both the email and new password are known.

    Unlike OAuth providers, credentials are stored directly in the Users collection (hashed with PBKDF2) via fields injected by withUsersCollection. The provider ID is password.

  3. Understand Passkey Account Storage

    main

    When a passkey is registered, an Account document is created in the Accounts collection with issuerName: 'Passkey'. The credential data is stored in a passkey group field.

    Data Schema for passkey group:

    {
      "credentialId": "string",
      "publicKey": "Uint8Array (serialized as JSON)",
      "counter": "number",
      "transports": "string[] (e.g., ['internal', 'usb'])",
      "deviceType": "'singleDevice' | 'multiDevice'",
      "backedUp": "boolean"
    }

    Note: The passkey group fields are only visible in the Payload admin UI when the account's issuerName is exactly 'Passkey'.

    Security Model: The private key never leaves the user's device. The server only stores the public key and a counter used to detect cloned keys.

  4. Customize Users and Accounts collections

    main

    When using withUsersCollection or withAccountCollection, the plugin follows a merge-then-override pattern. This allows you to extend the collections without redefining the entire configuration.

    Override Rules

    1. Fields: Plugin base fields are merged with your custom fields. For users, plugin fields are appended after yours; for accounts, plugin fields are prepended before yours.
    2. Access: Plugin default access rules are applied first, then your access keys overwrite the corresponding defaults.
    3. Admin: Plugin default admin config is applied first, then your admin keys overwrite them.
    4. Timestamps: timestamps: true is automatically and always set on both collections.

    Because of this pattern, you only need to specify the specific keys you wish to change; all other settings fall back to the plugin's defaults.

    // Only override what you need
    export const Users: CollectionConfig = withUsersCollection({
      slug: 'users',
      fields: [{ name: 'role', type: 'text' }],
      access: {
        read: () => true,          // override: allow public reads
        // create / delete / update → remain as plugin defaults
      },
      admin: {
        useAsTitle: 'email',       // override: display email in the admin list title
        // defaultColumns → remains as plugin default (['name', 'email'])
      },
    })
  5. Register multiple Keycloak realms

    main

    Since the identifier is configurable, you can register multiple Keycloak providers for different realms within the same application. This allows you to support different SSO providers (e.g., one for employees and one for partners) simultaneously.

    1. Define each provider with a unique identifier and name in authPlugin.
    2. Register the corresponding callback URLs in each respective Keycloak realm using the pattern: https://your-domain.com/api/{name}/oauth/callback/{identifier}.
    3. On the client, call oauth(identifier) using the specific identifier for the desired realm.
    // Configuration
    providers: [
      KeyCloakAuthProvider({
        realm: 'employees',
        domain: process.env.KEYCLOAK_DOMAIN!,
        identifier: 'keycloak-employees',
        name: 'Employee SSO',
        client_id: process.env.KEYCLOAK_EMPLOYEE_CLIENT_ID!,
        client_secret: process.env.KEYCLOAK_EMPLOYEE_CLIENT_SECRET!,
      }),
      KeyCloakAuthProvider({
        realm: 'partners',
        domain: process.env.KEYCLOAK_DOMAIN!,
        identifier: 'keycloak-partners',
        name: 'Partner SSO',
        client_id: process.env.KEYCLOAK_PARTNER_CLIENT_ID!,
        client_secret: process.env.KEYCLOAK_PARTNER_CLIENT_SECRET!,
      }),
    ]
    
    // Client Usage
    const { oauth } = authClient.signin()
    <button onClick={() => oauth('keycloak-employees')}>Employee SSO</button>
    <button onClick={() => oauth('keycloak-partners')}>Partner SSO</button>
  6. Choose between AppleOIDCAuthProvider and AppleOAuth2Provider

    main

    The plugin provides two variants for Apple authentication. Choosing the right one depends on your preference for setup complexity versus control:

    • AppleOIDCAuthProvider (Recommended): The simplest setup. It uses Apple's OIDC discovery document to resolve endpoints automatically. It does not require a client_secret because the plugin handles the JWT-based private key flow for you.
    • AppleOAuth2Provider: Use this if you need full control over the authorization server configuration or if you want to explicitly supply your own pre-generated client_secret (JWT).
    | Variant | Import | Protocol | `client_secret` required |
    |---------|--------|----------|--------------------------|
    | OIDC | `AppleOIDCAuthProvider` | OpenID Connect | **No** |
    | OAuth 2.0 | `AppleOAuth2Provider` | OAuth 2.0 | **Yes** |
  7. Important: Apple Name and Email availability

    main

    Apple sends the user's name and email only on the first sign-in. On all subsequent sign-ins, Apple only provides the sub (user identifier).

    How the plugin handles this: The plugin automatically stores the name and email in the Account and User documents during the initial sign-in so they remain available for the user's profile going forward.

  8. Fetch user session (Server-side vs Client-side)

    main

    Depending on where your code is running, use one of the following methods to retrieve the current user's session:

    Server-side: getSession()

    Use this in Next.js Server Components, Route Handlers, or Server Actions. You must pass the incoming request headers so the session cookie can be forwarded.

    Client-side: getClientSession()

    Use this in Client Components ('use client'). It uses the browser's cookie store automatically.

    Warning: Calling getClientSession() in a Node.js/server environment will throw a WrongClientUsage error.

    // Server-side (e.g., Next.js Page)
    import { headers } from 'next/headers'
    const session = await adminAuthClient.getSession({
      headers: await headers(),
    })
    
    // Client-side (e.g., React Hook)
    const session = await appAuthClient.getClientSession()
  9. Customize Microsoft Entra scopes and tenant types

    main

    Custom Scopes

    By default, the provider requests openid profile email. To request additional Microsoft Graph API scopes (e.g., User.Read, Calendars.Read), use overrideScope. Note: You must also add these permissions in the Azure Portal under API permissions.

    MicrosoftEntraAuthProvider({
      client_id: process.env.MSFT_ENTRA_CLIENT_ID!,
      client_secret: process.env.MSFT_ENTRA_CLIENT_SECRET!,
      tenant_id: process.env.MSFT_ENTRA_TENANT_ID!,
      overrideScope: 'openid profile email User.Read Calendars.Read',
    })

    Tenant Types

    The tenant_id determines which users can sign in:

    tenant_id valueWho can sign in
    Your directory tenant ID (UUID)Only users in your specific Entra ID organization
    commonUsers from any Entra ID organization and personal Microsoft accounts
    organizationsUsers from any Entra ID organization (no personal Microsoft accounts)
    consumersPersonal Microsoft accounts only (Outlook, Xbox, Hotmail, etc.)

    To allow all Microsoft accounts, use tenant_id: 'common' and ensure your Azure app registration is set to Multitenant.

    MicrosoftEntraAuthProvider({
      client_id: process.env.MSFT_ENTRA_CLIENT_ID!,
      client_secret: process.env.MSFT_ENTRA_CLIENT_SECRET!,
      tenant_id: 'common',
    })
  10. Understand plugin Endpoints and Session management

    main

    Endpoints

    The plugin preconfigures necessary endpoints for each provider, accounting for different protocols and session management styles. It utilizes a catch-all segment route that processes incoming requests and responses, delegating them to the plugin core for authentication handling.

    The plugin core utilizes Payload's built-in session mechanism. This allows developers to leverage Payload's existing session capabilities and provides a path to tailor or extend user sessions using Payload's exposed methods.

  11. Manage multiple AuthClient instances

    main

    If your application uses multiple authPlugin() instances (for example, one for an admin panel and one for a customer storefront), you must create a separate AuthClient for each. The name passed to the AuthClient constructor must exactly match the name used when configuring the corresponding plugin instance on the server.

    Each client operates independently, targeting its own /api/{name}/ endpoint namespace and managing its own session cookies.

    import { AuthClient } from 'payload-auth-plugin/client'
    
    /** Manages authentication for the Payload admin panel */
    export const adminAuthClient = new AuthClient('admin')
    
    /** Manages authentication for the customer-facing storefront */
    export const storefrontAuthClient = new AuthClient('storefront')
  12. How payload-auth-plugin works

    main

    The plugin extends Payload CMS's built-in authentication by registering catch-all API endpoints for various strategies (OAuth, Password, Passkey, etc.).

    Authentication Flow:

    1. A user interacts with a Provider (e.g., Google, GitHub).
    2. The request hits a Payload Endpoint (e.g., /api/{name}/oauth/callback/{provider}).
    3. The Plugin Core validates tokens, upserts an Account document (linked to a User), and creates a session.
    4. A Payload session cookie is issued using Payload's native cookie-based mechanism.
    5. The user is redirected to the successRedirectPath or errorRedirectPath.