0

I am trying to use Laravel Sanctum both for API authentication and SPA authentication. In SPA authentication. Do I need to define sanctum/csrf-cookie in api.php? If so, which one would be the correct one? 1 or 2? I got 204 error if I use 1. I got 401 error if I use 2.

  1. Route::get('/sanctum/csrf-cookie', [CsrfCookieController::class, 'show']);

  2. Route::middleware('auth:sanctum')->get('/sanctum/csrf-cookie', function (Request $request) { return response()->json(['message' => 'CSRF cookie set']); });

I have uncommented the following line as well. \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,

bootstrap.js file

import axios from 'axios';
window.axios = axios;
axios.defaults.withCredentials = true;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

.env file

SANCTUM_STATEFUL_DOMAINS=http://localhost:3000
SESSION_DOMAIN=localhost 

The registration function from UserController

public function registration(Request $request) {
        $credentials = $request->validate([
            'name' => 'required',
            'email' => 'required',
            'password' => 'required'
        ]);
        
        // Check if the email is already taken
        $existinguser = User::where('email', $credentials['email'])->first();
        if($existinguser) {
            return response()->json(['error'=>'Email already in use'], 400);
        }
        
        $user = new User;
        $user->name = $credentials['name'];
        $user->email = $credentials['email'];
        $user->password = bcrypt($credentials['password']);
        $user->save();
        return response()->json(['message'=>'User created successfully']);
}

signin function in UserController

public function signin(Request $request) {
        $credentials = $request->validate([
            'email' => 'required',
            'password' => 'required'
        ]);
    
        if (Auth::attempt($credentials)) {
            $user = User::where('email', $credentials['email'])->first();
            if ($user && Hash::check($credentials['password'], $user->password)) {
                $token = $user->createToken('api-token')->plainTextToken;
                return response()->json(['user' => $user, 'token' => $token]);
            }
        }
        return response()->json(['error' => 'Email or password is incorrect'], 401);
}

signout function

public function signout(Request $request) {

        $request->user()->tokens()->delete();

        return response()->json(['message' => 'Logged Out'], 200);
}

To use the token I receive from signin function as Authorization Bearer, do I store in localStorage? I read somewhere that it is not safe and not a good practice to store the token the token in localStorage.

The following is signin.js file

if(email && password) {
      let item={email, password}
      try {
          await axios.get('http://127.0.0.1:8000/api/sanctum/csrf-cookie')
          const response = await axios.post('http://127.0.0.1:8000/api/signin', item, {
            withCredentials: true,
          })
        
        if(response.status === 200) {
          console.log(response)
          localStorage.setItem('user_info', response.data.user.name)
          localStorage.setItem('api_token', response.data.token)
          router.push('/')
        } else {
          setsigninError('Error registering', error)
          console.log('error A')
        }
      } catch(error) {
          setsigninError(error.response.data.error);
          console.log(error.response.data.error)
      }
}

Is my API authentication using Laravel Sanctum correct?

Lets say I save the token I got from the signin function in the localStorage, I need to use that token in signin request as Authorization Bearer? How can I do that if I get the token after signin request. I really don't understand this.

This is the route I am redirecting after signin.

Route::get('/', [ProductController::class, 'showproduct']);

Please help.

2
  • First of all can you plz tell us, are you receiving sanctum token in your SignIn API response? One thing I want to tell you sanctum by default provide API access from of request origin. FRONTEND_URL should need to set your Local React server URL otherwise Laravel will not allow you to access API Commented Sep 16, 2023 at 20:47
  • Yes. I received sanctum token in my Signin API response. .env file is as follow SANCTUM_STATEFUL_DOMAINS=localhost:3000 SESSION_DOMAIN=localhost APP_URL=localhost:3000 Commented Sep 17, 2023 at 10:43

1 Answer 1

0

First you have to decide whether you want to use sanctum with stateless restful API or using session.

My solution is for Restful APIs.

  • Step # 01 (if you want to use sanctum token for Restful API)

go to app/Http/Kernel.php commit or remove \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class from api middleware group, like showing below snippet.


        'api' => [
        //  \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

It is use for session base authentication but now we are using token base authentication so we don't need it anymore.

  • Step# 02 (Fix csrf token problem)

There are two ways to fix it one is, move your all APIs routes (those require csrf) from web.php to api.php.

The other way is to add that route in App\Http\Middleware\VerifyCsrfToken middleware except array, like following:

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'stripe/*',
    ];
}

For more detail you can visit following Stackoverflow thread.

  • Step# 03 (append sanctum token with axios request):

In this step you will need store token in Cookie or Localstorage (depends upon you but I'm using cookie) and append sanctum token into axios request header. For this purpose you can register axios reducer in bootstrap.js which will automatically fetch token on every request and appends it into header. Intercepter prototype code is given below:

 axios.interceptors.request.use(async (config) => {
    config.baseURL = process.env.REACT_APP_BACKEND_URL   //backend server URL
    config.headers['accept'] = 'application/json';

    let token = Cookies.get("sanctum_cookie_name")
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    return config;
  });
}
  • Step# 04 (Attach sanctum middleware to route)

Following code reflect the example of route for sanctum authentication.

Route::get('/leaderboard', [LeadController::class, 'leaderboard'])->middleware('auth:sanctum');
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for your time and explanation. I stored the token in the localstorage since storing in the cookies is complicated for me. What about sanctum spa authentication? Can you please help me with that?
@saddysad update example is also work for SPA as well. Please explain be where you got stuck one thing remember you will need to attached sanctum auth middleware on route to enable sanctum authentication on route. i.e. Route::get('/leaderboard', [LeadController::class, 'leaderboard'])->middleware('auth:sanctum');

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.