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:
- Request arrives at
https://myapp.username.cubby.pro
- Traefik intercepts and checks for valid session cookie
- If not logged in: User is redirected to
auth.cubby.pro
- User authenticates: Magic link or password
- Redirect back: User returns to your app with session
- Headers injected: User info available in your code
Authenticated requests include these headers:
| Header | Description | Example |
|---|
X-Cubby-User-Id | User’s unique ID | clm1234567890abcdef |
X-Cubby-Username | User’s username | johndoe |
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:
| Capability | Owner | Shared User |
|---|
| Access app | Yes | Yes |
| View in dashboard | Yes | Yes |
| Modify secrets | Yes | No |
| Delete app | Yes | No |
| Share with others | Yes | No |
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.