Skip to main content

Vue client

WIP

Vue 3 composables - authUseSession, authUseSignIn, authUseSignOut. Reactive across components and across browser tabs.

Setup

src/main.ts
import { createApp } from 'vue'
import { createAuth } from '@gentleduck/auth/client/vue'
import App from './App.vue'

const auth = createAuth({
  baseUrl: '/auth',
})

createApp(App).use(auth).mount('#app')

authUseSession

<script setup lang="ts">
import { authUseSession } from '@gentleduck/auth/client/vue'

const { data, status } = authUseSession()
</script>

<template>
  <div v-if="status === 'loading'">Loading...</div>
  <div v-else-if="status === 'guest'">
    <SignInForm />
  </div>
  <div v-else>
    Hi, {{ data.identity.profile.displayName }}
    <SignOutButton />
  </div>
</template>

authUseSession() returns a Ref (under the hood, it's a composable backed by ref() + the vanilla client's onChange subscription). Use it in <template> directly.

authUseSignIn / authUseSignOut

<script setup lang="ts">
import { authUseSignIn, authUseSignOut } from '@gentleduck/auth/client/vue'

const signIn = authUseSignIn()
const signOut = authUseSignOut()

const email = ref('')
const password = ref('')

async function submit() {
  await signIn.mutateAsync({
    providerId: 'password',
    input: { email: email.value, password: password.value },
  })
}
</script>

<template>
  <form @submit.prevent="submit">
    <input v-model="email" type="email" required />
    <input v-model="password" type="password" required />
    <button :disabled="signIn.isLoading.value">
      {{ signIn.isLoading.value ? 'Signing in...' : 'Sign in' }}
    </button>
    <p v-if="signIn.error.value" class="text-red-500">
      {{ signIn.error.value.code }}
    </p>
  </form>
</template>

The mutate / mutateAsync / isLoading / error shape is the same as React's. The values are refs; unwrap with .value outside of templates.

SSR (Nuxt)

plugins/auth.ts
import { defineNuxtPlugin } from '#app'
import { createAuth } from '@gentleduck/auth/client/vue'

export default defineNuxtPlugin((nuxtApp) => {
  const auth = createAuth({
    baseUrl: '/api/auth',
    noInitialFetch: !!nuxtApp.payload.serverRendered,
  })
  nuxtApp.vueApp.use(auth)
})

For hydration, set noInitialFetch: true on the server side and pass the initial state via Nuxt payload:

// server middleware
const ctx = await auth.resolveSession(req)
nuxtApp.payload.authInitialState = ctx ? { session: ctx.session, identity: ctx.identity } : null

// plugin
createAuth({
  noInitialFetch: true,
  initialState: nuxtApp.payload.authInitialState,
})

Errors

<script setup>
const signIn = authUseSignIn()
</script>

<template>
  <template v-if="signIn.error.value">
    <p v-if="signIn.error.value.code === 'AUTH/INVALID_CREDENTIALS'">
      Wrong email or password
    </p>
    <p v-else-if="signIn.error.value.code === 'AUTH/MFA_REQUIRED'">
      <MfaPrompt />
    </p>
    <p v-else>
      Something went wrong: {{ signIn.error.value.code }}
    </p>
  </template>
</template>