Debug School

rakesh kumar
rakesh kumar

Posted on

Centralized Authentication using SSO

Centralized Authentication – Same Domain (e.g., subdomains)
Centralized Authentication – Different Domains
When to Use What?
Use Laravel Passport or Sanctum with Session Cookies

Centralized Authentication is a system where a single backend service (identity provider) manages user login, logout, and session state for multiple applications (clients)

Real-World Use Cases
Login to Gmail also gives access to YouTube and Drive (SSO via Google)

Corporate systems where login to the portal authenticates HR, Finance, and Email apps

Micro-frontend apps sharing the same user session

Types of Centralized Authentication
We’ll now categorize the approaches by:

Domain type (same vs different)

Technology (cookies, tokens, OAuth2, etc.)

Centralized Authentication – Same Domain (e.g., subdomains)

Centralized Authentication – Different Domains

When to Use What?

Use Laravel Passport or Sanctum with Session Cookies

How it works:
All your React apps are hosted under sub-paths or subdomains (e.g. api.example.com/users, api.example.com/orders or users.example.com, orders.example.com).

Laravel issues an HTTP-only cookie after login.

The browser sends the cookie automatically with every request — no need to pass tokens manually

Goal
User logs in from React App A

Automatically authenticated in React App B

All frontend apps share same Laravel backend and authentication

Use Laravel Sanctum and session-based cookies

Step 1: Install Sanctum in Laravel
In your Laravel backend:

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Sanctum
🔹 In config/sanctum.php:

'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost:3000,localhost:3001')),
Enter fullscreen mode Exit fullscreen mode

This lets Laravel know which frontend origins are allowed to use cookies for auth.

In .env file:

SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:3000,localhost:3001
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure CORS
Edit config/cors.php to enable cookie sharing and cross-origin requests:

return [
    'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout', 'user'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['http://localhost:3000', 'http://localhost:3001'],
    'allowed_headers' => ['*'],
    'supports_credentials' => true,
];
Enter fullscreen mode Exit fullscreen mode

Step 4: Middleware Setup
In app/Http/Kernel.php, add this:

// Ensure session and cookie middleware is applied
'api' => [
    \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Enter fullscreen mode Exit fullscreen mode

This lets Sanctum check for cookies in "stateful" frontend requests.

Step 5: Create Auth Routes
Example in routes/api.php:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('/login', function (Request $request) {
    $credentials = $request->only('email', 'password');

    if (!Auth::attempt($credentials)) {
        return response()->json(['message' => 'Invalid credentials'], 401);
    }

    $request->session()->regenerate();
    return response()->json(['message' => 'Logged in successfully']);
});

Route::post('/logout', function (Request $request) {
    Auth::guard('web')->logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();

    return response()->json(['message' => 'Logged out']);
});
Enter fullscreen mode Exit fullscreen mode

🧑‍💻 Frontend (React) Setup
Do this in each React app (e.g. localhost:3000, localhost:3001):

Step 1: Install Axios

npm install axios
Enter fullscreen mode Exit fullscreen mode

Step 2: Enable withCredentials Globally

// axiosSetup.js
import axios from 'axios';

axios.defaults.baseURL = 'http://localhost:8000'; // Laravel backend
axios.defaults.withCredentials = true;

export default axios;
Enter fullscreen mode Exit fullscreen mode

Step 3: Login Flow in React

import axios from './axiosSetup';

const login = async () => {
  try {
    // Step 1: Get CSRF token
    await axios.get('/sanctum/csrf-cookie');

    // Step 2: Post login credentials
    await axios.post('/login', {
      email: 'test@example.com',
      password: 'password123'
    });

    // Step 3: Fetch authenticated user
    const user = await axios.get('/api/user');
    console.log('Logged in as:', user.data);
  } catch (err) {
    console.error('Login failed:', err.response.data);
  }
};
Enter fullscreen mode Exit fullscreen mode

Step 4: Get Authenticated User in Other React Apps

const getUser = async () => {
  try {
    const user = await axios.get('/api/user');
    console.log('Authenticated as:', user.data);
  } catch (err) {
    console.error('Not authenticated');
  }
};
Enter fullscreen mode Exit fullscreen mode

Centralized Authentication – Different Domains

composer require laravel/passport
php artisan migrate
php artisan passport:install
Enter fullscreen mode Exit fullscreen mode

In App\Models\User.php

use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}
Enter fullscreen mode Exit fullscreen mode

