Skip to main content

Overview

The useAuth hook provides a complete authentication interface, including login, registration, password reset, and email verification.

useAuth Hook

The authentication hook is located at src/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 (or undefined)
  • 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
}
The CSRF token is automatically included in subsequent requests through cookies.

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)
  }
}
Formik will display field-level 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
}
Usage:
import { UserType } from '@/types/User'

const Navigation = ({ user }: { user: UserType }) => {
  return <div>{user?.name}</div>
}

Best Practices

  1. Always use middleware - Specify ‘auth’ or ‘guest’ to control access
  2. Handle 422 errors - Display validation errors to users
  3. Call mutate() - After login/register to refresh user data
  4. Type your forms - Use TypeScript interfaces for form values
  5. Validate with Yup - Define schemas for client-side validation
  6. Use Formik helpers - setErrors, setSubmitting for form state
  7. Display status messages - Use AuthSessionStatus for 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

Build docs developers (and LLMs) love