---
title: "Security"
description: "What nuxt-convex keeps on the server, what it sends to the browser, and the checks that stay your job."
canonical_url: "https://nuxt-convex.onmax.me/nuxt-module/security"
---
# Security

> What nuxt-convex keeps on the server, what it sends to the browser, and the checks that stay your job.

Convex functions are public endpoints: anyone with your deployment URL can call any public query, mutation or action with any arguments. `nuxt-convex` moves tokens and data between Nuxt and Convex, but authorization always happens in your Convex functions.

## Tokens stay on the server

- The SSR token is resolved per request and used only by the server's `ConvexHttpClient`. It's never written to the payload, `useState` or `runtimeConfig`.
- The browser only receives `{ isAuthenticated }` in the `convex:auth` state.
- The browser's realtime client gets its own token from the client-side provider: the Better Auth plugin, or your `fetchToken`. That token belongs to the user in that browser, as with any Convex client.
- Without a session cookie, the Better Auth provider returns `null` without making a request.

## Check auth in every function

`requireConvexAuth` in a server route, or `auth: 'required'` on a query, only decide when to call Convex. They don't protect the function: a client can call it directly. Check the identity, and the user's right to the record, inside the function:

```ts [convex/projects.ts]
export const rename = mutation({
  args: { id: v.id('projects'), name: v.string() },
  handler: async (ctx, { id, name }) => {
    const identity = await ctx.auth.getUserIdentity()
    if (!identity)
      throw new ConvexError({ code: 'UNAUTHENTICATED' })

    const project = await ctx.db.get(id)
    if (project?.ownerId !== identity.subject)
      throw new ConvexError({ code: 'NOT_FOUND' })

    await ctx.db.patch(id, { name })
  },
})
```

Take the user from `ctx.auth`, never from arguments: a `userId` argument is whatever the caller sends.

## Storage scaffold: add ownership checks

The functions scaffolded in `convex/_hub/storage.ts` require a signed-in user, but any signed-in user can read or delete any file whose storage id they have. Record the uploader next to the storage id and check it before production. The module warns about scaffolds from older releases, which had no auth check at all. See [File Storage](https://nuxt-convex.onmax.me/nuxt-module/file-storage#review-the-generated-scaffold).

## The auth proxy

With the Better Auth provider, `nuxt-convex` proxies `<basePath>/**` (`/api/auth/**` by default) to your Convex site URL:

- `x-forwarded-host`, `x-forwarded-proto`, `x-better-auth-forwarded-host` and `x-better-auth-forwarded-proto` are overwritten with the host and protocol of the request the app received, so Better Auth builds callback URLs for your app and a client can't spoof them.
- `Origin` is passed through unchanged, so Better Auth's origin checks see the real origin. List your app's URL in `trustedOrigins` in `convex/auth.ts`.
- `x-forwarded-for` is added from the connection when missing, and passed through when present. Behind a proxy or CDN that sets it, Better Auth sees the client IP; without one, a client can set it, which matters if you rate limit by IP.
- Hop-by-hop headers are dropped. Redirects are returned to the browser, not followed.

## Caching

Responses rendered with a token get `Cache-Control: private, no-cache` and `Vary: Cookie`, so shared caches don't store them. Nitro's own `cache`, `swr` and `isr` route rules ignore those headers: keep them off routes signed-in users visit, or force those routes anonymous. See [Authenticated SSR](https://nuxt-convex.onmax.me/nuxt-module/authenticated-ssr#caching).

## Errors

- `ConvexError` data is meant for the client: it reaches the browser through the SSR payload, through query `error` refs, and in the body of server route errors. Don't put secrets or internals in it.
- The server helpers turn any other failure into a 500 with a generic message. Convex also hides the message of non-`ConvexError` errors in production deployments.

## Server routes that write

Server routes that call `serverConvexMutation` act with the user's cookie. Use non-`GET` methods for writes, and keep the session cookie `SameSite=Lax` or stricter (Better Auth's default) so other sites can't trigger them.

## Related pages

- [Authenticated SSR](https://nuxt-convex.onmax.me/nuxt-module/authenticated-ssr)
- [Better Auth](https://nuxt-convex.onmax.me/integrations/better-auth)
- [Convex: Authorization](https://docs.convex.dev/auth/functions-auth)


## Sitemap

See the full [sitemap](https://nuxt-convex.onmax.me/sitemap.md) for all pages.
