supabase
blockpassword-based-auth-nuxtjssource: ok · hit
Password Based Auth flow for Nuxt.js and Supabase
Password Based Auth flow for Nuxt.js and Supabase
npx shadcn add "https://supabase.com/ui/r/password-based-auth-nuxtjs.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · light@supabase/ssr@latest@supabase/supabase-js@latest
Registry dependencies
buttoncardinputlabel
Files12 · source cached 10/8/2026, 8:15:59 PM
registry/default/password-based-auth/nuxtjs/app/components/login-form.vue
<script setup lang="ts">
import { ref } from "vue"
import { createClient } from "@/lib/supabase/client"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
const email = ref("")
const error = ref<string | null>(null)
const success = ref(false)
const isLoading = ref(false)
const handleForgotPassword = async (e: Event) => {
e.preventDefault()
const supabase = createClient()
isLoading.value = true
error.value = null
try {
const { error: supabaseError } = await supabase.auth.resetPasswordForEmail(email.value, {
redirectTo: "http://localhost:3000/update-password",
})
if (supabaseError) throw supabaseError
success.value = true
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : "An error occurred"
} finally {
isLoading.value = false
}
}
</script>
<template>
<div class="flex flex-col gap-6">
<Card v-if="success">
<CardHeader>
<CardTitle class="text-2xl">Check Your Email</CardTitle>
<CardDescription>Password reset instructions sent</CardDescription>
</CardHeader>
<CardContent>
<p class="text-sm text-muted-foreground">
If you registered using your email and password, you will receive a password reset email.
</p>
</CardContent>
</Card>
<Card v-else>
<CardHeader>
<CardTitle class="text-2xl">Reset Your Password</CardTitle>
<CardDescription>
Type in your email and we'll send you a link to reset your password
</CardDescription>
</CardHeader>
<CardContent>
<form @submit="handleForgotPassword">
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
v-model="email"
/>
</div>
<p v-if="error" class="text-sm text-red-500">{{ error }}</p>
<Button type="submit" class="w-full" :disabled="isLoading">
{{ isLoading ? "Sending..." : "Send reset email" }}
</Button>
</div>
<div class="mt-4 text-center text-sm">
Already have an account?
<a href="/login" class="underline underline-offset-4">Sign in</a>
</div>
</form>
</CardContent>
</Card>
</div>
</template>
registry/default/password-based-auth/nuxtjs/app/components/sign-up-form.vue
<script setup lang="ts">
import { ref } from "vue"
import { createClient } from "@/lib/supabase/client"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
const email = ref("")
const password = ref("")
const repeatPassword = ref("")
const error = ref<string | null>(null)
const isLoading = ref(false)
const success = ref(false)
const handleSignUp = async () => {
const supabase = createClient()
error.value = null
if (password.value !== repeatPassword.value) {
error.value = "Passwords do not match"
return
}
isLoading.value = true
try {
const { error: supabaseError } = await supabase.auth.signUp({
email: email.value,
password: password.value,
})
if (supabaseError) throw supabaseError
success.value = true
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : "An error occurred"
} finally {
isLoading.value = false
}
}
</script>
<template>
<div class="flex flex-col gap-6">
<Card v-if="success">
<CardHeader>
<CardTitle class="text-2xl">Thank you for signing up!</CardTitle>
<CardDescription>Check your email to confirm</CardDescription>
</CardHeader>
<CardContent>
<p class="text-sm text-muted-foreground">
You've successfully signed up. Please check your email to confirm your account before
signing in.
</p>
</CardContent>
</Card>
<Card v-else>
<CardHeader>
<CardTitle class="text-2xl">Sign up</CardTitle>
<CardDescription>Create a new account</CardDescription>
</CardHeader>
<CardContent>
<form @submit.prevent="handleSignUp">
<div class="flex flex-col gap-6">
<!-- Email -->
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
v-model="email"
/>
</div>
<!-- Password -->
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
</div>
<Input
id="password"
type="password"
required
v-model="password"
/>
</div>
<!-- Repeat Password -->
<div class="grid gap-2">
<div class="flex items-center">
<Label for="repeat-password">Repeat Password</Label>
</div>
<Input
id="repeat-password"
type="password"
required
v-model="repeatPassword"
/>
</div>
<!-- Error -->
<p v-if="error" class="text-sm text-red-500">{{ error }}</p>
<!-- Submit -->
<Button type="submit" class="w-full" :disabled="isLoading">
{{ isLoading ? "Creating an account..." : "Sign up" }}
</Button>
</div>
<div class="mt-4 text-center text-sm">
Already have an account?
<a href="/login" class="underline underline-offset-4">Sign in</a>
</div>
</form>
</CardContent>
</Card>
</div>
</template>
registry/default/password-based-auth/nuxtjs/app/components/forgot-password-form.vue
<script setup lang="ts">
import { ref } from "vue"
import { createClient } from "@/lib/supabase/client"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
const email = ref("")
const error = ref<string | null>(null)
const success = ref(false)
const isLoading = ref(false)
const handleForgotPassword = async (e: Event) => {
e.preventDefault()
const supabase = createClient()
isLoading.value = true
error.value = null
try {
const { error: supabaseError } = await supabase.auth.resetPasswordForEmail(email.value, {
redirectTo: "http://localhost:3000/update-password",
})
if (supabaseError) throw supabaseError
success.value = true
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : "An error occurred"
} finally {
isLoading.value = false
}
}
</script>
<template>
<div class="flex flex-col gap-6">
<Card v-if="success">
<CardHeader>
<CardTitle class="text-2xl">Check Your Email</CardTitle>
<CardDescription>Password reset instructions sent</CardDescription>
</CardHeader>
<CardContent>
<p class="text-sm text-muted-foreground">
If you registered using your email and password, you will receive a password reset email.
</p>
</CardContent>
</Card>
<Card v-else>
<CardHeader>
<CardTitle class="text-2xl">Reset Your Password</CardTitle>
<CardDescription>
Type in your email and we'll send you a link to reset your password
</CardDescription>
</CardHeader>
<CardContent>
<form @submit="handleForgotPassword">
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
v-model="email"
/>
</div>
<p v-if="error" class="text-sm text-red-500">{{ error }}</p>
<Button type="submit" class="w-full" :disabled="isLoading">
{{ isLoading ? "Sending..." : "Send reset email" }}
</Button>
</div>
<div class="mt-4 text-center text-sm">
Already have an account?
<a href="/login" class="underline underline-offset-4">Sign in</a>
</div>
</form>
</CardContent>
</Card>
</div>
</template>
registry/default/password-based-auth/nuxtjs/app/components/update-password-form.vue
<script setup lang="ts">
import { ref } from "vue"
import { createClient } from "@/lib/supabase/client"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
const password = ref("")
const error = ref<string | null>(null)
const isLoading = ref(false)
const handleUpdatePassword = async () => {
const supabase = createClient()
isLoading.value = true
error.value = null
try {
const { error: supabaseError } = await supabase.auth.updateUser({
password: password.value,
})
if (supabaseError) throw supabaseError
// Redirect user after successful password update
location.href = "/protected"
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : "An error occurred"
} finally {
isLoading.value = false
}
}
</script>
<template>
<div class="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle class="text-2xl">Reset Your Password</CardTitle>
<CardDescription>Please enter your new password below.</CardDescription>
</CardHeader>
<CardContent>
<form @submit.prevent="handleUpdatePassword">
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="password">New password</Label>
<Input
id="password"
type="password"
placeholder="New password"
required
v-model="password"
/>
</div>
<p v-if="error" class="text-sm text-red-500">{{ error }}</p>
<Button type="submit" class="w-full" :disabled="isLoading">
{{ isLoading ? "Saving..." : "Save new password" }}
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
</template>
registry/default/password-based-auth/nuxtjs/app/pages/auth/login.vue
<script setup lang="ts">
import LoginForm from "@/components/login-form.vue"
</script>
<template>
<div class="flex min-h-screen w-full items-center justify-center p-6 md:p-10">
<div class="w-full max-w-sm">
<LoginForm />
</div>
</div>
</template>registry/default/password-based-auth/nuxtjs/app/pages/auth/sign-up.vue
<script setup lang="ts">
import SignUpForm from "@/components/sign-up-form.vue"
</script>
<template>
<div class="flex min-h-screen w-full items-center justify-center p-6 md:p-10">
<div class="w-full max-w-sm">
<SignUpForm />
</div>
</div>
</template>
registry/default/password-based-auth/nuxtjs/app/pages/auth/forgot-password.vue
<script setup lang="ts">
import ForgotPasswordForm from "@/components/forgot-password-form.vue"
</script>
<template>
<div class="flex min-h-screen w-full items-center justify-center p-6 md:p-10">
<div class="w-full max-w-sm">
<ForgotPasswordForm />
</div>
</div>
</template>
registry/default/password-based-auth/nuxtjs/app/pages/auth/update-password.vue
<script setup lang="ts">
import UpdatePasswordForm from "@/components/update-password-form.vue"
</script>
<template>
<div class="flex min-h-screen w-full items-center justify-center p-6 md:p-10">
<div class="w-full max-w-sm">
<UpdatePasswordForm />
</div>
</div>
</template>
registry/default/password-based-auth/nuxtjs/app/pages/auth/error.vue
<script setup lang="ts">
import { useRoute } from "vue-router"
import { computed } from "vue"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
const route = useRoute()
const errorMessage = computed(() => route.query.error as string || null)
</script>
<template>
<div class="flex min-h-screen w-full items-center justify-center p-6 md:p-10">
<div class="w-full max-w-sm">
<div class="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle class="text-2xl">Sorry, something went wrong.</CardTitle>
</CardHeader>
<CardContent>
<p v-if="errorMessage" class="text-sm text-muted-foreground">
Code error: {{ errorMessage }}
</p>
<p v-else class="text-sm text-muted-foreground">
An unspecified error occurred.
</p>
</CardContent>
</Card>
</div>
</div>
</div>
</template>registry/default/password-based-auth/nuxtjs/app/pages/auth/sign-up-success.vue
<script setup lang="ts">
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
</script>
<template>
<div class="flex min-h-screen w-full items-center justify-center p-6 md:p-10">
<div class="w-full max-w-sm">
<div class="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle class="text-2xl">Thank you for signing up!</CardTitle>
<CardDescription>Check your email to confirm</CardDescription>
</CardHeader>
<CardContent>
<p class="text-sm text-muted-foreground">
You’ve successfully signed up. Please check your email to confirm your account
before signing in.
</p>
</CardContent>
</Card>
</div>
</div>
</div>
</template>
registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue
<script setup lang="ts">
import { onMounted, ref } from "vue"
import { useRouter } from "vue-router"
import { createClient } from "@/lib/supabase/client"
const router = useRouter()
const supabase = createClient()
const email = ref<string | null>(null)
const loading = ref(true)
onMounted(async () => {
const { data, error } = await supabase.auth.getUser()
if (error || !data?.user) {
router.replace("/auth/login")
return
}
email.value = data.user.email
loading.value = false
})
const handleLogout = async () => {
await supabase.auth.signOut()
router.replace("/auth/login")
}
</script>
<template>
<div class="flex h-screen w-full items-center justify-center">
<div v-if="loading" class="text-muted-foreground">Checking authentication...</div>
<div v-else class="flex items-center gap-2">
<p>
Hello <span class="font-semibold">{{ email }}</span>
</p>
<button
class="rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium hover:bg-primary/90"
@click="handleLogout"
>
Sign out
</button>
</div>
</div>
</template>
registry/default/password-based-auth/nuxtjs/server/routes/auth/confirm.get.ts
import type { EmailOtpType } from '@supabase/supabase-js'
import { defineEventHandler, getQuery, sendRedirect } from 'h3'
import { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const token_hash = query.token_hash as string | null
const type = query.type as EmailOtpType | null
const _next = query.next as string | undefined
const next = _next?.startsWith('/') ? _next : '/'
if (token_hash && type) {
const supabase = createSupabaseServerClient(event)
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return sendRedirect(event, next)
} else {
return sendRedirect(event, `/auth/error?error=${encodeURIComponent(error.message)}`)
}
}
return sendRedirect(event, `/auth/error?error=${encodeURIComponent('No token hash or type')}`)
})