skip to Main Content

I believe that I’m completely lost of reading n articles & documentations and not sure what is the right way of using Sanctum. Just a heads up what I’m trying to reach/create before talking more about the issue. I want to build an application which will communicate only via API, basically via tokens. For that my back-end will be created with Laravel and front-end with Nuxt3. I think that most of my Laravel setup is correct, but the whole workflow is incorrect, the issue is now that after log in from Nuxt application I successfully receive user data and token. However, when I’m trying to enter protected endpoint with auth:sanctum – I receive 401 (Unauthorized). However, if I manually attach the Bearer token to request – everything is fine and that makes sense, but! I read somewhere that if I use in request withCredentials: true, it will manually attach the token to headers as a Authorization Bearer. Also, question came to my head – would it be really bad if save the token in localStorage?

Here’s the Laravel part, which returns the token from log in:

 $token = Auth::user()->createToken('basic-app-token')->plainTextToken;

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

 return $this->successfullRequest($response, $message,200)->withCookie($cookie);

Maybe there is no point of attaching my own cookie? And Laravel from /sanctum/csrf-cookie will recognise the user and authorise it?

User successfully logs in and receives token:

    await axios.get('http://localhost:5000/sanctum/csrf-cookie', {
        withCredentials: true
    });


    const {data: {data: {user, token}}} = await axios.post('http://localhost:5000/api/v1/auth/login', userData, {
        headers: {
            'Content-Type': 'application/json'
        },
        withCredentials: true,
    });

The very next request to protected route looks like this:

   const request = await axios.get('http://localhost:5000/api/v1/company', {
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
        withCredentials: true
    });

    console.log(request); // Returns 401

2

Answers


  1. Chosen as BEST ANSWER

    Just noticed.. I thought I had the right .env file for SANCTUM_STATEFUL_DOMAINS... It was SANCTUM_STATEFUL_DOMAINS=localhost, had to have the port of my SPA, for the current case: SANCTUM_STATEFUL_DOMAINS=localhost:3000


  2. On the backend side you should find the user in the database and create a token for them, then return them to the frontend.

    public function signIn(Request $request): JsonResponse
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required|string'
        ]);
    
        $user = User::query()->where('email', $request->get('email'))->first();
    
        if (!$user || !Hash::check($request->get('password'), $user->password)) {
            throw new AuthorizationException(
                'The provided credentials are incorrect.',
                401
            );
        }
    
        return response()->json(['token' => $user->createToken(config('app.name'))->plainTextToken]);
    }
    

    on the front end you should store the token either in localStorage or in Cookie (this is the priority), but for the sake of simplicity, you can put it in localstorage. And then add Bearer + token to each axios request.

    const base = axios.create({
        baseURL: process.env.VUE_APP_URL,
        headers: {
            Authorization: `Bearer ${localStorage.getItem('user-token') || ''}`
        },
        responseType: 'json', // default
        responseEncoding: 'utf8', // default
    });
    base.defaults.withCredentials = true;
    
     api.post('/auth/login', {email: this.email, password: this.password})
        .then(response => response.data)
        .then(response => {
           localStorage.setItem('user-token', response.token);
        })
    
     api.get('/api/v1/company').then(response => console.log(response.data));
    

    If you want to use Cookie then you need withCredentials, in case of localStorage you don’t need to think about it.

    Sanctum Guard will take the token itself and check if it exists in the database. You can see this in vendor/laravel/sanctum/src/Guard.php

    public function __invoke(Request $request)
    
    ....
    
    
    if ($token = $request->bearerToken()) {
        $model = Sanctum::$personalAccessTokenModel;
    
        $accessToken = $model::findToken($token);
    
        if (! $this->isValidAccessToken($accessToken) ||
            ! $this->supportsTokens($accessToken->tokenable)) {
            return;
        }
    
        if (method_exists($accessToken->getConnection(), 'hasModifiedRecords') &&
            method_exists($accessToken->getConnection(), 'setRecordModificationState')) {
            tap($accessToken->getConnection()->hasModifiedRecords(), function ($hasModifiedRecords) use ($accessToken) {
                $accessToken->forceFill(['last_used_at' => now()])->save();
    
                $accessToken->getConnection()->setRecordModificationState($hasModifiedRecords);
            });
        } else {
            $accessToken->forceFill(['last_used_at' => now()])->save();
        }
    
        return $accessToken->tokenable->withAccessToken(
            $accessToken
        );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search