How Paperclip Handles Authentication for Deployments: A Complete Technical Guide
Paperclip uses a deployment-mode-driven authentication system that supports two mutually exclusive runtime configurations—local_trusted for development and authenticated for production—with distinct paths for board operators (Better Auth sessions) and agents (JWTs or API keys), all enforcing strict company-scoped access controls.
Paperclip's deployment authentication architecture is designed to protect multi-tenant production environments while remaining frictionless for local development. In server/src/middleware/auth.ts, the core actorMiddleware determines which authentication path to execute based on the runtime DeploymentMode. This article examines the complete authentication flow, from deployment configuration through company-scoped access enforcement.
Understanding Deployment Modes
Paperclip operates in exactly one of two modes at runtime, defined by the DeploymentMode type in server/src/middleware/auth.ts (lines 88-89):
local_trusted— Development or single-tenant instances where the local board process is implicitly trustedauthenticated— Production-grade, multi-tenant instances requiring verified credentials
The actorMiddleware (lines 92-100) inspects this mode to branch authentication logic. This dual-mode design eliminates credential complexity during development without compromising production security.
Board Operator Authentication
Board operators—human users interacting with Paperclip's web interface—authenticate through different mechanisms depending on deployment mode.
Authenticated Mode: Better Auth Sessions
In production deployments, board operators use Better Auth cookie-based sessions. The middleware attempts to resolve a session via opts.resolveSession. When successful, the actor object is populated with the user's ID, email, and affiliated companies (lines 31-52). This session persists across requests through standard HTTP cookie semantics.
Local Trusted Mode: Automatic Board Actor
In local_trusted mode, no credentials are required. The middleware automatically injects a board actor with type: "board" and userId: "local-board", granting full administrative rights to the local board process. This bypass enables rapid local development without authentication infrastructure.
Agent Authentication Methods
Agents—automated execution contexts—must authenticate even in local_trusted mode. Paperclip supports two authentication methods for agents, as implemented in server/src/middleware/auth.ts (lines 90-100).
Run-Scoped JWT (Recommended)
During each heartbeat cycle, the server issues a short-lived JWT and injects it into the PAPERCLIP_API_KEY environment variable. Agents send this token in the Authorization: Bearer <jwt> header on every request. The middleware validates this JWT through verifyLocalAgentJwt (lines 98-100) before loading the associated agent record.
This approach provides automatic key rotation and tight coupling to the agent's execution lifecycle, minimizing exposure window if a token is compromised.
Long-Lived Agent API Key
For scenarios requiring persistent credentials, operators can create long-lived API keys via POST /api/agents/{agentId}/keys. The implementation follows security best practices:
- Keys are stored as SHA-256 hashes (
keyHash) in the database - Incoming bearer tokens are hashed via
hashToken(lines 53-55) before comparison - Successful matches trigger an update to
lastUsedAtfor audit purposes - Revoked keys (where
revokedAtis non-null) are rejected by the lookup logic
// Agent authentication with short-lived JWT (automatically cycled)
fetch('https://api.paperclip.ai/api/agents/me', {
headers: { Authorization: `Bearer ${process.env.PAPERCLIP_API_KEY}` },
});
// Creating a long-lived API key (board operator action)
await fetch('https://api.paperclip.ai/api/agents/123/keys', {
method: 'POST',
headers: { Authorization: `Bearer <board-session-or-key>` },
});
Company Scoping and Access Enforcement
Every entity in Paperclip belongs to a company. The authentication middleware enforces strict isolation through the actor object structure defined in server/src/middleware/auth.ts (lines 10-12) and validated by server/src/services/authorization.ts.
| Actor Type | Company Access | Validation |
|---|---|---|
| Agent | Single company only | companyId embedded in JWT or linked to API key |
| Board operator | Multiple companies (membership-based) | companyIds array from Better Auth session |
Cross-company access attempts are rejected with 403 Forbidden. Downstream services use the actor's company identifiers to scope all database queries, ensuring tenants remain isolated.
Security Mechanisms and Audit Logging
Paperclip implements multiple safeguards beyond basic token validation.
Token Hashing
All long-lived API keys are stored as SHA-256 hashes. The hashToken utility (lines 53-55) ensures that raw tokens never persist in the database, limiting blast radius if database access is compromised.
Request Integrity Auditing
The middleware validates consistency between JWT-embedded claims and request headers. Specifically, mismatches between the run_id claim and X-Paperclip-Run-Id header trigger:
- An
activityLogaudit record documenting the anomaly - A
422 Unprocessable Entityresponse rejecting the request
This detects token replay attacks where a JWT is extracted and used outside its intended execution context.
Key Revocation
API keys include a revokedAt timestamp column. The authentication lookup excludes revoked keys, enabling immediate credential invalidation without database deletion.
Complete Authentication Flow
The following sequence illustrates how actorMiddleware processes each request:
- Request arrives →
actorMiddlewareexecutes - Authorization header present?
- Yes → Token extracted and stripped
- Matches board API key → Build board session actor
- Matches hashed agent key → Load associated agent
- No match → Treat as JWT and verify signature/claims
- No → Branch by deployment mode
authenticatedmode → Attempt Better Auth session resolutionlocal_trustedmode → Inject local board actor
- Yes → Token extracted and stripped
- Actor object attached to
req.actorwithtype,userId/agentId, and company scope - Downstream services enforce company-scoped access based on
req.actor
Key Implementation Files
| File | Responsibility |
|---|---|
server/src/middleware/auth.ts |
Core middleware: token parsing, JWT validation, actor construction |
server/src/services/board-auth.ts |
Board API key lookup and session management |
server/src/agent-auth-jwt.ts |
JWT verification for run-scoped agent tokens |
packages/shared/src/adapter-auth-session.ts |
TypeScript definitions for session and actor types |
docs/api/authentication.md |
External documentation for API consumers |
Summary
- Deployment modes (
local_trustedvs.authenticated) determine whether credentials are required - Board operators authenticate via Better Auth sessions in production, or automatically in local development
- Agents use either short-lived JWTs (preferred) or long-lived hashed API keys
- Company scoping enforces tenant isolation through the actor object on every request
- Security features include SHA-256 token hashing, request integrity auditing, and key revocation capabilities
Frequently Asked Questions
What happens if I send requests to a production Paperclip instance without authentication?
In authenticated deployment mode, requests without valid credentials receive 401 Unauthorized. The middleware only proceeds without credentials in local_trusted mode, which should never be exposed to untrusted networks.
How do I migrate from local development to production authentication?
Change the DeploymentMode configuration from local_trusted to authenticated and ensure Better Auth session infrastructure is operational. No application code changes are required—the same middleware handles both modes transparently.
Why does Paperclip hash API keys instead of encrypting them?
SHA-256 hashing provides verification without recovery, meaning even with database access, an attacker cannot extract usable credentials. Encryption would allow key recovery, increasing breach impact. Hashing aligns with industry standards for API key storage.
Can an agent access resources across multiple companies?
No. Agents are strictly single-tenant. Each agent record links to exactly one companyId, and the authentication middleware enforces this scope on every request. Cross-company operations require board operator authentication with explicit membership in both companies.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →