Authentication

Protect User Data

Limit private rows to their owner and add application-specific user fields.

This guide assumes sign-in is working. A session tells CloudConfig who made a request; model rules decide which rows they can access.

Protect Data That Belongs to a User

Relate the model to the built-in user model. Mark the owner as server-only so a browser cannot assign or change ownership. A creation hook fills it from the session after the access check:

note: model(
  {
    title: t.text().required(),
    owner: t.relation('user').required().serverOnly(),
  },
  {
    access: {
      list: ({ user, where }) =>
        user ? where('ownerId', 'eq', user.id) : false,
      read: ({ user, where }) =>
        user ? where('ownerId', 'eq', user.id) : false,
      create: ({ user }) => user != null,
      update: ({ user, where }) =>
        user ? where('ownerId', 'eq', user.id) : false,
      delete: ({ user, where }) =>
        user ? where('ownerId', 'eq', user.id) : false,
    },
    hooks: {
      beforeCreate: ({ user }, input) => {
        if (!user) throw new Error('Sign-in required')
        return { ...input, ownerId: user.id }
      },
    },
  },
)

These rules apply to REST requests and the typed client. Requests without a session receive no private rows.

After adding the relation or user fields, generate and apply a migration with pnpm db:generate and pnpm db:migrate.

Add Fields to a User

Use userFields when access rules need application-specific information:

auth: {
  providers: { github: true },
  userFields: {
    role: t.select(['member', 'admin']).default('member').hidden(),
  },
}

The field is included in the next migration and becomes available as user.role. The .hidden() modifier prevents sign-up requests from assigning the field themselves.

Copyright © 2026