skip to Main Content

I’m having trouble with authentication where I have a Laravel backend and a Nuxt frontend. When I log in and log out successfully, everything works fine. The problem occurs when the login fails due to incorrect credentials, yet I’m still able to access the dashboard. Even though the login fails, I can see a new XSRF token being added in the browser’s Application tab. The issue is that I can access the dashboard and even the profile section, but when I try to log out, I get an error saying I’m unauthorized, which makes sense because the token wasn’t provided. However, for some reason, a session with the XSRF token is hanging around, causing me to be partially logged in even though I’ve entered incorrect login credentials. Does anyone know what could be causing this issue? I’m new to REST APIs, so I’m probably doing something very wrong.
This is my useAuth.js

export default function useAuth() {
    const user = useState('auth-user', () => null)

    const { errorBag, transformValidationErrors, resetErrorBag } = useCustomError()

    const { api, csrf } = useAxios()

    async function me() {
        try {
            const data = await api.get("/api/me")
            user.value = data.data
        } catch (e) {
            user.value = null
            console.log("error")
        }
    }

    function login(userForm) {
        resetErrorBag()
        csrf().then(() => {
            api.post("/login", userForm).then(({ data }) => {

                user.value = data.user

                $fetch('/api/set-cookie', {
                    method: "POST",
                    body: { token: data.token }
                }).then(res => {
                    navigateTo("/dashboard")
                })
            }).catch(err => {
                transformValidationErrors(err.response)
            })
        })
    }


    function logout() {
        api.post("/api/logout").then(() => {
            user.value = null
            $fetch('/api/logout', {
                method: "POST",
            }).then(res => {
                navigateTo("/")
            })

        })
    }

    function register(userForm) {

        resetErrorBag()
        csrf().then(() => {
            api.post("/register", userForm).then(({ data }) => {
                //   verify email screen
            }).catch(err => {
                transformValidationErrors(err.response)
            })
        })
    }


    return { login, logout, register, errorBag, user, me }

}

this is my useAxios.js

import axios from "axios";

export default function useAxios(){

    const rtConfig = useRuntimeConfig()
    let api = axios.create({
        baseURL: rtConfig.public.API_URL,
        headers: {
            "Content-Type": "application/json",
            "Accept": "application/json",
            // "Authorization:": "Bearer " + localStorage.getItem("token"),
        },
        withCredentials: true,
        withXSRFToken: true
    })



    async function csrf(){
        return await api.get("/sanctum/csrf-cookie")
    }

    return {
        api, csrf
    }
}

guest middleware

export default defineNuxtRouteMiddleware((to, from) => {


    const key = process.server ? 'token' : 'XSRF-TOKEN'

    const token = useCookie(key)


    if(token.value)
        return navigateTo('/')


  })
  

auth middlware

export default defineNuxtRouteMiddleware((to, from) => {


    const key = process.server ? 'token' : 'XSRF-TOKEN'

    const token = useCookie(key)


    if(!token.value)
        return navigateTo('/')



  })

So even when I defined auth middleware for dashboard page lije this

definePageMeta({
    middleware: ['auth']
  })

I can still access it, but at all I’m not authorized. I don’t understand where is mistake :/

2

Answers


  1. Laravel will almost always include XSRF-TOKEN cookie unless the request is from an exlucded path, when the route is define in web routes instead of api routes.

    You can try this with this simple route:

    Route::get('test', function () {
        return 'test';
    });
    

    web routes are stateful and api routes are stateless.

    web routes by default have this middleware handling CSRF:
    https://github.com/laravel/framework/blob/v11.23.1/src/Illuminate/Foundation/Configuration/Middleware.php#L446

    That said, XSRF-TOKEN is not used for checking authentication.

    More on here: https://laravel.com/docs/11.x/csrf


    What you can do instead is actually getting the user.

    Your process then goes like:

    • always fetch user
    • check if user can access page via auth, guest, or no middleware

    To do the always fetch user, since I assume you’re most likely want to show the user data like in a User dropdown, and not just for authentication check, is to do it via Nuxt plugin.

    Create plugins/auth.server.ts plugin:

    export default defineNuxtPlugin(async () => {
      try {
        const user = useUser()
        const response = await getUser()
        user.value = {
          id: response.id,
          name: response.name,
          email: response.email,
        }
      }
      catch (error) {
        if (error instanceof FetchError && error.response?.status === 401) {
          return
        }
    
        throw error
      }
    })
    
    

    Now user data should always be available throughout your Nuxt app.

    In your authentication check middlwares, you can now do:

    export default defineNuxtRouteMiddleware(() => {
      const user = useUser()
    
      if (user.value === null) {
        return navigateTo('/login')
      }
    })
    

    and

    export default defineNuxtRouteMiddleware(() => {
      const user = useUser()
    
      if (user.value !== null) {
        // You can set this to anything you need
        return navigateTo('/dashboard')
      }
    })
    
    
    Login or Signup to reply.
  2. It looks like there are a few key points causing the issue:

    1. XSRF Token Handling: The XSRF token (used to protect against CSRF attacks) is being set regardless of whether the login succeeds or fails. This means that even on a failed login, the token is being created and stored, allowing partial access to protected routes.

    2. Authorization Flow: Your auth middleware only checks for the presence of a token, not whether the user is actually authenticated. The XSRF token alone doesn’t represent an authenticated session.

    3. Improper Error Handling on Failed Logins: When login fails, your code still sets the XSRF token, which allows partial access.

    Suggestions to Fix:

    1. Fix Login Error Handling:
      You need to ensure that the token is not set unless the login succeeds. This will prevent setting the XSRF token on failed login attempts. Modify the login function:

      function login(userForm) {
          resetErrorBag();
          csrf().then(() => {
              api.post("/login", userForm)
              .then(({ data }) => {
                  user.value = data.user;
      
                  $fetch('/api/set-cookie', {
                      method: "POST",
                      body: { token: data.token }
                  }).then(res => {
                      navigateTo("/dashboard");
                  });
              })
              .catch(err => {
                  // Don't set the user or token on error
                  user.value = null;
                  transformValidationErrors(err.response);
              });
          });
      }
      

      This ensures that if the login fails, the user and token are not set.

    2. Check Authentication in Middleware:
      The current auth middleware only checks for the presence of a token, which can lead to false positives. Modify it to check the user’s authentication status instead of just the token:

      export default defineNuxtRouteMiddleware(async (to, from) => {
          const user = useState('auth-user');
      
          // Fetch the current user status
          if (!user.value) {
              try {
                  await me(); // Fetch user data from the backend
              } catch (e) {
                  return navigateTo('/login'); // Redirect if user is not authenticated
              }
          }
      
          // If the user is not authenticated, redirect to login
          if (!user.value) {
              return navigateTo('/login');
          }
      });
      

      This middleware will ensure that the user is fully authenticated before allowing access to protected routes.

    3. Ensure Session-Based Authentication:
      If you’re using session-based authentication, ensure that withCredentials: true is properly set on all requests and that cookies are being handled correctly by the backend.

    4. Logout Function:
      The logout process is also critical. It should invalidate the session properly, removing both the user token and any session cookies.

      function logout() {
          api.post("/api/logout").then(() => {
              user.value = null;
              $fetch('/api/logout', { method: "POST" }).then(res => {
                  navigateTo("/login");
              });
          }).catch(err => {
              // Handle any logout errors, if needed
          });
      }
      

    Debugging:

    • Network Tab: Check if any Set-Cookie headers are being sent on failed logins. They should only be set on successful login.
    • Check Server-Side Authentication: Ensure that the /api/me route is correctly protected and doesn’t return authenticated user data on failed logins.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search