@voyantcloud/payload-auth
v0.0.6
Published
Voyant authentication plugin for Payload CMS
Downloads
250
Maintainers
Readme
@voyantcloud/payload-auth
Ticket-based authentication plugin that lets customers host Payload CMS on their own domain while trusting Voyant for identity. If a user is already logged in to the Voyant dashboard, they land in Payload Admin signed-in — no duplicated credentials, no cross-site cookies.
What this plugin does
- Adds custom endpoints to your Payload instance:
GET /api/auth/voyant/login— completes the one-time ticket handoff and redirects to Admin.GET /api/auth/voyant/start— initiates SSO flow for direct /admin access (redirects to Voyant Dashboard).POST /api/auth/voyant/consume— JSON API used by the optional login component.
- Auto-provisions Payload users keyed by the Voyant account ID and maps roles (default: Voyant super admins →
super-admin, everyone else →user). - Issues the Payload auth cookie directly inside the Payload app using your existing
auth.cookiesconfiguration (no forced SameSite). - Optionally sets a
payload-tenantcookie if you provide a resolver hook. - Injects a lightweight “Continue with Voyant” message on the login screen (disabled with
installAdminLoginButton: false). - Exports helpers (
getVoyantUserFields,defaultRoleMapper,defaultMapVoyantUser) for advanced customization.
Prerequisites
- A Voyant API deployment exposing
POST /auth/payload/validate-ticket(already provided byapps/api). - A Voyant API key scoped to the target workspace.
- The workspace identifier the Voyant dashboard uses for the customer tenant.
- A Payload collection with
auth: true(usuallyusers).
Install
pnpm add @voyantcloud/payload-authQuick start
// payload.config.ts
import { buildConfig } from 'payload'
import { voyantAuthPlugin } from '@voyantcloud/payload-auth'
export default buildConfig({
collections: [Users],
plugins: [
voyantAuthPlugin({
voyantWorkspaceId: process.env.VOYANT_WORKSPACE_ID!,
voyantApiKey: process.env.VOYANT_API_KEY!,
collectionSlug: 'users',
}),
],
})That’s it — the plugin registers the endpoints, injects the voyantId field, and adds the optional login helper.
Linking from Voyant Dash
When a user clicks "Admin" in Voyant Dash, send them to the customer-hosted Payload admin with a one-time ticket:
https://customersite.com/api/auth/voyant/login?ticket=<ticket>&return_to=/adminTickets are generated by POST /auth/payload/generate-ticket inside the Voyant API. They expire in ~2 minutes and are single-use.
Options
voyantAuthPlugin({
voyantWorkspaceId: 'ws_xxx', // Required workspace identifier
voyantApiKey: 'voyant_api_abc', // Required API key from Voyant dashboard
collectionSlug: 'users', // Auth-enabled collection (default "users")
voyantIdFieldName: 'voyantId', // Override the injected field name
addVoyantIdField: true, // Disable if you manage the field yourself
installAdminLoginButton: true, // Disable to skip the login helper component
roleMapping: (profile) => // Optional custom role mapper
profile.isSuperAdmin ? ['super-admin'] : ['user'],
mapVoyantUser: ({ profile, roles }) => ({
fullName: profile.fullName,
avatarUrl: profile.pictureUrl,
roles,
}),
cookieOverrides: { sameSite: 'none', secure: true },
allowedReturnToHosts: ['admin.customer.com'],
resolveTenantIdByHost: async ({ req }) => {
const host = req.headers.get('host')
if (!host) return
const tenant = await req.payload.find({
collection: 'tenants',
where: { domain: { equals: host } },
limit: 1,
})
return tenant.docs[0]?.id
},
})Added fields
By default the plugin appends the following field to your auth collection (unless addVoyantIdField: false):
import { getVoyantUserFields } from '@voyantcloud/payload-auth'
getVoyantUserFields() // → [{ name: 'voyantId', type: 'text', unique: true, saveToJWT: true }]If you want to manage the field yourself, set addVoyantIdField: false and call getVoyantUserFields in your collection definition.
Endpoint reference
GET /api/auth/voyant/login
Query params:
| Param | Description |
|-------------|----------------------------------|
| ticket | Voyant one-time ticket (required) |
| return_to | Relative or allowed absolute URL |
Behaviour:
- Validates the ticket with Voyant API.
- Creates/updates the Payload user.
- Signs the Payload auth cookie (respecting your configured cookie attributes).
- Optionally sets
payload-tenantifresolveTenantIdByHostreturns an ID. - Redirects to
return_toor/admin.
GET /api/auth/voyant/start
Initiates SSO flow for users who navigate directly to /admin without a ticket. Redirects to the Voyant Dashboard for authentication, which then redirects back with a ticket.
POST /api/auth/voyant/consume
Body:
{
"ticket": "abc123", // required
"return_to": "/admin" // optional
}Returns { ok: true, redirectTo: "https://..." } and sets cookies. Used by the login helper to finish sign-in without a full page reload.
Front-end helper
When installAdminLoginButton is true, the plugin injects a client component on the Payload login screen. It:
- Detects
ticket/return_toquery params and POSTs to/api/auth/voyant/consumeautomatically. - Shows guidance for users who arrive directly (without a ticket) to go back to the Voyant dashboard.
Disable the helper by setting installAdminLoginButton: false and render your own UI if preferred.
Helper exports
import {
DEFAULT_VOYANT_ID_FIELD,
getVoyantUserFields,
defaultRoleMapper,
defaultMapVoyantUser,
resolveRoles,
} from '@voyantcloud/payload-auth'defaultRoleMapper(profile)→['super-admin']ifprofile.isSuperAdmin, otherwise['user'].defaultMapVoyantUser({ profile, roles })→{ email, name, roles, voyantId }.resolveRoles(profile, roleMapping)→ applies function or mapping config, falling back todefaultRoleMapper.
Voyant API contract
The plugin expects the Voyant API to expose:
POST https://api.voyant.cloud/auth/payload/validate-ticket
Headers: { Authorization: "Bearer <voyantApiKey>", X-Voyant-Workspace: "ws_xxx" }
Body: { ticket, workspaceId }
Response: { valid: boolean, user?: { id, email, firstName?, lastName?, fullName?, pictureUrl?, isSuperAdmin?, workspaceId } }Only valid tickets return user. Invalid, expired, or already-consumed tickets must yield a non-200 or { valid: false }.
Error handling
- Missing or invalid tickets redirect to
/admin/login?voyantError=.... - The JSON endpoint responds with
{ ok: false, error: '...' }and HTTP400/401. - Enable debug logging with
debug: true(logs prefixed with[VOYANT_AUTH]).
Security notes
- All cookies are set by the Payload app, so browsers treat them as first-party.
return_todefaults to same-origin paths; absolute URLs must be allow-listed viaallowedReturnToHosts.- Tickets are single-use and short-lived (enforced server-side by Voyant API).
- Authentication uses a per-workspace API key (sent as
Authorization: Bearer …).
Complete Integration Guide
Overview
The Voyant Payload Auth plugin enables seamless SSO between the Voyant platform and your Payload CMS admin panel. There are three ways users can access the admin:
- From Voyant Dashboard — Click "Open Admin" on a deployment
- Direct URL Access — Navigate directly to
/adminon your site - Local Development — Use
voyant dev --payloadfor authenticated local development
Authentication Flows
Flow 1: Dashboard → Admin
User clicks "Open Admin" in Dashboard
↓
Dashboard generates ticket via POST /auth/payload/generate-ticket
↓
User redirected to: https://yoursite.com/api/auth/voyant/login?ticket=XXX&return_to=/admin
↓
Plugin validates ticket, creates/updates user, sets cookie
↓
User lands in authenticated Admin panelFlow 2: Direct /admin Access
User navigates to https://yoursite.com/admin
↓
Plugin detects no auth → redirects to /api/auth/voyant/start
↓
Start endpoint redirects to Voyant Dashboard: /auth/redirect?workspace=X&return_to=...
↓
Dashboard authenticates user, generates ticket
↓
User redirected back with ticket → authenticatedFlow 3: Local Development
# Initialize your project
voyant init
# Start with Payload auth
voyant dev --payload
# Or just Payload (skip dev session)
voyant dev --payload-onlyThis generates a ticket and opens your browser to http://localhost:3000/api/auth/voyant/login?ticket=XXX&return_to=/admin
Environment Variables
The plugin requires two environment variables:
| Variable | Description | How to Set |
|----------|-------------|------------|
| VOYANT_WORKSPACE_ID | Your workspace identifier | Auto-injected during deployment |
| VOYANT_API_KEY | API key for ticket validation | Create in Dashboard → Developers → API Keys |
For local development, create a .env file:
VOYANT_WORKSPACE_ID=wksp_xxxxxxxxxxxxx
VOYANT_API_KEY=vyk_xxxxxxxxxxxxxDeployment Configuration
When deploying with Voyant:
- VOYANT_WORKSPACE_ID is automatically injected during deployment
- VOYANT_API_KEY must be added manually:
- Go to Dashboard → Developers → API Keys
- Create a new key with appropriate scopes
- Add it as an environment variable in Dashboard → Projects → [Your Project] → Settings → Environment Variables
Multi-Tenant Support
For multi-tenant deployments, use the resolveTenantIdByHost hook:
voyantAuthPlugin({
voyantWorkspaceId: process.env.VOYANT_WORKSPACE_ID!,
voyantApiKey: process.env.VOYANT_API_KEY!,
resolveTenantIdByHost: async ({ req, profile }) => {
const host = req.headers.get('host')
const tenant = await req.payload.find({
collection: 'tenants',
where: { domain: { equals: host } },
limit: 1,
})
return tenant.docs[0]?.id
},
})This sets a payload-tenant cookie that your collections can use for tenant isolation.
Workspace & User Model
- Workspaces — Each workspace has a unique ID and API key
- Users — Workspace members can access deployed Payload admin panels
- Roles — Voyant super admins map to
super-admin, others touser(customizable)
Users are automatically provisioned in Payload on first login. Subsequent logins update their profile if changed.
Troubleshooting
"Invalid or expired ticket"
- Tickets expire after 5 minutes — try again
- Ensure
VOYANT_API_KEYis valid and has correct scopes - Check that
VOYANT_WORKSPACE_IDmatches the ticket's workspace
"User not a member of this workspace"
- The user must be added to the workspace in Dashboard → Settings → Team
Cookies not being set (local development)
// For local development, relax cookie settings:
voyantAuthPlugin({
voyantWorkspaceId: process.env.VOYANT_WORKSPACE_ID!,
voyantApiKey: process.env.VOYANT_API_KEY!,
debug: true, // Enable verbose logging
cookieOverrides: {
secure: false, // Allow HTTP for localhost
sameSite: 'Lax',
},
})Debug logging
Enable debug mode to see detailed authentication logs:
voyantAuthPlugin({
// ... other options
debug: true, // or set VOYANT_AUTH_DEBUG=true
})Logs are prefixed with [VOYANT_AUTH].
License
MIT
