Core Concepts
January 20, 2025

Authentication Guide

Learn how to implement secure authentication in your NuxtFast application using Supabase Auth

Author

Lorenzo

Author

NuxtFast comes with built-in authentication powered by Supabase Auth, providing a secure and scalable solution for user management. This guide will walk you through setting up and customizing authentication in your application.

Overview

Supabase Auth provides:

  • Multiple authentication methods (email/password, magic links, OAuth)
  • Built-in security features (rate limiting, email verification, password policies)
  • User management (profiles, roles, metadata)
  • Session management (automatic token refresh, secure storage)

Quick Setup

1. Environment Configuration

First, ensure your Supabase credentials are configured in your .env file:

SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_supabase_anon_key

Enable Google OAuth

Social authentication with providers like Google, GitHub, etc.

The most important one is definitely the Google provider. Follow the official documentation to setup the Google OAuth providers.

If your product is focused to developers, you can also setup the GitHub provider.

2. Basic Authentication Flow

The most common authentication pattern in NuxtFast:

<template>
  <div>
    <!-- Login Form -->
    <form v-if="!user" @submit.prevent="signIn">
      <div class="form-control">
        <label class="label">Email</label>
        <input v-model="email" type="email" class="input input-bordered" required />
      </div>
      <div class="form-control">
        <label class="label">Password</label>
        <input v-model="password" type="password" class="input input-bordered" required />
      </div>
      <button type="submit" class="btn btn-primary">Sign In</button>
    </form>

    <!-- Authenticated Content -->
    <div v-else>
      <p>Welcome, {{ user.email }}!</p>
      <button @click="signOut" class="btn btn-ghost">Sign Out</button>
    </div>
  </div>
</template>

<script setup>
const supabase = useSupabaseClient()
const user = useSupabaseUser()

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

const signIn = async () => {
  const { error } = await supabase.auth.signInWithPassword({
    email: email.value,
    password: password.value,
  })

  if (error) {
    console.error('Error signing in:', error)
  }
}

const signOut = async () => {
  const { error } = await supabase.auth.signOut()
  if (error) {
    console.error('Error signing out:', error)
  }
}
</script>

Authentication Methods

Email & Password

The traditional email and password authentication:

// Sign up
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
})

// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password',
})

Passwordless authentication via email:

const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://your-app.com/auth/callback',
  },
})

OAuth Providers

const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://your-app.com/auth/callback',
  },
})

User Management

Accessing User Data

Always use the useSupabaseUser() composable to access user data:

<script setup>
const user = useSupabaseUser()

// Reactive user data
watchEffect(() => {
  if (user.value) {
    console.log('User ID:', user.value.id)
    console.log('Email:', user.value.email)
    console.log('Metadata:', user.value.user_metadata)
  }
})
</script>

User Profiles

Create and manage user profiles with additional data:

// Create profile after sign up
const { data, error } = await supabase.from('profiles').insert({
  user_id: user.value.id,
  username: 'johndoe',
  full_name: 'John Doe',
  avatar_url: 'https://example.com/avatar.jpg',
})

// Update profile
const { data, error } = await supabase
  .from('profiles')
  .update({ full_name: 'John Smith' })
  .eq('user_id', user.value.id)

Route Protection

Middleware Protection

Protect pages using middleware:

// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useSupabaseUser()

  if (!user.value) {
    return navigateTo('/login')
  }
})

Use in pages:

<script setup>
definePageMeta({
  middleware: 'auth',
})
</script>

Component Protection

Protect specific components:

<template>
  <div>
    <div v-if="user">
      <!-- Protected content -->
    </div>
    <div v-else>
      <p>Please sign in to access this content.</p>
      <NuxtLink to="/login" class="btn btn-primary">Sign In</NuxtLink>
    </div>
  </div>
</template>

<script setup>
const user = useSupabaseUser()
</script>

Row Level Security (RLS)

Supabase's Row Level Security ensures data access is properly controlled:

-- Enable RLS on tables
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- Policy: Users can only see their own profile
CREATE POLICY "Users can view own profile" ON profiles
  FOR SELECT USING (auth.uid() = user_id);

-- Policy: Users can update their own profile
CREATE POLICY "Users can update own profile" ON profiles
  FOR UPDATE USING (auth.uid() = user_id);

Advanced Features

Custom Claims

Add custom claims to user tokens:

const { data, error } = await supabase.auth.updateUser({
  data: {
    role: 'admin',
    subscription_tier: 'premium',
  },
})

Session Management

Handle session events:

supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    console.log('User signed in:', session.user)
  } else if (event === 'SIGNED_OUT') {
    console.log('User signed out')
  }
})

Password Reset

Implement password reset functionality:

// Send reset email
const { data, error } = await supabase.auth.resetPasswordForEmail('user@example.com', {
  redirectTo: 'https://your-app.com/reset-password',
})

// Update password
const { data, error } = await supabase.auth.updateUser({
  password: 'new-secure-password',
})

Best Practices

1. Always Validate on Server

Never trust client-side validation alone. Implement server-side validation:

// server/api/protected-action.js
export default defineEventHandler(async event => {
  const user = await serverSupabaseUser(event)

  if (!user) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Unauthorized',
    })
  }

  // Proceed with protected action
})

2. Handle Loading States

Provide good UX during authentication:

<template>
  <div>
    <div v-if="pending" class="loading loading-spinner"></div>
    <div v-else-if="user">
      <!-- Authenticated content -->
    </div>
    <div v-else>
      <!-- Sign in form -->
    </div>
  </div>
</template>

<script setup>
const user = useSupabaseUser()
const pending = computed(() => user.value === undefined)
</script>

3. Secure Sensitive Operations

For sensitive operations, require re-authentication:

const deleteAccount = async () => {
  // Require recent authentication
  const { data, error } = await supabase.auth.reauthenticate()

  if (!error) {
    // Proceed with account deletion
    await supabase.rpc('delete_user_account')
  }
}

Troubleshooting

Common Issues

User is null after page refresh

  • Solution: The user data loads asynchronously. Use loading states.

Authentication redirects not working

  • Solution: Check your Site URL in Supabase dashboard settings.

CORS errors during development

  • Solution: Add your development URL to allowed origins in Supabase.