Skip to main content
    Back to all articles

    Laravel Sanctum + Next.js: Secure Authentication Guide

    Web Development
    7 min read
    By BAHAJ ABDERRAZAK
    Featured image for "Laravel Sanctum + Next.js: Secure Authentication Guide"

    Laravel Sanctum + Next.js: Secure Authentication Guide

    Integrating a robust and secure authentication system is crucial for modern web applications. This guide explores how to leverage Laravel Sanctum, a lightweight authentication package for Laravel APIs, with a Next.js frontend to create a secure Single Page Application (SPA).

    Understanding Laravel Sanctum and Its Role in SPA Authentication

    Laravel Sanctum provides a simple and effective way to authenticate users accessing your Laravel API. Unlike traditional session-based authentication, Sanctum primarily relies on API tokens. This makes it ideal for SPAs, mobile applications, and any scenario where a stateless API is required. It shines as a NextAuth alternative for Laravel-based applications.

    Key benefits of using Laravel Sanctum for SPA authentication include:

    * Lightweight and easy to use: Sanctum is designed for simplicity and quick integration.

    * Stateless authentication: Uses API tokens, eliminating the need for server-side sessions.

    * CSRF protection: Protects against Cross-Site Request Forgery attacks.

    * Cookie-based authentication: Seamless integration with browser cookies for session management (optional, but crucial for Next.js).

    * Fine-grained control: Allows specifying abilities (permissions) for each token, limiting API access.

    Setting Up Sanctum in Laravel

    Before integrating Sanctum with Next.js, you need to set it up in your Laravel backend. Follow these steps:

    1. Install Sanctum:

        composer require laravel/sanctum

    2. Publish the configuration file and migrations:

        php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

    3. Run the migrations:

        php artisan migrate

    4. Configure the auth.php file:

    In your config/auth.php file, set the api guard's provider to users and driver to sanctum:

        'guards' => [
            'web' => [
                'driver' => 'session',
                'provider' => 'users',
            ],
    
            'api' => [
                'driver' => 'sanctum',
                'provider' => 'users',
            ],
        ];

    5. Apply the EnsureFrontendRequestsAreStateful middleware:

    This middleware is crucial for setting cookies correctly for your Next.js application. It's responsible for setting the XSRF-TOKEN cookie. In app/Http/Kernel.php, add the middleware to the api middleware group:

        protected $middlewareGroups = [
            'web' => [
                \App\Http\Middleware\EncryptCookies::class,
                \Illuminate\Session\Middleware\StartSession::class,
                \Illuminate\View\Middleware\ShareErrorsFromSession::class,
                \App\Http\Middleware\VerifyCsrfToken::class,
                \Illuminate\Routing\Middleware\SubstituteBindings::class,
            ],
    
            'api' => [
                \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
                'throttle:api',
                \Illuminate\Routing\Middleware\SubstituteBindings::class,
            ],
        ];

    Configuring CORS and Cookies for Next.js

    CORS (Cross-Origin Resource Sharing) and cookie configuration are essential for enabling communication between your Next.js frontend and Laravel API. Ensure that your Laravel application accepts requests from your Next.js origin and handles cookies correctly. Install the fruitcake/laravel-cors package.

    1. Install the package

        composer require fruitcake/laravel-cors

    2. Publish the configuration file

        php artisan vendor:publish --tag="cors"

    3. configure config/cors.php.

    Make sure to allow credentials

        'supports_credentials' => true,
        'allowed_origins' => ['http://localhost:3000'], // Replace with your Next.js domain
        'allowed_origins_patterns' => [],
        'allowed_methods' => ['*'],
        'allowed_headers' => ['*'],
        'exposed_headers' => [],
        'max_age' => 0,

    4. Add the Cors middleware to the global middleware in app/Http/Kernel.php:

        protected $middleware = [
            \Fruitcake\Cors\HandleCors::class,
            // ... other middleware
        ];

    Also ensure the Sanctum's stateful configuration allows your Next.js application's domain (without trailing slash):

    // config/sanctum.php
    
    'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
        '%s%s',
        parse_url(config('app.url'), PHP_URL_HOST),
        $port = config('app.port') ? ':'.$port : ''
    ))), //This can be changed to your next.js domain
    

    Implementing Login, Logout, and CSRF Protection

    Login

    The login process involves sending a POST request from your Next.js frontend to your Laravel API's login endpoint. Upon successful authentication, Laravel Sanctum will return a 204 No Content response with cookies set including XSRF-TOKEN. The XSRF-TOKEN cookie is then used in the X-XSRF-TOKEN header in subsequent requests.

    Logout

    To log out a user, send a POST request to a logout endpoint (e.g., /api/logout). On the backend, revoke the current token. This effectively invalidates the user's session.

    CSRF Protection

    Sanctum provides CSRF protection out-of-the-box using the XSRF-TOKEN cookie. The frontend needs to read the token from this cookie and include it in the X-XSRF-TOKEN header for every POST, PUT, PATCH, and DELETE request.

    Code Examples: Laravel Backend and Next.js Frontend Integration

    Laravel (routes/api.php)

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Route;
    
    Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
        return $request->user();
    });
    
    Route::post('/login', 'App\Http\Controllers\AuthController@login');
    Route::post('/logout', 'App\Http\Controllers\AuthController@logout')->middleware('auth:sanctum');

    Laravel (AuthController.php)

    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;
    
    class AuthController extends Controller
    {
        public function login(Request $request)
        {
            $credentials = $request->validate([
                'email' => 'required|email',
                'password' => 'required',
            ]);
    
            if (Auth::attempt($credentials)) {
                $request->user()->tokens()->delete();
                $token = $request->user()->createToken('auth-token');
                return response()->noContent();
            }
    
            return response(['message' => 'Invalid credentials'], 401);
        }
    
        public function logout(Request $request)
        {
            $request->user()->currentAccessToken()->delete();
            return response()->noContent();
        }
    }

    Next.js (Login Component)

    import { useState } from 'react';
    import axios from 'axios';
    import { useRouter } from 'next/router';
    import { getCsrfToken } from 'next-auth/react';
    
    const Login = () => {
      const [email, setEmail] = useState('');
      const [password, setPassword] = useState('');
      const router = useRouter();
    
      const handleSubmit = async (e) => {
        e.preventDefault();
        try {
          //We no longer need to fetch csrf token since sanctum provides it already
          await axios.get('/sanctum/csrf-cookie');
          await axios.post('/api/login', { email, password });
          router.push('/dashboard'); // Redirect on successful login
        } catch (error) {
          console.error('Login failed:', error);
        }
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
          <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
          <button type="submit">Login</button>
        </form>
      );
    };
    
    export default Login;

    Next.js (Logout Component)

    import axios from 'axios';
    import { useRouter } from 'next/router';
    
    const Logout = () => {
      const router = useRouter();
    
      const handleLogout = async () => {
        try {
          await axios.post('/api/logout');
          router.push('/'); // Redirect to home page after logout
        } catch (error) {
          console.error('Logout failed:', error);
        }
      };
    
      return (
        <button onClick={handleLogout}>Logout</button>
      );
    };
    
    export default Logout;

    Ensure you're configuring axios to send the X-XSRF-TOKEN header automatically. This can be done using an interceptor.

    // axios.js (or similar)
    import axios from 'axios';
    import Cookies from 'js-cookie';
    
    axios.defaults.baseURL = process.env.NEXT_PUBLIC_API_URL; // e.g., 'http://localhost:8000';
    axios.defaults.withCredentials = true; //Crucial for reading cookies
    
    axios.interceptors.request.use(function (config) {
        const token = Cookies.get('XSRF-TOKEN');
        config.headers['X-XSRF-TOKEN'] = token;
        return config;
    });
    
    export default axios;

    Testing Authentication Flow

    1. Register a user in your Laravel application (using a seeder or manually).

    2. Run your Next.js frontend and navigate to the login page.

    3. Enter the registered user's credentials and submit the form.

    4. Verify that you are redirected to the dashboard after successful login.

    5. Check the browser's developer tools to ensure cookies (including XSRF-TOKEN and laravel_session) are being set correctly.

    6. Click the logout button and confirm that you are redirected back to the home page.

    Best Practices and Common Issues

    * Environment Variables: Store sensitive information like API URLs in environment variables (.env).

    * Error Handling: Implement comprehensive error handling in both your frontend and backend.

    * Session Timeout: Configure appropriate session timeouts in Laravel.

    * CSRF Token Mismatch: This is a common issue. Double-check that the EnsureFrontendRequestsAreStateful middleware is applied correctly and that your Next.js application is sending the X-XSRF-TOKEN header with every request.

    * CORS Configuration: Ensure your CORS settings are correct and allow requests from your Next.js origin.

    * Cookie Domains: Pay close attention to cookie domains to ensure they are correctly configured for your application's environment.

    * Token Revocation: Revoke tokens upon logout to prevent unauthorized access.

    * Secure Cookies: Ensure cookies are marked as Secure and HttpOnly for enhanced security. Sanctum handles these by default.

    Conclusion

    Integrating Laravel Sanctum with a Next.js frontend provides a secure and efficient way to build modern SPAs. By following this guide and adhering to best practices, you can create a robust authentication system that protects your application and user data. Leveraging Laravel API capabilities with a modern React framework ensures a streamlined development process and a seamless user experience. Remember to test thoroughly and address any potential issues to ensure a secure and reliable authentication flow.

    Tags

    Tailwind CSS
    sass
    JWT
    Authentication
    API Design
    Backend Development
    Developer Productivity
    NextAuth
    PHP