In AuthServiceProvider.php

use Laravel\Passport\Passport;

public function boot()
{
    $this->registerPolicies();
    Passport::routes(); // registers /oauth/authorize, /oauth/token
}
Enter fullscreen mode Exit fullscreen mode

In config/auth.php

'guards' => [
    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],
Enter fullscreen mode Exit fullscreen mode

Create OAuth2 Clients

php artisan passport:client
Enter fullscreen mode Exit fullscreen mode
App 1 → redirect: https://app1.example.com/auth/callback

App 2 → redirect: https://app2.example.com/auth/callback
Enter fullscreen mode Exit fullscreen mode

Save:

CLIENT_ID

CLIENT_SECRET
Enter fullscreen mode Exit fullscreen mode

Login Controller (PassportLoginController.php)

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class PassportLoginController extends Controller
{
    public function showLoginForm()
    {
        return view('auth.login');
    }

    public function login(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();
            return redirect()->intended();
        }

        return back()->withErrors([
            'email' => 'Invalid credentials',
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Routes (routes/web.php)

use App\Http\Controllers\PassportLoginController;

Route::get('/login', [PassportLoginController::class, 'showLoginForm'])->name('login');
Route::post('/login', [PassportLoginController::class, 'login']);
Enter fullscreen mode Exit fullscreen mode

Login View (resources/views/auth/login.blade.php)

<form method="POST" action="/login">
    @csrf
    <input type="email" name="email" placeholder="Email" required>
    <input type="password" name="password" placeholder="Password" required>
    <button type="submit">Login</button>
</form>
🧑‍💻 React App 1 & 2 Code (Same Structure)
Enter fullscreen mode Exit fullscreen mode

Axios Client (axiosClient.js)

import axios from 'axios';

const axiosClient = axios.create({
  baseURL: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json'
  }
});

export default axiosClient;
Enter fullscreen mode Exit fullscreen mode

Login.js (React)

const CLIENT_ID = 'YOUR_CLIENT_ID';
const REDIRECT_URI = 'https://app1.example.com/auth/callback'; // change for app2

export const login = () => {
  window.location.href = `https://api.example.com/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=`;
};
Enter fullscreen mode Exit fullscreen mode

AuthCallback.js

import axiosClient from './axiosClient';
import { useEffect } from 'react';

const AuthCallback = () => {
  useEffect(() => {
    const code = new URLSearchParams(window.location.search).get('code');

    if (code) {
      axiosClient.post('/oauth/token', {
        grant_type: 'authorization_code',
        client_id: 'YOUR_CLIENT_ID',
        client_secret: 'YOUR_CLIENT_SECRET',
        redirect_uri: 'https://app1.example.com/auth/callback',
        code: code
      }).then(response => {
        const { access_token } = response.data;
        localStorage.setItem('access_token', access_token);
        axiosClient.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
        window.location.href = '/dashboard'; // or wherever you want
      });
    }
  }, []);

  return <div>Logging you in...</div>;
};

export default AuthCallback;
Enter fullscreen mode Exit fullscreen mode

Authenticated API Request (Anywhere)

const token = localStorage.getItem('access_token');

axiosClient.get('/api/user', {
  headers: {
    Authorization: `Bearer ${token}`
  }
}).then(res => {
  console.log('User:', res.data);
});
Enter fullscreen mode Exit fullscreen mode

🧪 Bonus: React Router Setup

<BrowserRouter>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/login" element={<Login />} />
    <Route path="/auth/callback" element={<AuthCallback />} />
    <Route path="/dashboard" element={<Dashboard />} />
  </Routes>
</BrowserRouter>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)