Session Management in Vue

User
How set session in vue when logged in
Assistant
In Vue.js, you can store session data when a user logs in using different methods depending on your setup. Since you are using **Laravel, Vue 3, and Inertia.js**, here are the best ways to handle session storage:

### 1️⃣ **Using Local Storage (Recommended for Persistence)**
Local storage retains data even if the page is refreshed.

// After successful login
localStorage.setItem('user', JSON.stringify(response.data.user));

// Retrieve session data
const user = JSON.parse(localStorage.getItem('user'));

// Remove session data on logout
localStorage.removeItem('user');

### 2️⃣ **Using Vuex or Pinia (State Management)**
If you are using **Vuex** or **Pinia**, store session data globally.

#### ✅ **Vuex (if used in your project)**

// store.js
import { createStore } from 'vuex';

export default createStore({
  state: {
    user: null
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    }
  },
  actions: {
    login({ commit }, userData) {
      commit('setUser', userData);
      localStorage.setItem('user', JSON.stringify(userData)); // Optional for persistence
    },
    logout({ commit }) {
      commit('setUser', null);
      localStorage.removeItem('user');
    }
  }
});

#### ✅ **Pinia (Alternative)**

// stores/auth.js
import { defineStore } from 'pinia';

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: JSON.parse(localStorage.getItem('user')) || null
  }),
  actions: {
    setUser(user) {
      this.user = user;
      localStorage.setItem('user', JSON.stringify(user));
    },
    logout() {
      this.user = null;
      localStorage.removeItem('user');
    }
  }
});

### 3️⃣ **Using Cookies (For Laravel Session)**
If you want to keep the session in sync with Laravel, use **cookies**.

#### ✅ **Set Session with Cookies in Laravel API**
Modify the login method in `AuthController.php`:

return response()->json([
    'user' => $user
])->cookie('session_token', $token, 60); // 60 minutes expiry

#### ✅ **Retrieve Session in Vue**
Use `js-cookie` to store session data.

import Cookies from 'js-cookie';

// Set session
Cookies.set('session_token', response.data.token, { expires: 1 }); // 1 day

// Get session
const sessionToken = Cookies.get('session_token');

// Remove session on logout
Cookies.remove('session_token');

### 🔥 **Best Approach for Your Laravel + Inertia.js Setup**
Since Inertia uses **Laravel sessions**, you can simply store authentication data globally:

#### ✅ **After Login**

// Call Laravel API for login
axios.post('/login', credentials)
  .then(response => {
    localStorage.setItem('user', JSON.stringify(response.data.user));
    Inertia.visit('/dashboard'); // Redirect to dashboard
  });

#### ✅ **On Logout**

axios.post('/logout').then(() => {
  localStorage.removeItem('user');
  Inertia.visit('/login'); // Redirect to login
});

### 🎯 **Final Recommendation**
– If you need persistence, use **LocalStorage**.
– If you want centralized state management, use **Pinia or Vuex**.
– If you rely on Laravel sessions, use **cookies**.

Which method fits your setup best? 🚀

User
for both page referesh and pina
Assistant
For handling sessions in Vue 3 with **Pinia** while also keeping the session **persistent across page refreshes**, follow these steps:

## ✅ **1. Install Pinia (if not installed)**

yarn add pinia

## ✅ **2. Create an Authentication Store (`auth.js`)**
Use **localStorage** to persist user data across refreshes.

// stores/auth.js
import { defineStore } from 'pinia';

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: JSON.parse(localStorage.getItem('user')) || null
  }),

  actions: {
    setUser(user) {
      this.user = user;
      localStorage.setItem('user', JSON.stringify(user));
    },

    logout() {
      this.user = null;
      localStorage.removeItem('user');
    }
  }
});

## ✅ **3. Register Pinia in `main.js`**
Modify your `main.js` to use Pinia:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
app.use(createPinia());
app.mount('#app');

## ✅ **4. Use the Store in Components**
### **🔹 In Login Component**

<script setup>
import { ref } from 'vue';
import { useAuthStore } from '@/stores/auth';
import axios from 'axios';
import { useRouter } from 'vue-router';

const authStore = useAuthStore();
const router = useRouter();
const email = ref('');
const password = ref('');

