How to make an authentication in azure with socialite in a laravel application

Good morning,

I want to do an Azure authentication with socialite and laravel (+ react client side)

I have already set up the base and it is functional (authentication with socialite, send code on the front end to then be able to obtain user token/data)

but during deployment I get this error “Proof Key for Code Exchange is required for cross-origin authorization code redemption.”

I don’t think I did this authentication correctly.

Do you have any recent documentation to recommend to me for different Azure server/client authentication?

Thank you

This is what i have done for now :

Server side – laravel php

Routes api.php

Route::get('/auth/check-token', [AuthController::class, 'checkToken']);
Route::get('/auth/azure/redirect', [AuthController::class, 'authRedirectWithAzure']);
Route::post('/auth/exchange', [AuthController::class, 'exchangeCodeForToken']);

AuthController.php

   public function checkToken(Request $request)
    {
        $token = $request->cookie('auth_token');
        $isValid = $this->authService->checkToken($token);
        return response()->json(['isAuthenticated' => $isValid]);
    }

    public function authRedirectWithAzure(Request $request)
    {
        try {
            $scopes = ['openid', 'profile', 'email', 'offline_access'];

            $url = Socialite::driver('azure')->scopes($scopes)->stateless()->redirect()->getTargetUrl();

            if ($url) {
                return response()->json($url);
            }
        } catch (Throwable $th) {
            return $this->logService->sendLog(
                $th->getMessage(),
                'Failed to connect to Microsoft Azure.',
                'connection with azure',
                $request->ip(),
                $request->url(),
                'Failed to connect to Microsoft Azure.',
                HttpStatus::INTERNAL_SERVER_ERROR
            );
        }
    }

    public function exchangeCodeForToken(Request $request)
    {
        try {
            $user = Socialite::driver('azure')->stateless()->user();

            if ($user) {
                $token = $user->token;

                $refreshToken = $user->refreshToken;
                $expiresIn = $user->expiresIn;

                $response = Http::withHeaders([
                    'Authorization' => 'Bearer ' . $token
                ])->get('https://graph.microsoft.com/v1.0/me');

                if ($response->successful()) {

                    session([
                        'microsoft_token' => $token,
                        'microsoft_refresh_token' => $refreshToken,
                        'microsoft_token_expires' => now()->addSeconds($expiresIn)->timestamp,
                    ]);

                    $cookie = cookie('auth_token', $token, 120, '/', null, false, true, false, 'Strict');


                    return response()->json([
                        'userData' => $response->json(),
                        'message' => 'Authentication successful',
                    ], 200)->withCookie($cookie);
                }
            } else {
                return $this->logService->sendLog(
                    'error',
                    'Error to process user data and set cookie response',
                    'processexchangeCodeForTokenRequest',
                    $request->ip(),
                    $request->url(),
                    "Request to exchangeCodeForToken don't success.",
                    HttpStatus::INTERNAL_SERVER_ERROR
                );
            }
        } catch (Throwable $th) {

            return $this->logService->sendLog(
                $th->getMessage(),
                'Unexpected error during Azure authentication.',
                'azure authentication',
                $request->ip(),
                $request->url(),
                'Unexpected error',
                HttpStatus::INTERNAL_SERVER_ERROR
            );
        }
    }

Client side :

Index.js

const msalInstance = new PublicClientApplication(msalConfig);

store.dispatch(getCategories());

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <Provider store={store}>
    {' '}
    <MsalProvider instance={msalInstance}>
      <ThemeProvider theme={muiTheme}>
        <AuthProvider>
          <App />
        </AuthProvider>
      </ThemeProvider>
    </MsalProvider>{' '}
  </Provider>
);

authContext.js

import React, { createContext, useContext, useState, useEffect } from 'react';
import api from 'config/api';
import { useDispatch } from 'react-redux';
import { setUserData } from 'feature/user';

const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const dispatch = useDispatch();
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const checkAuth = async () => {
    try {
      const response = await api.get('api/auth/check-token');
      if (response.status === 200) {
        setIsAuthenticated(response.data.isAuthenticated);
      } else {
        setIsAuthenticated(false);
      }
    } catch (error) {
      console.error(
        "Erreur lors de la vérification de l'authentification",
        error
      );
      setIsAuthenticated(false);
    }
  };


  useEffect(() => {
    checkAuth();
  }, []);

  const login = async (code) => {
    try {
      const response = await api.post('api/auth/exchange', { code });
      if (response.status === 200) {
         await checkAuth();

        dispatch(setUserData(response.data.userData));
      } else {
        console.error('Token exchange failed', response.status);
      }
    } catch (err) {
      console.error('Error during token exchange:', err);
    }
  };

  const logout = async () => {
    setIsAuthenticated(false);

    try { 
      const res = await api.delete('api/auth/logout');
      if (res) {
         window.location.href = res.data.logoutUrl;
      }
    } catch (error) {
      console.log(error, "erreur lors de l'appel de la déconnexion");
    }
  };

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);

Login.js

export const Login = () => {
  const { login } = useAuth();
  const navigate = useNavigate();

  const handleTokenExchange = async () => {
    try {
      const urlParams = new URLSearchParams(window.location.search);
      const code = urlParams.get('code');
      if (code) {
        await login(code);
        navigate('/home');
      } else {
        const res = await api.get('api/auth/azure/redirect');
        window.location.href = res.data;
      }
    } catch (err) {
      console.log('Error initiating Azure login:', err);
    }

  };

  useEffect(() => {
    handleTokenExchange();
  }, []);}