Skip to main content

Overview

Quinn uses session-based authentication with HTTP-only cookies. This provides security while maintaining simplicity for client applications.

Authentication Flow

1

Login

Send your credentials to the login endpoint:
POST /api/auth/login
Content-Type: application/json
{
  "email": "[email protected]",
  "password": "your-password"
}
Response:
{
  "message": "Login successful",
  "user": {
    "id": "uuid",
    "email": "[email protected]",
    "organizationId": "uuid",
    "role": "admin"
  }
}
The server returns a Set-Cookie header with sessionId (HTTP-only, secure).
2

Make Authenticated Requests

Include the session cookie in subsequent requests:
fetch('https://your-instance.replit.app/api/customers', {
  credentials: 'include' // Automatically sends cookies
});
curl https://your-instance.replit.app/api/customers \
  -H "Cookie: sessionId=YOUR_SESSION_ID"
3

Logout

End your session:
POST /api/auth/logout
This invalidates the session and clears the cookie.

Session Management

Session Duration

  • Default: 7 days
  • Activity Extension: Sessions extend on each request
  • Absolute Maximum: 30 days

Session Storage

Sessions are stored in PostgreSQL with the following data:
  • User ID
  • Organization ID
  • Email
  • Role
  • Created/Expires timestamps
  • Impersonation state (for support)

Multi-Tenant Isolation

All API requests are automatically scoped to your organization. You can only access data belonging to your organization.
When you authenticate, your session includes:
  • userId: Your unique user ID
  • organizationId: Your organization’s ID
  • role: Your permission level
Every API endpoint validates:
  1. ✅ Session exists and is valid
  2. ✅ User belongs to organization
  3. ✅ User has required permissions for that endpoint

Role-Based Access Control

Quinn has three role levels:
RoleDescriptionPermissions
userStandard team memberRead/write access to CRM data
adminOrganization administratorAll user permissions + organization settings, user management, billing
super_adminPlatform administratorQuinn team only - cross-organization support tools

Permission Examples

// ✅ All authenticated users
GET /api/customers
POST /api/tasks

// ✅ Admin only
PUT /api/settings/organization
DELETE /api/organization/users/:userId
POST /api/organization/invite

// ✅ Super admin only  
POST /api/admin/impersonate
GET /api/admin/organizations

Code Examples

JavaScript/TypeScript

class QuinnAPI {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  async login(email: string, password: string) {
    const response = await fetch(`${this.baseUrl}/api/auth/login`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({ email, password })
    });

    if (!response.ok) {
      throw new Error('Login failed');
    }

    return response.json();
  }

  async getCustomers() {
    const response = await fetch(`${this.baseUrl}/api/customers`, {
      credentials: 'include'
    });

    return response.json();
  }

  async logout() {
    await fetch(`${this.baseUrl}/api/auth/logout`, {
      method: 'POST',
      credentials: 'include'
    });
  }
}

// Usage
const api = new QuinnAPI('https://your-instance.replit.app');
await api.login('[email protected]', 'password');
const customers = await api.getCustomers();
await api.logout();

Python

import requests

class QuinnAPI:
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()

    def login(self, email, password):
        response = self.session.post(
            f"{self.base_url}/api/auth/login",
            json={"email": email, "password": password}
        )
        response.raise_for_status()
        return response.json()

    def get_customers(self):
        response = self.session.get(f"{self.base_url}/api/customers")
        response.raise_for_status()
        return response.json()

    def logout(self):
        self.session.post(f"{self.base_url}/api/auth/logout")

# Usage
api = QuinnAPI("https://your-instance.replit.app")
api.login("[email protected]", "password")
customers = api.get_customers()
api.logout()

cURL

# Login and save cookies
curl -X POST https://your-instance.replit.app/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"your-password"}' \
  -c cookies.txt

# Use saved cookies for requests
curl https://your-instance.replit.app/api/customers \
  -b cookies.txt

# Logout
curl -X POST https://your-instance.replit.app/api/auth/logout \
  -b cookies.txt

Security Best Practices

Never send credentials over HTTP. Session cookies are marked as Secure and will only be sent over HTTPS connections.
If building a web application, implement CSRF tokens for state-changing operations (POST, PUT, DELETE).
Encourage users to change passwords every 90 days. Quinn supports password requirements via organization settings.
Never hardcode credentials. Use environment variables:
const API_BASE_URL = process.env.QUINN_API_URL;
const API_EMAIL = process.env.QUINN_EMAIL;
const API_PASSWORD = process.env.QUINN_PASSWORD;
Quinn rate-limits login attempts (10/minute per IP). Monitor for suspicious activity via admin dashboard.

Error Handling

Common Authentication Errors

Status CodeErrorCauseSolution
401UnauthorizedMissing or invalid sessionLogin again
403ForbiddenInsufficient permissionsContact admin to upgrade role
429Too Many RequestsRate limit exceededWait 60 seconds

Error Response Format

{
  "error": "Unauthorized",
  "message": "Session expired. Please login again.",
  "code": "SESSION_EXPIRED"
}

Advanced: Impersonation (Support)

Impersonation is only available to super admins for customer support purposes.
Super admins can impersonate users to debug issues:
POST /api/admin/impersonate
{
  "userId": "user-uuid",
  "organizationId": "org-uuid"
}
While impersonating:
  • All requests are scoped to the impersonated user’s organization
  • Audit log tracks all actions taken
  • Destructive actions (delete user, delete organization) are blocked
  • Session includes isImpersonating: true flag
To end impersonation:
POST /api/admin/end-impersonation

Testing Authentication

Use the API playground to test authentication:

Try it in the Playground

Test the login endpoint with your credentials

Troubleshooting

Solutions:
  • Verify session cookie is being sent (check browser DevTools → Network → Cookies)
  • Re-login to get fresh session
  • Check if session expired (7 day limit)
Verify:
  • Your role is admin or super_admin (check /api/auth/me)
  • You’re accessing the correct organization
  • Feature is enabled in organization settings

Next: Learn about Core Concepts or explore the API Reference.