Authentication
Overview
Section titled “Overview”Uploadista is designed to work with your existing authentication system. Rather than imposing a specific auth solution, Uploadista provides flexible middleware hooks on the server and credential injection on the client, allowing you to integrate any authentication method you already use.
Key design principles:
- Bring your own auth: Use JWT, API keys, sessions, OAuth, or any auth system
- Middleware-based: Server adapters accept auth middleware that you control
- Client flexibility: Clients can inject credentials via headers, cookies, or custom logic
- Optional by default: Authentication is entirely optional for development or public endpoints
How It Works
Section titled “How It Works”Server-Side: Auth Middleware
Section titled “Server-Side: Auth Middleware”Each server adapter (Hono, Express, Fastify) accepts an authMiddleware function. This middleware:
- Receives the incoming request context
- Validates credentials using your auth system
- Returns an
AuthContextwith client ID and permissions, ornullto reject
// The auth middleware signaturetype AuthMiddleware = (context) => Promise<AuthContext | null>;
// AuthContext returned on successful authenticationtype AuthContext = { clientId: string; // Unique identifier for the authenticated user/client permissions?: string[]; // Optional permissions array (see Permissions section) metadata?: Record<string, unknown>; // Optional custom data};Client-Side: Credential Injection
Section titled “Client-Side: Credential Injection”The client supports two authentication modes:
- Direct Mode: You provide credentials (headers, cookies) for each request
- UploadistaCloud Mode: Automatic JWT token exchange with your auth server
// Direct mode - bring your own authauth: { mode: 'direct', getCredentials: async () => ({ headers: { 'Authorization': `Bearer ${await getToken()}` } })}
// UploadistaCloud mode - automatic token managementauth: { mode: 'uploadista-cloud', authServerUrl: '/api/auth/token', clientId: 'your-client-id'}Server Authentication
Section titled “Server Authentication”Hono Adapter
Section titled “Hono Adapter”import { createUploadistaServer } from "@uploadista/server";import { honoAdapter } from "@uploadista/adapters-hono";
const uploadistaServer = await createUploadistaServer({ dataStore: s3Store({ /* config */ }), kvStore: redisKvStore({ /* config */ }), flows, adapter: honoAdapter({ authMiddleware: async (c) => { // Extract and validate credentials const token = c.req.header("Authorization")?.split(" ")[1]; if (!token) return null;
try { const payload = await verifyJWT(token, process.env.JWT_SECRET); return { clientId: payload.sub, permissions: payload.permissions || [], }; } catch { return null; } }, }),});Express Adapter
Section titled “Express Adapter”import { expressAdapter } from "@uploadista/adapters-express";
const uploadistaServer = await createUploadistaServer({ // ... other config adapter: expressAdapter({ authMiddleware: async (ctx) => { const token = ctx.request.headers.authorization?.split(" ")[1]; if (!token) return null;
try { const payload = await verifyJWT(token, process.env.JWT_SECRET); return { clientId: payload.sub, permissions: payload.permissions }; } catch { return null; } }, }),});Fastify Adapter
Section titled “Fastify Adapter”import { fastifyAdapter } from "@uploadista/adapters-fastify";
const uploadistaServer = await createUploadistaServer({ // ... other config adapter: fastifyAdapter({ authMiddleware: async (ctx) => { const token = ctx.request.headers.authorization?.split(" ")[1]; if (!token) return null;
try { const payload = await verifyJWT(token, process.env.JWT_SECRET); return { clientId: payload.sub, permissions: payload.permissions }; } catch { return null; } }, }),});Common Auth Patterns
Section titled “Common Auth Patterns”authMiddleware: async (ctx) => { const token = ctx.request.headers.authorization?.replace('Bearer ', ''); if (!token) return null;
const user = await verifyJWT(token); return user ? { clientId: user.sub, permissions: user.permissions } : null;}authMiddleware: async (ctx) => { const apiKey = ctx.request.headers['x-api-key']; if (!apiKey) return null;
const client = await db.clients.findByApiKey(apiKey); return client ? { clientId: client.id, permissions: client.permissions } : null;}authMiddleware: async (ctx) => { const sessionId = ctx.request.cookies?.session; if (!sessionId) return null;
const session = await getSession(sessionId); return session ? { clientId: session.userId } : null;}authMiddleware: async (ctx) => { const token = ctx.request.headers.authorization?.replace('Bearer ', ''); if (!token) return null;
// Validate with your OAuth provider const userInfo = await oauth.validateAccessToken(token); return userInfo ? { clientId: userInfo.sub } : null;}Client Authentication
Section titled “Client Authentication”Direct Mode
Section titled “Direct Mode”Direct mode gives you complete control over authentication. Provide a getCredentials function that returns headers or cookies:
import { createUploadistaClient } from "@uploadista/client";
const client = createUploadistaClient({ baseUrl: "https://api.example.com", storageId: "my-storage", chunkSize: 1024 * 1024,
auth: { mode: 'direct', getCredentials: async () => ({ headers: { 'Authorization': `Bearer ${await getAccessToken()}` } }) }});
// Credentials are automatically attached to all requestsawait client.upload(file);Direct Mode Examples
Section titled “Direct Mode Examples”auth: { mode: 'direct', getCredentials: async () => { const token = await oauth.getAccessToken(); return { headers: { 'Authorization': `Bearer ${token}` } }; }}auth: { mode: 'direct', getCredentials: () => ({ headers: { 'X-API-Key': process.env.API_KEY } })}auth: { mode: 'direct', getCredentials: () => ({ cookies: { 'session': getSessionId() } })}UploadistaCloud Mode
Section titled “UploadistaCloud Mode”UploadistaCloud mode provides automatic JWT token exchange. Your backend exchanges credentials with Uploadista Cloud, and the client automatically manages token lifecycle. See the Uploadista Cloud Integration Guide for a complete setup walkthrough.
const client = createUploadistaClient({ storageId: "my-storage", chunkSize: 1024 * 1024,
auth: { mode: 'uploadista-cloud', authServerUrl: '/api/auth/token', clientId: 'your-client-id' }});Server endpoint implementation (Next.js):
import { getAuthCredentials } from '@uploadista/server/auth';
export const GET = async (req, ctx) => { const { clientId } = await ctx.params; const apiKey = process.env.UPLOADISTA_API_KEY;
const response = await getAuthCredentials({ uploadistaClientId: clientId, uploadistaApiKey: apiKey, });
if (!response.isValid) { return Response.json({ error: response.error }, { status: 500 }); }
return Response.json(response.data);};Token lifecycle features:
- Caching: Tokens are cached to minimize auth server requests
- Auto-refresh: Tokens are refreshed 60 seconds before expiration
- Retry: Automatic retry with fresh token on 401 errors
Permissions
Section titled “Permissions”Uploadista supports fine-grained, permission-based access control. Permissions follow a resource:action format with wildcards and hierarchies.
Permission Format
Section titled “Permission Format”// Permission format: resource:action"upload:create" // Create new uploads"flow:execute" // Execute flows"engine:health" // Access health endpoints"engine:*" // All engine permissions (wildcard)Available Permissions
Section titled “Available Permissions”| Resource | Permission | Description |
|---|---|---|
| Upload | upload:* | All upload operations |
upload:create | Start new uploads, upload chunks | |
upload:read | View upload status, download files | |
upload:cancel | Cancel in-progress uploads | |
| Flow | flow:* | All flow operations |
flow:execute | Run flows on uploaded files | |
flow:status | Check flow job status | |
flow:cancel | Cancel running flows | |
| Engine | engine:* | All admin operations |
engine:health | Access /health endpoint | |
engine:metrics | Access /metrics endpoint | |
engine:dlq | Full Dead Letter Queue access | |
engine:dlq:read | Read DLQ entries | |
engine:dlq:write | Retry or delete DLQ entries |
Granting Permissions
Section titled “Granting Permissions”Include permissions in the AuthContext returned by your auth middleware:
authMiddleware: async (ctx) => { const user = await validateUser(ctx); if (!user) return null;
return { clientId: user.organizationId, permissions: user.isAdmin ? ['engine:*'] // Admin gets full access : ['flow:*', 'upload:*'], // Regular user gets flow and upload access };}Permission Matching
Section titled “Permission Matching”// Exact matchhasPermission(["flow:execute"], "flow:execute"); // truehasPermission(["flow:execute"], "flow:cancel"); // false
// Wildcard matchhasPermission(["flow:*"], "flow:execute"); // truehasPermission(["flow:*"], "flow:cancel"); // true
// Hierarchical matchhasPermission(["engine:dlq"], "engine:dlq:read"); // truehasPermission(["engine:dlq"], "engine:dlq:write"); // trueRole-Based Setup
Section titled “Role-Based Setup”Map your existing roles to Uploadista permissions:
import { PERMISSION_SETS } from "@uploadista/server";
const roleToPermissions = { admin: PERMISSION_SETS.ADMIN, editor: ["flow:execute", "flow:status", "upload:*"], viewer: ["flow:status", "upload:read"], uploader: ["upload:create", "upload:read"],};
// In your auth middlewareauthMiddleware: async (ctx) => { const user = await validateUser(ctx); if (!user) return null;
return { clientId: user.id, permissions: roleToPermissions[user.role] ?? [], };}No Authentication (Development)
Section titled “No Authentication (Development)”Authentication is completely optional. If you don’t provide authMiddleware (server) or auth config (client), everything works without authentication:
// Server - no auth middlewareconst uploadistaServer = await createUploadistaServer({ dataStore: fileStore({ directory: "./uploads" }), kvStore: fileKvStore({ directory: "./uploads" }), flows, adapter: expressAdapter(), // No authMiddleware - all requests allowed});
// Client - no auth configconst client = createUploadistaClient({ baseUrl: "http://localhost:3000", storageId: "local", chunkSize: 1024 * 1024, // No auth - no credentials sent});Error Responses
Section titled “Error Responses”| Status | Error | Description |
|---|---|---|
401 Unauthorized | AUTHENTICATION_REQUIRED | No authentication provided or auth middleware returned null |
403 Forbidden | PERMISSION_DENIED | Authenticated but missing required permission |
// 401 response{ "code": "AUTHENTICATION_REQUIRED", "message": "Authentication required"}
// 403 response{ "code": "PERMISSION_DENIED", "message": "Permission denied: flow:execute required"}Security Best Practices
Section titled “Security Best Practices”- Always use HTTPS in production - credentials sent over HTTP can be intercepted
- Use short-lived tokens - reduce impact if token is compromised
- Validate tokens properly - use established libraries (jose, jsonwebtoken, etc.)
- Use least-privilege permissions - grant only the permissions each user needs
- Separate admin access - keep
engine:*permissions separate from regular users - Don’t log credentials or tokens - they contain sensitive information
- Implement rate limiting - prevent brute force attacks
Troubleshooting
Section titled “Troubleshooting”Always getting 401
Section titled “Always getting 401”- Verify your auth middleware is returning a valid
AuthContextobject (notnull) - Check credentials are being sent correctly from the client
- Add logging to your auth middleware to debug
Client credentials not being sent
Section titled “Client credentials not being sent”- Verify
authconfig is passed tocreateUploadistaClient - Check
getCredentials()is returning the correct format - Ensure credentials are not empty
Getting 403 Forbidden
Section titled “Getting 403 Forbidden”- Check that
permissionsarray is included in yourAuthContext - Verify the required permission is in the user’s permissions list
- Use wildcard permissions (
flow:*,upload:*) for full access
Related Concepts
Section titled “Related Concepts”- Express Integration - Express-specific auth examples
- Hono Integration - Hono-specific auth examples
- Fastify Integration - Fastify-specific auth examples
- React Client - Client-side auth in React