Skip to content

useAuth ​

The three things a page ever does with a session: log in, log out, refresh. Each one updates the store, the abilities and the route together.

Open in demo

Import ​

ts
import { bootstrapAuth, useAuth } from '@modules/auth/composables/useAuth'

Signature ​

ts
function useAuth(): {
  login: (creds: LoginCredentials) => Promise<void>
  logout: () => Promise<void>
  refreshSession: () => Promise<boolean>
}

async function bootstrapAuth(): Promise<void>

Returns ​

MemberTypeDescription
login(creds) => Promise<void>Posts the credentials, stores the user and permissions, updates CASL, redirects, toasts a welcome. Rejects on failure so the form can show it.
logout() => Promise<void>Calls the API, then resets the store, clears abilities and returns to /auth/login.
refreshSession() => Promise<boolean>true when the session was renewed, false when it was not. Never rejects.

bootstrapAuth ​

A standalone export, not part of the returned object. It re-hydrates the session from the HTTP-only cookie and is safe to call before app.mount(), because it touches only the store and CASL — no router, no route.

Example ​

vue
<script setup lang="ts">
import { useAuth } from '@modules/auth/composables/useAuth'
import { ref } from 'vue'

const { login } = useAuth()

const email = ref('admin@example.com')
const password = ref('')
const failed = ref(false)
const busy = ref(false)

async function submit() {
  failed.value = false
  busy.value = true
  try {
    await login({ email: email.value, password: password.value })
  }
  catch {
    failed.value = true
  }
  finally {
    busy.value = false
  }
}
</script>

<template>
  <form class="space-y-3" @submit.prevent="submit()">
    <input v-model="email" type="email" autocomplete="username">
    <input v-model="password" type="password" autocomplete="current-password">
    <p v-if="failed">
      Those details did not match.
    </p>
    <button type="submit" :disabled="busy">
      Sign in
    </button>
  </form>
</template>

And in main.ts:

ts
import { bootstrapAuth } from '@modules/auth/composables/useAuth'
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

await bootstrapAuth()

app.mount('#app')

Notes ​

  • The three differ on purpose in how they fail. login lets the caller catch, because a login form has to show the error. refreshSession and bootstrapAuth swallow, because they run where there is nobody to tell. logout resets local state in a finally, so a failed server call still signs you out locally.
  • login redirects to route.query.redirect, falling back to /dashboard. That is how the router guard returns a reader to the page they asked for.
  • The router and route are resolved at the top of useAuth, before any await. Read after one, inject() warns that the component instance is no longer current.
  • bootstrapAuth's probe opts out of the refresh interceptor. A logged-out 401 is the normal answer there; letting it trigger refresh-and-redirect would reload the page and re-run the bootstrap forever.
  • bootstrapAuth also kicks off the tenant palette through useThemeProvider, unconditionally and fire-and-forget, so a slow /theme never blocks first paint.
  • Permissions land in CASL through updateAbility, which is what makes usePermissions and v-permission answer correctly straight afterwards.