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
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
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
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
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
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
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
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
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
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
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
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
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.