Skip to main content

Authentication

Every Cubby app gets built-in authentication via Cubby SSO. You don’t need to implement any auth logic.

How It Works

When users access your app:
  1. Request arrives at https://myapp.username.cubby.pro
  2. Traefik intercepts and checks for valid session cookie
  3. If not logged in: User is redirected to auth.cubby.pro
  4. User authenticates: Magic link or password
  5. Redirect back: User returns to your app with session
  6. Headers injected: User info available in your code

Request Headers

Authenticated requests include these headers:
HeaderDescriptionExample
X-Cubby-User-IdUser’s unique IDclm1234567890abcdef
X-Cubby-UsernameUser’s usernamejohndoe
These headers are set by Cubby’s infrastructure. They’re stripped from incoming requests and re-added after authentication. You cannot spoof them.

Accessing User Info

In API Routes

// app/api/profile/route.ts
import { headers } from 'next/headers'

export async function GET() {
  const h = await headers()
  const userId = h.get('X-Cubby-User-Id')
  const username = h.get('X-Cubby-Username')

  return Response.json({ userId, username })
}

In Server Components

// app/profile/page.tsx
import { headers } from 'next/headers'

export default async function ProfilePage() {
  const h = await headers()
  const username = h.get('X-Cubby-Username')

  return <h1>Hello, {username}!</h1>
}

In Server Actions

// app/actions.ts
'use server'

import { headers } from 'next/headers'

export async function createTodo(title: string) {
  const h = await headers()
  const userId = h.get('X-Cubby-User-Id')

  await prisma.todo.create({
    data: { title, userId: userId! }
  })
}

User-Scoped Data

Most apps need to scope data by user. Use the userId from headers:
// app/api/todos/route.ts
import { headers } from 'next/headers'
import { prisma } from '@/lib/db'

export async function GET() {
  const h = await headers()
  const userId = h.get('X-Cubby-User-Id')!

  const todos = await prisma.todo.findMany({
    where: { userId },
    orderBy: { createdAt: 'desc' }
  })

  return Response.json(todos)
}

export async function POST(request: Request) {
  const h = await headers()
  const userId = h.get('X-Cubby-User-Id')!
  const { title } = await request.json()

  const todo = await prisma.todo.create({
    data: { title, userId }
  })

  return Response.json(todo, { status: 201 })
}

Owner vs Shared Access

Apps can be shared with other users. Both owners and shared users are authenticated, but they have different permissions:
CapabilityOwnerShared User
Access appYesYes
View in dashboardYesYes
Modify secretsYesNo
Delete appYesNo
Share with othersYesNo
Your app code receives the same headers for both. If you need to distinguish:
// The app owner's userId is stored in your database
// Compare with the current user's userId
const isOwner = (currentUserId === app.ownerId)

What NOT To Do

Since Cubby handles authentication, you should NOT:
  • Install auth libraries (NextAuth, Clerk, Auth0, etc.)
  • Create user/session tables in your database
  • Implement login/logout pages
  • Handle password reset flows
  • Store passwords or tokens
All of this is handled by Cubby SSO.

Login Methods

Users can authenticate via:
  • Magic link: Email with one-time login link
  • Password: Traditional email + password
Users manage their login methods at auth.cubby.pro.

Session Duration

  • Inactivity timeout: 7 days without activity
  • Absolute timeout: 90 days maximum
Users are automatically logged out after either timeout.