Scalar LogoScalar
API Reference

Authentication

Learn how to authenticate with Scalar APIs

API Key Authentication

The simplest way to authenticate API requests is using API keys.

Generating API Keys

Navigate to the admin panel at /admin
Go to Settings → API Keys
Click "Generate New Key" and set permissions
Copy the generated key (it won't be shown again)

Using API Keys

Include the API key in the Authorization header:

curl -H "Authorization: Bearer your-api-key" \
  https://your-app.com/api/posts

API Key Permissions

Configure granular permissions for each API key:

{
  "permissions": {
    "models": {
      "post": ["read", "create", "update"],
      "user": ["read"],
      "category": ["read", "create", "update", "delete"]
    },
    "admin": false,
    "rateLimit": {
      "requests": 1000,
      "windowMs": 3600000
    }
  }
}

JWT Authentication

For user-based authentication, Scalar supports JWT tokens.

Configuration

scalar.config.ts
export default defineConfig({
  auth: {
    jwt: {
      secret: process.env.JWT_SECRET,
      expiresIn: '7d',
      algorithm: 'HS256',
    },

    providers: {
      email: {
        enabled: true,
        smtp: {
          host: process.env.SMTP_HOST,
          port: 587,
          user: process.env.SMTP_USER,
          pass: process.env.SMTP_PASS,
        },
      },

      github: {
        enabled: true,
        clientId: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET,
      },
    },
  },
});

Email Authentication

curl -X POST https://your-app.com/api/auth/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "securepassword",
    "name": "John Doe"
  }'

Response:

{
  "user": {
    "id": "user_123",
    "email": "user@example.com",
    "name": "John Doe"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresAt": "2024-01-22T10:00:00Z"
}
curl -X POST https://your-app.com/api/auth/signin \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "securepassword"
  }'
# Request reset
curl -X POST https://your-app.com/api/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com"}'

# Confirm reset with token
curl -X POST https://your-app.com/api/auth/reset-password/confirm \
  -H "Content-Type: application/json" \
  -d '{
    "token": "reset_token_here",
    "password": "newpassword"
  }'

OAuth Providers

GitHub OAuth

# Redirect to GitHub OAuth
GET https://your-app.com/api/auth/github

# Handle callback (automatic)
GET https://your-app.com/api/auth/github/callback?code=...

Google OAuth

scalar.config.ts
export default defineConfig({
  auth: {
    providers: {
      google: {
        enabled: true,
        clientId: process.env.GOOGLE_CLIENT_ID,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        scope: ['email', 'profile'],
      },
    },
  },
});

Session Management

Creating Sessions

curl -X POST https://your-app.com/api/auth/session \
  -H "Authorization: Bearer jwt-token-here"

Using Sessions

curl -H "Cookie: session=session-token" \
  https://your-app.com/api/posts

Session Configuration

scalar.config.ts
export default defineConfig({
  session: {
    strategy: 'jwt', // 'jwt' or 'database'
    maxAge: 7 * 24 * 60 * 60, // 7 days
    updateAge: 24 * 60 * 60, // 1 day

    cookies: {
      secure: process.env.NODE_ENV === 'production',
      httpOnly: true,
      sameSite: 'lax',
    },
  },
});

Role-Based Access Control (RBAC)

Defining Roles

schema/user.ts
export const user = defineModel({
  name: 'user',
  fields: {
    email: { type: 'email', required: true, unique: true },
    name: { type: 'text', required: true },
    role: {
      type: 'select',
      options: ['viewer', 'editor', 'admin', 'super_admin'],
      defaultValue: 'viewer',
    },
    permissions: {
      type: 'array',
      of: 'text',
      label: 'Custom Permissions',
    },
  },
});

Model-Level Permissions

schema/post.ts
export const post = defineModel({
  name: 'post',
  permissions: {
    create: ['admin', 'editor'],
    read: ['admin', 'editor', 'viewer'],
    update: ['admin', 'editor'],
    delete: ['admin'],

    // Field-level permissions
    fields: {
      publishedAt: {
        update: ['admin'],
      },
    },
  },
});

Custom Permission Checks

middleware/auth.ts
import { scalar } from '@/lib/scalar';

