Skip to content

Authentication Overview

This guide explains how authentication works in Centrali and how to choose the right method for your use case.

Authentication Methods

Centrali uses OAuth 2.0 for authentication. There are two primary ways to authenticate:

1. User Authentication (Dashboard Login)

Use for: - Accessing the Centrali web dashboard - Interactive administration tasks - Managing workspaces and settings

How it works: - Login at centrali.io - Session managed via browser cookies - Automatically handles token refresh

You don't need to implement this - it's handled by the Centrali dashboard.


Use for: - API integrations - SDK usage in applications - Server-to-server communication - CI/CD pipelines - Production deployments

How it works: 1. Create a service account in your workspace dashboard 2. Receive client_id and client_secret 3. Exchange credentials for a JWT access token 4. Use the token in API requests

This is the primary authentication method for building with Centrali.


Service Account Authentication Flow

Step 1: Create a Service Account

See the Account Setup Guide for detailed instructions.

You'll receive:

client_id: ci_a1b2c3d4e5f6g7h8i9j0
client_secret: sk_0123456789abcdef...

Step 2: Obtain an Access Token

Exchange your credentials for a JWT token:

Endpoint: POST https://auth.centrali.io/oidc/token

Request:

curl -X POST "https://auth.centrali.io/oidc/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=ci_a1b2c3d4e5f6g7h8i9j0" \
  -d "client_secret=sk_0123456789abcdef..." \
  -d "scope=openid"

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 25200,
  "scope": "openid"
}

Token Details: - Lifetime: 7 hours (25,200 seconds) - Type: JWT (JSON Web Token) - Refresh: Request a new token before expiration

Step 3: Use the Token in API Requests

Include the token in the Authorization header:

curl -X GET "https://api.centrali.io/data/workspace/my-workspace/api/v1/structures" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

The Centrali SDK handles authentication automatically:

import { CentraliSDK } from '@centrali-io/centrali-sdk';

const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: 'my-workspace',
  clientId: process.env.CENTRALI_CLIENT_ID,
  clientSecret: process.env.CENTRALI_CLIENT_SECRET
});

// SDK automatically:
// 1. Fetches access token on first request
// 2. Caches the token
// 3. Refreshes before expiration
// 4. Retries on auth errors

// Just use the API:
const products = await centrali.queryRecords('Product', { limit: 10 });

Benefits: - ✅ Automatic token management - ✅ Automatic token refresh - ✅ Built-in retry logic - ✅ TypeScript support - ✅ Better error handling

See the SDK Guide for complete documentation.


Manual Token Management

If you're not using the SDK, you'll need to manage tokens yourself:

Best Practices

1. Cache Tokens Don't fetch a new token for every request:

class CentraliAuth {
  constructor(clientId, clientSecret) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.token = null;
    this.tokenExpiry = null;
  }

  async getToken() {
    // Return cached token if still valid
    if (this.token && this.tokenExpiry && Date.now() < this.tokenExpiry) {
      return this.token;
    }

    // Fetch new token
    const response = await fetch('https://auth.centrali.io/oidc/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: this.clientId,
        client_secret: this.clientSecret,
        scope: 'openid'
      })
    });

    const data = await response.json();
    this.token = data.access_token;
    // Refresh 5 minutes before actual expiration
    this.tokenExpiry = Date.now() + ((data.expires_in - 300) * 1000);

    return this.token;
  }
}

// Usage
const auth = new CentraliAuth(clientId, clientSecret);
const token = await auth.getToken();

2. Handle Token Expiration

