Skip to main content

Overview

Openfront supports multiple authentication methods to suit different use cases. All authentication is handled through the Authorization header using Bearer tokens or session cookies.

Authentication Methods

Openfront provides four authentication mechanisms:
  1. OAuth Access Tokens - For third-party app integrations
  2. API Keys - For server-to-server communication
  3. Customer Tokens - For B2B customer accounts (Openship integration)
  4. Session Cookies - For browser-based applications

1. OAuth Access Tokens

OAuth tokens are ideal for third-party applications that need to act on behalf of users.

How It Works

OAuth tokens are created through the OAuth authorization flow and validated against the OAuthToken model.

Using OAuth Tokens

Authorization
string
required
Bearer token in the format: Bearer {access_token}
curl -X POST https://your-domain.com/api/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -d '{
    "query": "query { products(take: 10) { id title } }"
  }'

Token Validation

The system validates:
  • Token type must be access_token
  • Token must not be revoked (isRevoked !== "true")
  • Token must not be expired (expiresAt > now)
  • Associated OAuth app must be active

Scopes

OAuth tokens include scopes that define what operations the token can perform. The scopes are attached to the session as oauthScopes and used for permission checking.
// Example session with OAuth scopes
{
  itemId: "user_id",
  listKey: "User",
  oauthScopes: ["read:products", "write:orders"]
}

2. API Keys

API keys provide server-to-server authentication with fine-grained scope control.

Key Format

API keys use the of_ prefix:
of_1234567890abcdef

Creating an API Key

API keys are created through the Openfront admin dashboard:
  1. Navigate to SettingsAPI Keys
  2. Click Create API Key
  3. Set name, scopes, and optional IP restrictions
  4. Save the key securely (it’s only shown once)

Using API Keys

Authorization
string
required
Bearer token with of_ prefix: Bearer of_1234567890abcdef
curl -X POST https://your-domain.com/api/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer of_1234567890abcdef" \
  -d '{
    "query": "mutation { importInventory }"
  }'

Security Features

IP Restrictions

API keys can be restricted to specific IP addresses. The system extracts the client IP from:
  • x-forwarded-for header (first IP in comma-separated list)
  • x-real-ip header
  • Connection remote address
// IP validation logic (from keystone/index.ts:140-148)
if (matchingApiKey.restrictedToIPs && 
    Array.isArray(matchingApiKey.restrictedToIPs) && 
    matchingApiKey.restrictedToIPs.length > 0) {
  const allowedIPs = matchingApiKey.restrictedToIPs;
  const isAllowedIP = allowedIPs.includes(actualClientIP);
  
  if (!isAllowedIP) {
    return; // Access denied
  }
}

Automatic Expiration

Keys with expiresAt dates are automatically revoked when expired:
// Auto-revoke expired keys (from keystone/index.ts:155-162)
if (matchingApiKey.expiresAt && new Date() > new Date(matchingApiKey.expiresAt)) {
  await context.sudo().query.ApiKey.updateOne({
    where: { id: matchingApiKey.id },
    data: { status: 'revoked' },
  });
  return; // Access denied
}

Usage Tracking

Every API request increments usage counters:
// Usage tracking (from keystone/index.ts:164-176)
const today = new Date().toISOString().split('T')[0];
const usage = matchingApiKey.usageCount || { total: 0, daily: {} };
usage.total = (usage.total || 0) + 1;
usage.daily[today] = (usage.daily[today] || 0) + 1;

await context.sudo().query.ApiKey.updateOne({
  where: { id: matchingApiKey.id },
  data: {
    lastUsedAt: new Date(),
    usageCount: usage,
  },
});

Scopes

API key scopes define granular permissions:
// Example session with API key scopes
{
  itemId: "user_id",
  listKey: "User",
  apiKeyScopes: [
    "read:products",
    "write:products",
    "read:orders"
  ]
}

3. Customer Tokens (B2B)

Customer tokens enable B2B customers to access their account information and place orders programmatically.

Token Format

Customer tokens use the ctok_ prefix:
ctok_1234567890abcdef

Use Cases

  • Openship integration for B2B order placement
  • Automated order creation from ERP systems
  • Account balance and invoice management

Using Customer Tokens