export async function checkPermission(
  userId: string,
  action: string,
  resource: string,
) {
  const user = await scalar.content.findOne({
    model: 'user',
    filter: { id: userId },
  });

  // Check role-based permissions
  if (user.role === 'admin') return true;

  // Check custom permissions
  const permission = `${action}:${resource}`;
  return user.permissions.includes(permission);
}

Middleware Integration

Next.js Middleware

middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyToken } from '@/lib/auth';

export async function middleware(request: NextRequest) {
  // Check if request needs authentication
  if (request.nextUrl.pathname.startsWith('/api/admin')) {
    const token = request.headers.get('authorization')?.replace('Bearer ', '');

    if (!token) {
      return NextResponse.json(
        { error: 'Authentication required' },
        { status: 401 },
      );
    }

    try {
      const payload = await verifyToken(token);

      // Add user info to request headers
      const requestHeaders = new Headers(request.headers);
      requestHeaders.set('x-user-id', payload.userId);
      requestHeaders.set('x-user-role', payload.role);

      return NextResponse.next({
        request: {
          headers: requestHeaders,
        },
      });
    } catch (error) {
      return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/api/admin/:path*', '/admin/:path*'],
};

Express.js Middleware

middleware/auth.js
const jwt = require('jsonwebtoken');

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'Access token required' });
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Invalid or expired token' });
    }

    req.user = user;
    next();
  });
}

module.exports = authenticateToken;

Security Best Practices

Token Security

Never expose JWT secrets or API keys in client-side code

// ✅ Good - Server-side only
const token = jwt.sign(payload, process.env.JWT_SECRET);

// ❌ Bad - Client-side exposure
const token = jwt.sign(payload, 'hardcoded-secret');

Rate Limiting

scalar.config.ts
export default defineConfig({
  security: {
    rateLimit: {
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // requests per window
      skipSuccessfulRequests: false,
      standardHeaders: true,
    },

    bruteForce: {
      freeRetries: 5,
      minWait: 5 * 60 * 1000, // 5 minutes
      maxWait: 15 * 60 * 1000, // 15 minutes
      failuresBeforeBrute: 5,
    },
  },
});

CORS Configuration

scalar.config.ts
export default defineConfig({
  api: {
    cors: {
      origin: ['http://localhost:3000', 'https://yourdomain.com'],
      credentials: true,
      methods: ['GET', 'POST', 'PUT', 'DELETE'],
      allowedHeaders: ['Content-Type', 'Authorization'],
    },
  },
});

Testing Authentication

Unit Tests

__tests__/auth.test.ts
import { createMocks } from 'node-mocks-http';
import { authHandler } from '@/pages/api/auth/signin';

describe('/api/auth/signin', () => {
  it('should authenticate valid credentials', async () => {
    const { req, res } = createMocks({
      method: 'POST',
      body: {
        email: 'test@example.com',
        password: 'testpassword',
      },
    });

    await authHandler(req, res);

    expect(res._getStatusCode()).toBe(200);
    const data = JSON.parse(res._getData());
    expect(data).toHaveProperty('token');
    expect(data).toHaveProperty('user');
  });

  it('should reject invalid credentials', async () => {
    const { req, res } = createMocks({
      method: 'POST',
      body: {
        email: 'test@example.com',
        password: 'wrongpassword',
      },
    });

    await authHandler(req, res);

    expect(res._getStatusCode()).toBe(401);
  });
});

Integration Tests

__tests__/protected-routes.test.ts
import request from 'supertest';
import app from '@/app';

describe('Protected Routes', () => {
  let authToken: string;

  beforeAll(async () => {
    // Get auth token
    const response = await request(app).post('/api/auth/signin').send({
      email: 'admin@example.com',
      password: 'adminpassword',
    });

    authToken = response.body.token;
  });

  it('should access protected route with valid token', async () => {
    const response = await request(app)
      .get('/api/admin/users')
      .set('Authorization', `Bearer ${authToken}`);

    expect(response.status).toBe(200);
  });

  it('should reject access without token', async () => {
    const response = await request(app).get('/api/admin/users');

    expect(response.status).toBe(401);
  });
});

For production deployments, consider using a dedicated authentication service like Auth0, Firebase Auth, or AWS Cognito for enhanced security and scalability.