Overview
TheuseAuth hook provides a complete authentication interface, including login, registration, password reset, and email verification.
useAuth Hook
The authentication hook is located atsrc/hooks/auth.ts:
src/hooks/auth.ts
import useSWR from 'swr'
import axios from '@/lib/axios'
import { useEffect } from 'react'
import { AxiosResponse } from 'axios'
import { useRouter, useParams } from 'next/navigation'
export const useAuth = ({
middleware,
redirectIfAuthenticated,
}: {
middleware?: string
redirectIfAuthenticated?: string
}) => {
const router = useRouter()
const params = useParams()
const {
data: user,
error,
mutate,
} = useSWR('/api/user', () =>
axios
.get('/api/user')
.then(res => res.data)
.catch(error => {
if (error.response.status !== 409) throw error
router.push('/verify-email')
}),
)
const csrf = () => axios.get('/sanctum/csrf-cookie')
const register = async (data: {
name: string
email: string
password: string
password_confirmation: string
}) => {
try {
await csrf()
await axios.post('/register', data)
mutate()
} catch (error) {
throw error
}
}
const login = async (data: {
email: string
password: string
remember: boolean
}) => {
try {
await csrf()
await axios.post('/login', data)
mutate()
} catch (error) {
throw error
}
}
const forgotPassword = async (data: {
email: string
}): Promise<AxiosResponse> => {
try {
await csrf()
return await axios.post('/forgot-password', data)
} catch (error) {
throw error
}
}
const resetPassword = async (data: {
email: string
password: string
password_confirmation: string
}) => {
try {
await csrf()
const response = await axios.post('/reset-password', {
...data,
token: params.token,
})
router.push('/login?reset=' + btoa(response.data.status))
} catch (error) {
throw error
}
}
const resendEmailVerification = async () => {
try {
return await axios.post('/email/verification-notification')
} catch (error) {
throw error
}
}
const logout = async () => {
if (!error) {
await axios.post('/logout').then(() => mutate())
}
window.location.pathname = '/login'
}
useEffect(() => {
if (middleware === 'guest' && redirectIfAuthenticated && user) {
router.push(redirectIfAuthenticated)
}
if (
window.location.pathname === '/verify-email' &&
user?.email_verified_at &&
redirectIfAuthenticated
) {
router.push(redirectIfAuthenticated)
}
if (middleware === 'auth' && error) logout()
}, [user, error, middleware, redirectIfAuthenticated])
return {
user,
register,
login,
forgotPassword,
resetPassword,
resendEmailVerification,
logout,
}
}
Hook Features
Authentication State
The hook uses SWR to fetch and cache the current user:const { data: user, error, mutate } = useSWR('/api/user', () =>
axios
.get('/api/user')
.then(res => res.data)
.catch(error => {
if (error.response.status !== 409) throw error
router.push('/verify-email')
}),
)
user- The authenticated user object (orundefined)error- Error if user fetch fails (indicates not authenticated)mutate- Function to revalidate the user data
Middleware
The hook supports two middleware types:// Protect authenticated routes
const { user } = useAuth({ middleware: 'auth' })
// Redirect authenticated users (for login/register pages)
const { login } = useAuth({
middleware: 'guest',
redirectIfAuthenticated: '/dashboard',
})
Usage Examples
Login Page
src/app/(guest)/login/page.tsx
'use client'
import { useState } from 'react'
import { useAuth } from '@/hooks/auth'
import axios, { AxiosError } from 'axios'
import { Formik, Form, Field, ErrorMessage, FormikHelpers } from 'formik'
import * as Yup from 'yup'
interface Values {
email: string
password: string
remember: boolean
}
const LoginPage = () => {
const [status, setStatus] = useState<string>('')
const { login } = useAuth({
middleware: 'guest',
redirectIfAuthenticated: '/dashboard',
})
const submitForm = async (
values: Values,
{ setSubmitting, setErrors }: FormikHelpers<Values>,
) => {
try {
await login(values)
} catch (error: Error | AxiosError | any) {
if (axios.isAxiosError(error) && error.response?.status === 422) {
setErrors(error.response?.data?.errors)
}
} finally {
setSubmitting(false)
setStatus('')
}
}
const LoginSchema = Yup.object().shape({
email: Yup.string()
.email('Invalid email')
.required('The email field is required.'),
password: Yup.string().required('The password field is required.'),
})
return (
<Formik
onSubmit={submitForm}
validationSchema={LoginSchema}
initialValues={{ email: '', password: '', remember: false }}>
<Form className="space-y-4">
<div>
<label htmlFor="email">Email</label>
<Field
id="email"
name="email"
type="email"
className="block mt-1 w-full rounded-md"
/>
<ErrorMessage
name="email"
component="span"
className="text-xs text-red-500"
/>
</div>
<div>
<label htmlFor="password">Password</label>
<Field
id="password"
name="password"
type="password"
className="block mt-1 w-full rounded-md"
/>
<ErrorMessage
name="password"
component="span"
className="text-xs text-red-500"
/>
</div>
<div>
<label htmlFor="remember">
<Field type="checkbox" name="remember" />
Remember me
</label>
</div>
<button type="submit">Login</button>
</Form>
</Formik>
)
}
export default LoginPage
Register Page
src/app/(guest)/register/page.tsx
'use client'
import { useAuth } from '@/hooks/auth'
import axios, { AxiosError } from 'axios'
import { Formik, Form, Field, ErrorMessage, FormikHelpers } from 'formik'
import * as Yup from 'yup'
interface Values {
name: string
email: string
password: string
password_confirmation: string
}
const RegisterPage = () => {
const { register } = useAuth({
middleware: 'guest',
redirectIfAuthenticated: '/dashboard',
})
const submitForm = async (
values: Values,
{ setSubmitting, setErrors }: FormikHelpers<Values>,
) => {
try {
await register(values)
} catch (error: Error | AxiosError | any) {
if (axios.isAxiosError(error) && error.response?.status === 422) {
setErrors(error.response?.data?.errors)
}
} finally {
setSubmitting(false)
}
}
const RegisterSchema = Yup.object().shape({
name: Yup.string().required('The name field is required.'),
email: Yup.string()
.email('Invalid email')
.required('The email field is required.'),
password: Yup.string().required('The password field is required.'),
password_confirmation: Yup.string()
.required('Please confirm password.')
.oneOf([Yup.ref('password')], 'Your passwords do not match.'),
})
return (
<Formik
onSubmit={submitForm}
validationSchema={RegisterSchema}
initialValues={{
name: '',
email: '',
password: '',
password_confirmation: '',
}}>
<Form className="space-y-4">
{/* Form fields */}
</Form>
</Formik>
)
}
Forgot Password
src/app/(guest)/forgot-password/page.tsx
'use client'
import { useState } from 'react'
import { useAuth } from '@/hooks/auth'
import axios, { AxiosError } from 'axios'
import { Formik, Form, Field, ErrorMessage, FormikHelpers } from 'formik'
import * as Yup from 'yup'
import AuthSessionStatus from '@/components/AuthSessionStatus'
interface FormValues {
email: string
}
const ForgotPasswordPage = () => {
const [status, setStatus] = useState<string>('')
const { forgotPassword } = useAuth({
middleware: 'guest',
redirectIfAuthenticated: '/dashboard',
})
const submitForm = async (
values: FormValues,
{ setSubmitting, setErrors }: FormikHelpers<FormValues>,
) => {
try {
const response = await forgotPassword(values)
setStatus(response.data.status)
} catch (error: Error | AxiosError | any) {
setStatus('')
if (axios.isAxiosError(error) && error.response?.status === 422) {
setErrors(error.response?.data?.errors)
}
} finally {
setSubmitting(false)
}
}
const ForgotPasswordSchema = Yup.object().shape({
email: Yup.string()
.email('Invalid email')
.required('The email field is required.'),
})
return (
<>
<div className="mb-4 text-sm text-gray-600">
Forgot your password? No problem. Just let us know your email address
and we will email you a password reset link.
</div>
<AuthSessionStatus className="mb-4" status={status} />
<Formik
onSubmit={submitForm}
validationSchema={ForgotPasswordSchema}
initialValues={{ email: '' }}>
<Form>
<Field
id="email"
name="email"
type="email"
/>
<ErrorMessage name="email" />
<button type="submit">Email Password Reset Link</button>
</Form>
</Formik>
</>
)
}
Protected Layout
src/app/(authenticated)/layout.tsx
'use client'
import { ReactNode } from 'react'
import { useAuth } from '@/hooks/auth'
import Navigation from '@/components/Layouts/Navigation'
const AppLayout = ({ children }: { children: ReactNode }) => {
const { user } = useAuth({ middleware: 'auth' })
return (
<div className="min-h-screen bg-gray-100">
<Navigation user={user} />
<main>{children}</main>
</div>
)
}
export default AppLayout
Logout
import { useAuth } from '@/hooks/auth'
const Navigation = () => {
const { logout } = useAuth({})
return (
<button onClick={logout}>
Logout
</button>
)
}
CSRF Protection
All mutation requests automatically fetch the CSRF cookie first:const csrf = () => axios.get('/sanctum/csrf-cookie')
const login = async (data) => {
await csrf() // Fetch CSRF token
await axios.post('/login', data)
mutate() // Revalidate user
}
Error Handling
Validation Errors (422)
Handle Laravel validation errors:try {
await login(values)
} catch (error: Error | AxiosError | any) {
if (axios.isAxiosError(error) && error.response?.status === 422) {
setErrors(error.response?.data?.errors)
}
}
<ErrorMessage
name="email"
component="span"
className="text-xs text-red-500"
/>
Email Verification (409)
Unverified users are automatically redirected:useSWR('/api/user', () =>
axios
.get('/api/user')
.then(res => res.data)
.catch(error => {
if (error.response.status !== 409) throw error
router.push('/verify-email') // Redirect to verify email
}),
)
User Type
src/types/User.ts
export interface UserType {
id: number
email: string
name: string
email_verified_at?: Date
created_at: Date
updated_at: Date
}
import { UserType } from '@/types/User'
const Navigation = ({ user }: { user: UserType }) => {
return <div>{user?.name}</div>
}
Best Practices
- Always use middleware - Specify ‘auth’ or ‘guest’ to control access
- Handle 422 errors - Display validation errors to users
- Call mutate() - After login/register to refresh user data
- Type your forms - Use TypeScript interfaces for form values
- Validate with Yup - Define schemas for client-side validation
- Use Formik helpers -
setErrors,setSubmittingfor form state - Display status messages - Use
AuthSessionStatusfor success messages
Common Patterns
Check if Authenticated
const { user } = useAuth({})
if (user) {
// User is authenticated
} else {
// User is not authenticated
}
Get Current User
const { user } = useAuth({ middleware: 'auth' })
<div>Welcome, {user?.name}!</div>
<div>Email: {user?.email}</div>
Conditional Rendering
const { user } = useAuth({})
return (
<>
{user ? (
<button onClick={logout}>Logout</button>
) : (
<Link href="/login">Login</Link>
)}
</>
)
Next Steps
- API Integration - Make authenticated API calls
- Components - Use auth components