async function makeApiRequest(url, options = {}) {
  const token = await auth.getToken();

  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  });

  // If unauthorized, token may have expired - retry once
  if (response.status === 401) {
    // Clear cached token and retry
    auth.token = null;
    const newToken = await auth.getToken();

    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${newToken}`
      }
    });
  }

  return response;
}

Security Considerations

Storing Credentials

✅ Do: - Use environment variables - Use secret management services (AWS Secrets Manager, etc.) - Encrypt secrets at rest - Use different credentials per environment

❌ Don't: - Hardcode in source code - Commit to version control - Share via email or messaging - Log credentials (even accidentally)

Credential Rotation

Rotate service account credentials regularly:

Recommended Schedule: - Development: Every 90 days - Production: Every 30-90 days - Immediately: If credentials are compromised

How to Rotate: 1. Dashboard → Settings → Service Accounts 2. Select your service account 3. Click "Rotate Secret" 4. Update your application with the new client_secret 5. Old secret is immediately invalidated

Permission Scoping (Coming Soon)

In the future, you'll be able to: - Create service accounts with limited permissions - Restrict access to specific structures - Limit operations (read-only, write-only, etc.) - Set IP allowlists


Token Format & Claims

Service account JWT tokens include these claims:

{
  "sub": "ci_a1b2c3d4e5f6g7h8i9j0",      // Client ID
  "iss": "https://auth.centrali.io",      // Issuer
  "aud": "https://centrali.io",           // Audience
  "iat": 1705326000,                      // Issued at (timestamp)
  "exp": 1705351200,                      // Expires at (timestamp)
  "workspace": "my-workspace",            // Workspace slug
  "isServiceAccount": "true",             // Service account flag
  "groups": ["developers"]                // Assigned groups
}

You can decode and inspect tokens at jwt.io (never paste production tokens!).


Common Authentication Patterns

Pattern 1: Backend API Service

// server.js
import { CentraliSDK } from '@centrali-io/centrali-sdk';

const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: process.env.CENTRALI_WORKSPACE,
  clientId: process.env.CENTRALI_CLIENT_ID,
  clientSecret: process.env.CENTRALI_CLIENT_SECRET
});

app.get('/api/products', async (req, res) => {
  const products = await centrali.queryRecords('Product', {
    filter: 'inStock = true',
    limit: 20
  });
  res.json(products);
});

Pattern 2: CLI Tool

#!/usr/bin/env node
import { CentraliSDK } from '@centrali-io/centrali-sdk';
import dotenv from 'dotenv';

dotenv.config();

const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: process.env.CENTRALI_WORKSPACE,
  clientId: process.env.CENTRALI_CLIENT_ID,
  clientSecret: process.env.CENTRALI_CLIENT_SECRET
});

async function exportData() {
  const records = await centrali.queryRecords('Product', {});
  console.log(JSON.stringify(records, null, 2));
}

exportData();

Pattern 3: Serverless Function (Vercel, AWS Lambda)

// api/products.js (Vercel)
import { CentraliSDK } from '@centrali-io/centrali-sdk';

// Initialize outside handler for connection reuse
const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: process.env.CENTRALI_WORKSPACE,
  clientId: process.env.CENTRALI_CLIENT_ID,
  clientSecret: process.env.CENTRALI_CLIENT_SECRET
});

export default async function handler(req, res) {
  const products = await centrali.queryRecords('Product', {
    limit: 10
  });

  res.json(products);
}

Troubleshooting

"Invalid client credentials" Error

Causes: - Incorrect client_id or client_secret - Extra spaces or newlines in credentials - Service account was deleted or rotated

Solutions: - Verify credentials in dashboard - Check for whitespace in environment variables - Ensure you're using the correct workspace's credentials

"Token expired" Error

Causes: - Token lifetime exceeded (7 hours) - System clock skew

Solutions: - Fetch a new token - Implement automatic token refresh - Check system time is synchronized

"Forbidden" or "Unauthorized" Errors

Causes: - Token for wrong workspace - Missing Authorization header - Malformed token

Solutions: - Verify workspace slug matches token - Check Authorization: Bearer {token} header format - Ensure token hasn't been tampered with

See the Troubleshooting Guide for more help.



Summary

For most use cases, use the Centrali SDK with service account credentials:

const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: 'my-workspace',
  clientId: process.env.CENTRALI_CLIENT_ID,
  clientSecret: process.env.CENTRALI_CLIENT_SECRET
});

The SDK handles everything else - token fetching, caching, refresh, and retries.

Ready to build? Check out the Quick Start Guide!