const login = async () => {
  try {
    const response = await axios.post('/login', { email: email.value, password: password.value });
    authStore.setUser(response.data.user);
    router.push('/dashboard'); // Redirect after login
  } catch (error) {
    console.error('Login failed', error);
  }
};
</script>

<template>
  <form @submit.prevent="login">
    <input v-model="email" type="email" placeholder="Email">
    <input v-model="password" type="password" placeholder="Password">
    <button type="submit">Login</button>
  </form>
</template>

### **🔹 In Logout Component**

<script setup>
import { useAuthStore } from '@/stores/auth';
import axios from 'axios';
import { useRouter } from 'vue-router';

const authStore = useAuthStore();
const router = useRouter();

const logout = async () => {
  try {
    await axios.post('/logout');
    authStore.logout();
    router.push('/login'); // Redirect to login
  } catch (error) {
    console.error('Logout failed', error);
  }
};
</script>

<template>
  <button @click="logout">Logout</button>
</template>

### **🔹 In Navbar (Show Login/Logout Based on Session)**

<script setup>
import { useAuthStore } from '@/stores/auth';

const authStore = useAuthStore();
</script>

<template>
  <nav>
    <span v-if="authStore.user">Welcome, {{ authStore.user.name }}</span>
    <router-link v-if="!authStore.user" to="/login">Login</router-link>
    <button v-if="authStore.user" @click="authStore.logout">Logout</button>
  </nav>
</template>

## 🎯 **How It Works**
– When the user logs in, `authStore.setUser(user)` saves data in **Pinia** and **localStorage**.
– On refresh, `state: () => ({ user: JSON.parse(localStorage.getItem('user')) || null })` loads data from **localStorage**.
– On logout, `authStore.logout()` clears **Pinia** state and **localStorage**.

### 🔥 **Final Thoughts**
This setup ensures:
✔️ **Session persists across refreshes**
✔️ **State is managed centrally with Pinia**
✔️ **Easy access to user data across components**

Would you like to add automatic token handling for API authentication? 🚀

User
also add token along with user
Assistant
To store both the **user data** and the **token** in Pinia while keeping it persistent across page refreshes, follow this updated approach.

## ✅ **1. Update the `auth.js` Store**
Store both **user data** and **token** in **localStorage**.

// stores/auth.js
import { defineStore } from 'pinia';

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: JSON.parse(localStorage.getItem('user')) || null,
    token: localStorage.getItem('token') || null,
  }),

  actions: {
    setUser(user, token) {
      this.user = user;
      this.token = token;
      localStorage.setItem('user', JSON.stringify(user));
      localStorage.setItem('token', token);
    },

    logout() {
      this.user = null;
      this.token = null;
      localStorage.removeItem('user');
      localStorage.removeItem('token');
    }
  }
});

## ✅ **2. Modify the Login Component**
After logging in, store both **user** and **token**.

<script setup>
import { ref } from 'vue';
import { useAuthStore } from '@/stores/auth';
import axios from 'axios';
import { useRouter } from 'vue-router';

const authStore = useAuthStore();
const router = useRouter();
const email = ref('');
const password = ref('');

const login = async () => {
  try {
    const response = await axios.post('/login', { email: email.value, password: password.value });

    // Store user & token
    authStore.setUser(response.data.user, response.data.token);

    // Redirect to dashboard
    router.push('/dashboard');
  } catch (error) {
    console.error('Login failed', error);
  }
};
</script>

<template>
  <form @submit.prevent="login">
    <input v-model="email" type="email" placeholder="Email">
    <input v-model="password" type="password" placeholder="Password">
    <button type="submit">Login</button>
  </form>
</template>

## ✅ **3. Modify the Logout Component**
Clear **user** and **token** when logging out.

<script setup>
import { useAuthStore } from '@/stores/auth';
import axios from 'axios';
import { useRouter } from 'vue-router';

const authStore = useAuthStore();
const router = useRouter();

const logout = async () => {
  try {
    await axios.post('/logout', {}, {
      headers: {
        Authorization: `Bearer ${authStore.token}` // Send token for logout request
      }
    });

    authStore.logout();
    router.push('/login');
  } catch (error) {
    console.error('Logout failed', error);
  }
};
</script>

<template>
  <button @click="logout">Logout</button>
</template>

## ✅ **4. Send Token with Every API Request**
You need to attach the token to **all API requests** for authenticated routes.

