Authentication
API Key Authentication
Section titled “API Key Authentication”The simplest way to authenticate API requests is using API keys.
Generating API Keys
Section titled “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
Section titled “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
Section titled “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
Section titled “JWT Authentication”For user-based authentication, Scalar supports JWT tokens.
Configuration
Section titled “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
Section titled “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 resetcurl -X POST https://your-app.com/api/auth/reset-password \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com"}'
# Confirm reset with tokencurl -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
Section titled “OAuth Providers”GitHub OAuth
Section titled “GitHub OAuth”# Redirect to GitHub OAuthGET https://your-app.com/api/auth/github
# Handle callback (automatic)GET https://your-app.com/api/auth/github/callback?code=...
Google OAuth
Section titled “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
Section titled “Session Management”Creating Sessions
Section titled “Creating Sessions”curl -X POST https://your-app.com/api/auth/session \ -H "Authorization: Bearer jwt-token-here"
Using Sessions
Section titled “Using Sessions”curl -H "Cookie: session=session-token" \ https://your-app.com/api/posts
Session Configuration
Section titled “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)
Section titled “Role-Based Access Control (RBAC)”Defining Roles
Section titled “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
Section titled “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
Section titled “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
Section titled “Middleware Integration”Next.js Middleware
Section titled “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
Section titled “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
Section titled “Security Best Practices”Token Security
Section titled “Token Security”// ✅ Good - Server-side onlyconst token = jwt.sign(payload, process.env.JWT_SECRET);
// ❌ Bad - Client-side exposureconst token = jwt.sign(payload, 'hardcoded-secret');
Rate Limiting
Section titled “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
Section titled “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
Section titled “Testing Authentication”Unit Tests
Section titled “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
Section titled “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); });});