Supabase Email Password Authentication: Implementation Guide

Email and password authentication remains the most widely used authentication method, providing users with full control over their credentials while offering developers straightforward implementation and broad compatibility. This practical guide demonstrates building complete email/password authentication flows including signup forms with validation, login pages, password requirements, email verification, error handling, protected routes, user session management, and logout functionality. We'll build production-ready React components with TypeScript support, implement proper error messages, handle loading states, and integrate with React Router for navigation. Unlike tutorial examples that skip crucial details, this guide covers real-world scenarios including form validation, password strength indicators, remember me functionality, and graceful error recovery. Before proceeding, ensure you've completed Supabase setup and understand authentication fundamentals.
Prerequisites and Setup
Install required packages including @supabase/supabase-js for authentication, React 18+, React Router for navigation, and optionally React Hook Form for form handling. Configure your Supabase project with email authentication enabled (Settings > Authentication > Providers > Email). Set email confirmation requirements and customize email templates. Initialize the Supabase client with your project credentials.
// Install dependencies
npm install @supabase/supabase-js react-router-dom
// Optional: Form validation
npm install react-hook-form zod
// supabaseClient.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.REACT_APP_SUPABASE_URL
const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)Signup Component
// SignUp.jsx
import { useState } from 'react'
import { supabase } from './supabaseClient'
import { useNavigate } from 'react-router-dom'
function SignUp() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [fullName, setFullName] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleSignUp(e) {
e.preventDefault()
setLoading(true)
setError(null)
// Validate password strength
if (password.length < 8) {
setError('Password must be at least 8 characters')
setLoading(false)
return
}
const { data, error } = await supabase.auth.signUp({
email: email,
password: password,
options: {
data: {
full_name: fullName
},
emailRedirectTo: `${window.location.origin}/dashboard`
}
})
setLoading(false)
if (error) {
setError(error.message)
return
}
// Show verification message
alert('Check your email to verify your account!')
navigate('/login')
}
return (
<div className="signup-container">
<h2>Create Account</h2>
<form onSubmit={handleSignUp}>
<input
type="text"
placeholder="Full Name"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
required
/>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="Password (min 8 characters)"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
{error && <p className="error">{error}</p>}
<button type="submit" disabled={loading}>
{loading ? 'Creating Account...' : 'Sign Up'}
</button>
</form>
<p>Already have an account? <a href="/login">Log In</a></p>
</div>
)
}
export default SignUpLogin Component
// Login.jsx
import { useState } from 'react'
import { supabase } from './supabaseClient'
import { useNavigate } from 'react-router-dom'
function Login() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleLogin(e) {
e.preventDefault()
setLoading(true)
setError(null)
const { data, error } = await supabase.auth.signInWithPassword({
email: email,
password: password
})
setLoading(false)
if (error) {
setError(error.message)
return
}
// Redirect to dashboard on success
navigate('/dashboard')
}
return (
<div className="login-container">
<h2>Log In</h2>
<form onSubmit={handleLogin}>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
{error && <p className="error">{error}</p>}
<button type="submit" disabled={loading}>
{loading ? 'Logging in...' : 'Log In'}
</button>
</form>
<p>
<a href="/forgot-password">Forgot Password?</a>
</p>
<p>Don't have an account? <a href="/signup">Sign Up</a></p>
</div>
)
}
export default LoginProtected Routes
// ProtectedRoute.jsx
import { useEffect, useState } from 'react'
import { Navigate } from 'react-router-dom'
import { supabase } from './supabaseClient'
function ProtectedRoute({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// Check if user is logged in
supabase.auth.getUser().then(({ data: { user } }) => {
setUser(user)
setLoading(false)
})
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null)
}
)
return () => subscription.unsubscribe()
}, [])
if (loading) {
return <div>Loading...</div>
}
if (!user) {
return <Navigate to="/login" />
}
return children
}
export default ProtectedRoute
// Usage in App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import ProtectedRoute from './ProtectedRoute'
import Dashboard from './Dashboard'
import Login from './Login'
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignUp />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
)
}Password Reset
// ForgotPassword.jsx
import { useState } from 'react'
import { supabase } from './supabaseClient'
function ForgotPassword() {
const [email, setEmail] = useState('')
const [loading, setLoading] = useState(false)
const [message, setMessage] = useState(null)
async function handleResetRequest(e) {
e.preventDefault()
setLoading(true)
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/reset-password`
})
setLoading(false)
if (error) {
setMessage({ type: 'error', text: error.message })
} else {
setMessage({
type: 'success',
text: 'Check your email for password reset link!'
})
}
}
return (
<div>
<h2>Reset Password</h2>
<form onSubmit={handleResetRequest}>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
{message && <p className={message.type}>{message.text}</p>}
<button type="submit" disabled={loading}>
{loading ? 'Sending...' : 'Send Reset Link'}
</button>
</form>
</div>
)
}
// UpdatePassword.jsx (after clicking email link)
function UpdatePassword() {
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const navigate = useNavigate()
async function handleUpdatePassword(e) {
e.preventDefault()
setLoading(true)
const { error } = await supabase.auth.updateUser({
password: password
})
setLoading(false)
if (error) {
alert(error.message)
} else {
alert('Password updated successfully!')
navigate('/login')
}
}
return (
<form onSubmit={handleUpdatePassword}>
<input
type="password"
placeholder="New Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit" disabled={loading}>
Update Password
</button>
</form>
)
}User Profile Component
// Profile.jsx
import { useEffect, useState } from 'react'
import { supabase } from './supabaseClient'
import { useNavigate } from 'react-router-dom'
function Profile() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const navigate = useNavigate()
useEffect(() => {
getUser()
}, [])
async function getUser() {
const { data: { user } } = await supabase.auth.getUser()
setUser(user)
setLoading(false)
}
async function handleLogout() {
await supabase.auth.signOut()
navigate('/login')
}
if (loading) return <div>Loading...</div>
return (
<div>
<h2>Profile</h2>
<p><strong>Email:</strong> {user.email}</p>
<p><strong>User ID:</strong> {user.id}</p>
<p><strong>Name:</strong> {user.user_metadata?.full_name}</p>
<p><strong>Created:</strong> {new Date(user.created_at).toLocaleDateString()}</p>
<button onClick={handleLogout}>Log Out</button>
</div>
)
}
export default ProfileProduction Best Practices
- Validate Input: Check email format and password strength on client and server
- Show Clear Errors: Display user-friendly error messages for authentication failures
- Implement Loading States: Disable buttons and show loaders during async operations
- Require Email Verification: Enable email confirmation in Supabase dashboard settings
- Add RLS Policies: Protect user data with Row Level Security
- Handle Session Expiry: Listen to auth state changes and redirect when sessions expire
Next Steps
- Add User Profiles: Create database tables for extended user profiles with foreign keys
- Implement RLS: Secure user data with Row Level Security policies
- Add Profile Images: Upload avatars with Supabase Storage
- Build Complete App: Integrate with Next.js for production-ready authentication
Conclusion
Email and password authentication provides a solid foundation for user management in React applications with Supabase handling the complex security details like password hashing, JWT tokens, and session management. By building reusable components for signup, login, password reset, and protected routes, you create a complete authentication system that scales from prototype to production. Always remember to validate user input, provide clear error messages, implement loading states for better UX, and protect user data with Row Level Security policies. With authentication complete, you're ready to build personalized applications where users can manage their data, preferences, and content. Continue enhancing your app with security policies, file uploads, and Next.js integration for production-ready features.
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


