Advanced

Team-Based Authentication

Scope data and API calls to specific teams

Team-based authentication is built into Nuxt Crouton, automatically scoping all data and API calls to the current team context.

How It Works

All generated collections are team-scoped by default:

  1. Database fields are automatically added:
    • teamId (text, required) - References the team/organization
    • owner (text, required) - References the user who owns the record
    • createdBy (text) - References the user who created the record
  2. API endpoints automatically:
    • Inject teamId and owner from the authenticated session
    • Include team membership validation via resolveTeamAndCheckMembership
    • Scope all database queries to the current user's team
  3. Client-side queries automatically:
    • Include the current team ID in API calls
    • Filter results by team
    • Invalidate when team changes
Important: Do NOT manually define teamId or owner in your schema JSON files. The generator adds these automatically, and manual definitions will cause duplicate key errors.See Schema Format - Auto-Generated Fields for details.

Requirements

Team authentication requires @fyit/crouton-auth:

pnpm add @fyit/crouton-auth
// nuxt.config.ts
export default defineNuxtConfig({
  extends: [
    '@fyit/crouton',
    '@fyit/crouton-auth'
  ]
})

Client-Side Usage

Use useTeamContext() to access the current team:

<script setup lang="ts">
const { teamId, teamSlug } = useTeamContext()

// All API calls include team context
const { items } = await useCollectionQuery('shopProducts')
// → Fetches /api/teams/[teamId]/shop-products
</script>
Query Examples: For complete useCollectionQuery patterns, see Querying Data.

Server-Side Usage

Generated API endpoints use @fyit/crouton-auth/server/utils/team for team authentication:

// Generated: server/api/teams/[team]/shop-products/index.get.ts
import { resolveTeamAndCheckMembership } from '@fyit/crouton-auth/server/utils/team'

export default defineEventHandler(async (event) => {
  const { team, user } = await resolveTeamAndCheckMembership(event)

  // Query scoped to team
  const products = await db
    .select()
    .from(shopProducts)
    .where(eq(shopProducts.teamId, team.id))

  return products
})

API Routes

Team-scoped API routes follow the pattern:

/api/teams/[team]/{layer}-{collection}/

For example:

  • /api/teams/abc123/shop-products/ - List products for team
  • /api/teams/abc123/shop-products/xyz789 - Get/update/delete specific product

Team Switching

Allow users to switch between teams with automatic query refetching:

<script setup lang="ts">
const { currentTeam, teams, switchTeam } = useTeam()

const handleSwitchTeam = async (teamId: string) => {
  await switchTeam(teamId)
  // All queries auto-refetch for new team!
}
</script>

<template>
  <USelectMenu
    v-model="currentTeam"
    :options="teams"
    @update:model-value="handleSwitchTeam"
  />
</template>

How Team Context Flows

  1. User authenticates via @fyit/crouton-auth
  2. Team is resolved from route parameter [team]
  3. Membership is validated against the user's session
  4. teamId is injected into all queries and mutations
  5. Switching teams invalidates and refetches all queries