Playbooks

Auth with nuxt-auth-utils

Sign users in with nuxt-auth-utils and run every PostgREST query as them.

nuxt-auth-utils keeps the session in a sealed cookie and you own the sign-in route, so this is the simplest setup: mint the PostgREST token in the same route that creates the session.

Steps 1 to 4 (credentials sign-in, sign-out and querying) are what the module's playground runs. The OAuth example in step 2 follows the nuxt-auth-utils docs but isn't part of the playground.

1. Configure the session and the JWT secret

Give the session a lifetime and use the same value for the token, so they end together:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-auth-utils', 'nuxt-postgrest'],
  runtimeConfig: {
    public: { postgrestUrl: 'http://localhost:3001' },
    session: { maxAge: 60 * 60 * 24 }, // 1 day, in seconds
  },
})
.env
NUXT_SESSION_PASSWORD=a-random-string-of-at-least-32-characters
# Must equal PostgREST's jwt-secret (PGRST_JWT_SECRET)
NUXT_POSTGREST_JWT_SECRET=replace-with-a-secret-of-at-least-32-characters

2. Mint the token when the user signs in

server/api/login.post.ts
export default defineEventHandler(async (event) => {
  const user = await verifyCredentials(event) // your own check

  await setUserSession(event, { user: { id: user.id, name: user.name } })

  const token = await mintJwt(
    { role: 'authenticated', sub: user.id },
    { expiresIn: useRuntimeConfig(event).session.maxAge },
  )
  setPostgrestToken(event, token)

  return { ok: true }
})

Signing in with an OAuth provider works the same way: do it in the handler's onSuccess, next to setUserSession.

server/routes/auth/github.get.ts
export default defineOAuthGitHubEventHandler({
  async onSuccess(event, { user }) {
    await setUserSession(event, { user })
    setPostgrestToken(event, await mintJwt({ role: 'authenticated', sub: String(user.id) }, { expiresIn: useRuntimeConfig(event).session.maxAge }))
    return sendRedirect(event, '/')
  },
})

3. Clear it when the user signs out

server/api/logout.post.ts
export default defineEventHandler(async (event) => {
  await clearUserSession(event)
  clearPostgrestToken(event)
  return { ok: true }
})

4. Query as the user

Nothing special is needed. After signing in, usePostgrest() sends the token, including after client-side navigation:

app/pages/todos.vue
<script setup lang="ts">
const { loggedIn, fetch: refreshSession } = useUserSession()

const { data: todos, refresh } = await useAsyncData('todos', async () => {
  const { data, error } = await usePostgrest().from('todos').select('*')
  if (error) throw error
  return data
})

async function login() {
  await $fetch('/api/login', { method: 'POST' })
  await refreshSession()
  await refresh() // run the query again, now as the user
}
</script>

In server routes, use usePostgrestUser(event) the same way. Your RLS policies see the claims you minted, so a policy like owner = current_setting('request.jwt.claims', true)::json ->> 'sub' matches the signed-in user. See Reading claims in RLS policies.

Things to know

  • Fixed lifetime. The session and the token both last session.maxAge, and neither is extended while the user is active. If you refresh sessions yourself, mint a new token at the same time. See Expiry.
  • Revocation. Clearing the session doesn't invalidate a token a user already copied. Keep maxAge as short as your app can stand.
Copyright © 2026