### **🔹 Create an Axios Interceptor (`axios.js`)**

import axios from 'axios';
import { useAuthStore } from '@/stores/auth';

const api = axios.create({
  baseURL: 'https://your-api-url.com', // Replace with your API URL
});

// Attach token to every request
api.interceptors.request.use((config) => {
  const authStore = useAuthStore();
  if (authStore.token) {
    config.headers.Authorization = `Bearer ${authStore.token}`;
  }
  return config;
}, (error) => {
  return Promise.reject(error);
});

export default api;

### **🔹 Use `api.js` Instead of `axios`**
Now, in all your components, **replace** `axios` with `api` to automatically include the token.

import api from '@/axios.js';

const fetchData = async () => {
  try {
    const response = await api.get('/protected-route');
    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
};

## ✅ **5. Protect Routes (Optional)**
To prevent unauthorized users from accessing protected pages, **check the token before entering routes**.

// router.js
import { createRouter, createWebHistory } from 'vue-router';
import { useAuthStore } from '@/stores/auth';
import Dashboard from '@/views/Dashboard.vue';
import Login from '@/views/Login.vue';

const routes = [
  { path: '/login', component: Login },
  { 
    path: '/dashboard', 
    component: Dashboard, 
    meta: { requiresAuth: true } 
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

router.beforeEach((to, from, next) => {
  const authStore = useAuthStore();

  if (to.meta.requiresAuth && !authStore.token) {
    next('/login'); // Redirect if not logged in
  } else {
    next();
  }
});

export default router;

## 🎯 **How It Works**
✔️ **User logs in** → Stores `user` & `token` in **Pinia + localStorage**
✔️ **Page refresh** → Reloads `user` & `token` from **localStorage**
✔️ **All API requests** automatically include **Authorization: Bearer token**
✔️ **Logout** clears session from **Pinia + localStorage**
✔️ **Routes are protected** based on `token`

## 🚀 **Final Thoughts**
– This setup ensures seamless **session persistence** even after refresh.
– API requests **automatically include the token**.
– **Unauthorized users** are redirected to `/login`.

Would you like to add **auto-refresh for tokens**? 🔄🔥

User
user: JSON.parse(localStorage.getItem('user')) || null,
type null is not assignanle to string
Assistant
You're encountering a TypeScript error because `localStorage.getItem('user')` returns a `string | null`, and `JSON.parse(null)` results in `null`, which TypeScript is not expecting when assigning it to a string type.

### ✅ **Fix in TypeScript (`auth.ts`)**
Modify the store to handle this properly:

// stores/auth.ts
import { defineStore } from 'pinia';

interface User {
  id: number;
  name: string;
  email: string;
}

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: localStorage.getItem('user') ? (JSON.parse(localStorage.getItem('user')!) as User) : null,
    token: localStorage.getItem('token') || null,
  }),

  actions: {
    setUser(user: User, token: string) {
      this.user = user;
      this.token = token;
      localStorage.setItem('user', JSON.stringify(user));
      localStorage.setItem('token', token);
    },

    logout() {
      this.user = null;
      this.token = null;
      localStorage.removeItem('user');
      localStorage.removeItem('token');
    }
  }
});

### 🔹 **Changes & Fixes**
1. **Defined an `interface User`** → Ensures `user` has a specific structure.
2. **Used `JSON.parse(localStorage.getItem('user')!)`** → `!` tells TypeScript that `getItem('user')` won't be `null` when parsing.
3. **Explicitly cast `as User`** → Helps TypeScript understand the parsed object.

### ✅ **Alternative Without `!` Operator**

user: (() => {
  const storedUser = localStorage.getItem('user');
  return storedUser ? (JSON.parse(storedUser) as User) : null;
})(),

This avoids using `!` and ensures safe handling of `null`.

### 🚀 **Final Thoughts**
✔️ **Fixes TypeScript error**
✔️ **Ensures correct type safety**
✔️ **Handles `null` properly without breaking the app**

Let me know if you need further refinements! 🚀

User
but user api.js only for protected route
Assistant
If you want to use `api.js` **only for protected routes**, while keeping public routes separate, follow this approach:

## ✅ **1. Create Two Axios Instances**
– **Public API (`publicApi.js`)** → For login, registration, etc. (No token required).
– **Protected API (`api.js`)** → For authenticated routes (Token required).