Authorization
string
required
Bearer token with ctok_ prefix: Bearer ctok_1234567890abcdef
curl -X POST https://your-domain.com/api/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ctok_1234567890abcdef" \
  -d '{
    "query": "query { getCustomerOrders(limit: 10) { id displayId status } }"
  }'

Token Validation

Customer tokens are validated by:
  1. Checking the customerToken field on the User model
  2. Verifying the user has an active business account
  3. Ensuring the account status is active
// Customer token validation (from keystone/index.ts:242-277)
if (accessToken.startsWith('ctok_')) {
  const users = await context.sudo().query.User.findMany({
    where: { customerToken: { equals: accessToken } },
    take: 1,
    query: `
      id
      email
      name
      accounts(where: { 
        status: { equals: "active" }, 
        accountType: { equals: "business" } 
      }) {
        id
        status
        availableCredit
      }
    `
  });
  
  const user = users[0];
  const activeAccount = user.accounts?.[0];
  
  if (!activeAccount) {
    return; // No active account
  }
  
  return { 
    itemId: user.id, 
    listKey: "User",
    customerToken: true,
    activeAccountId: activeAccount.id
  };
}

Generating Customer Tokens

Customers can regenerate their tokens through the API:
mutation RegenerateToken {
  regenerateCustomerToken {
    success
    token
  }
}

4. Session Cookies

Session cookies are used for browser-based authentication after user login. Sessions are managed using Iron-sealed cookies:
// Session config (from keystone/index.ts:26-30)
const sessionConfig = {
  maxAge: 60 * 60 * 24 * 360, // 360 days
  secret: process.env.SESSION_SECRET,
  cookieName: "keystonejs-session",
  sameSite: "lax",
  secure: process.env.NODE_ENV === "production"
};
Session cookie: keystonejs-session={sealed_token}
  • HttpOnly: Yes (prevents XSS attacks)
  • Secure: Yes (in production)
  • SameSite: lax (CSRF protection)
  • Max Age: 360 days

Using Session Cookies

No manual setup needed - browsers automatically send cookies with requests:
// Frontend example using fetch
fetch('https://your-domain.com/api/graphql', {
  method: 'POST',
  credentials: 'include', // Important: sends cookies
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: `query { products(take: 10) { id title } }`
  })
})

Testing Authentication

Test API Key

curl -X POST http://localhost:3000/api/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer of_test_key_12345" \
  -d '{
    "query": "query { products(take: 1) { id } }"
  }'

Test Customer Token

curl -X POST http://localhost:3000/api/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ctok_customer_12345" \
  -d '{
    "query": "query { getCustomerOrders(limit: 5) { id displayId } }"
  }'

Security Best Practices

Never expose API keys or tokens in client-side code. Always store them securely on your server.

Recommendations

  1. Rotate Keys Regularly: Set expiration dates on API keys and rotate them periodically
  2. Use IP Restrictions: Limit API key usage to known IP addresses when possible
  3. Minimum Scopes: Grant only the scopes needed for each integration
  4. Monitor Usage: Track API key usage to detect anomalies
  5. Secure Storage: Store tokens in environment variables or secret management systems
  6. HTTPS Only: Always use HTTPS in production to prevent token interception

Environment Variables

Store sensitive credentials in environment variables:
SESSION_SECRET="your-32-character-secret-here"
STRIPE_SECRET_KEY="sk_test_..."
PAYPAL_CLIENT_ID="your-paypal-client-id"
PAYPAL_CLIENT_SECRET="your-paypal-secret"

Troubleshooting

Common Issues

”Access Denied” Errors

  • Verify token format matches the expected prefix (of_, ctok_, or none for OAuth)
  • Check token expiration date
  • Ensure API key status is active, not revoked
  • Verify IP address is in allowed list (if IP restrictions enabled)

“Insufficient Permissions” Errors

  • Check token scopes match required permissions
  • Verify user role has necessary access rights
  • Ensure OAuth app is active

Session Not Persisting

  • Verify credentials: 'include' is set in fetch requests
  • Check cookie SameSite settings for cross-origin requests
  • Ensure HTTPS in production (required for secure cookies)

Next Steps

API Overview

Learn about the GraphQL API structure

Rate Limiting

Understand rate limits and best practices

Build docs developers (and LLMs) love