### **🔹 `publicApi.js` (For Public Routes)**
Use this for login, registration, etc.

import axios from 'axios';

const publicApi = axios.create({
  baseURL: 'https://your-api-url.com', // Replace with your API URL
});

export default publicApi;

### **🔹 `api.js` (For Protected Routes)**
Use this only for routes requiring authentication.

import axios from 'axios';
import { useAuthStore } from '@/stores/auth';

const api = axios.create({
  baseURL: 'https://your-api-url.com',
});

// Attach token to protected requests
api.interceptors.request.use(
  (config) => {
    const authStore = useAuthStore();
    if (authStore.token) {
      config.headers.Authorization = `Bearer ${authStore.token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

export default api;

## ✅ **2. Use Correct API in Components**
– **Public API** (`publicApi.js`) for login, signup.
– **Protected API** (`api.js`) for authenticated requests.

### **🔹 Login Component (Uses `publicApi.js`)**

<script setup>
import { ref } from 'vue';
import { useAuthStore } from '@/stores/auth';
import publicApi from '@/publicApi';
import { useRouter } from 'vue-router';

const authStore = useAuthStore();
const router = useRouter();
const email = ref('');
const password = ref('');

const login = async () => {
  try {
    const response = await publicApi.post('/login', {
      email: email.value,
      password: password.value
    });

    authStore.setUser(response.data.user, response.data.token);
    router.push('/dashboard');
  } catch (error) {
    console.error('Login failed', error);
  }
};
</script>

### **🔹 Fetch Protected Data (Uses `api.js`)**

<script setup>
import { ref, onMounted } from 'vue';
import api from '@/api';

const userData = ref(null);

const fetchData = async () => {
  try {
    const response = await api.get('/user-data'); // Protected route
    userData.value = response.data;
  } catch (error) {
    console.error('Error fetching user data', error);
  }
};

onMounted(fetchData);
</script>

## ✅ **Final Setup**
✔️ **Public API (`publicApi.js`)** → Used for login, register, etc.
✔️ **Protected API (`api.js`)** → Used for authenticated requests.
✔️ **Components use correct API** → Prevents unnecessary token headers.

Let me know if you need further refinements! 🚀

User
Assistant
The error **"Call to a member function `currentAccessToken()` on null"** occurs because `$request->user()` is returning `null`. This means the authenticated user is not being recognized in the `logout` method.

## ✅ **Fix 1: Ensure Middleware is Applied**
Make sure your `logout` method is protected by authentication middleware.

Modify your `routes/api.php`:

use AppHttpControllersAPIAuthAuthController;
use IlluminateSupportFacadesRoute;

Route::post('/register', [AuthController::class, 'register']);
Route::post('/verify-otp', [AuthController::class, 'verifyOtp']);
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
});

🔹 **Explanation**:
– `auth:sanctum` ensures only authenticated users can access `logout`.
– If a user is not logged in, `logout` won’t be accessible.

## ✅ **Fix 2: Use `auth()` Helper Instead of `$request->user()`**
Modify `logout()` to explicitly get the authenticated user:

use IlluminateSupportFacadesAuth;

public function logout(Request $request)
{
    $user = Auth::user();

    if (!$user) {
        return response()->json(['message' => 'User not authenticated'], 401);
    }

    $user->currentAccessToken()->delete();

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

🔹 **Why?**
– `Auth::user()` explicitly retrieves the authenticated user.
– If `null`, it returns an **unauthenticated** response instead of causing a fatal error.

## ✅ **Fix 3: Ensure Bearer Token is Sent in Logout Request**
Your logout request must include the `Authorization: Bearer ` header.

Example **Axios Request in Vue.js**:

import api from '@/api'; // Protected API instance

async function logout() {
    try {
        await api.post('/logout');
        localStorage.removeItem('user');
        localStorage.removeItem('token');
    } catch (error) {
        console.error('Logout failed:', error);
    }
}

## 🚀 **Final Steps**
✔ **Ensure `auth:sanctum` middleware is applied in `routes/api.php`**
✔ **Use `Auth::user()` instead of `$request->user()`**
✔ **Send the Bearer token in the logout request**

This should resolve the issue! Let me know if you need further clarifications. 🚀

Leave a Comment

Your email address will not be published. Required fields are marked *