Laravel 12 + Vue.js: JWT Token Refresh & Profile Page Magic

Author

Kritim Yantra

Jun 04, 2025

Laravel 12 + Vue.js: JWT Token Refresh & Profile Page Magic

πŸ”„ Why Refresh Tokens?

Imagine your JWT token is a concert ticket 🎫:

  • Access Token: Short-lived (1-2 hours), gets you in.
  • Refresh Token: Long-lived (7-30 days), gets you a new ticket when yours expires.

Without refresh logic, users get logged out constantly. Let’s fix that!


πŸ”§ Step 1: Update JWT Configuration

In .env, set token lifetimes:

JWT_TTL=60 # Access token expires in 60 minutes (for testing)  
JWT_REFRESH_TTL=10080 # Refresh token expires in 7 days (10080 minutes)

πŸ”„ Step 2: Create Refresh Token Endpoint

Add to routes/api.php:

Route::post('/refresh', [AuthController::class, 'refresh']);

Update AuthController.php:

public function refresh() {
    try {
        $newToken = auth()->refresh();
        return response()->json([
            'token' => $newToken,
            'refresh_token' => $newToken // Simplified for clarity
        ]);
    } catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
        return response()->json(['error' => 'Invalid token'], 401);
    }
}

⚑ Step 3: Auto-Refresh Tokens in Vue.js

Update your Axios setup (resources/js/app.js):

axios.interceptors.response.use(response => response, async error => {
  const originalRequest = error.config;
  
  // If token expired (401) and not already retrying
  if (error.response.status === 401 && !originalRequest._retry) {
    originalRequest._retry = true;
    
    try {
      // Attempt token refresh
      const refreshResponse = await axios.post('/api/refresh');
      const newToken = refreshResponse.data.token;
      
      // Update stored token
      localStorage.setItem('jwt_token', newToken);
      
      // Retry original request with new token
      originalRequest.headers.Authorization = `Bearer ${newToken}`;
      return axios(originalRequest);
      
    } catch (refreshError) {
      // Refresh failed - force logout
      localStorage.removeItem('jwt_token');
      window.location.href = '/login';
    }
  }
  return Promise.reject(error);
});

πŸ‘€ Step 4: Build the Profile Page

Create resources/js/components/Profile.vue:

<template>
  <div class="profile-card">
    <h1>πŸ‘‹ Hello, {{ user.name }}!</h1>
    <div class="details">
      <p><strong>πŸ“§ Email:</strong> {{ user.email }}</p>
      <p><strong>πŸ†” User ID:</strong> {{ user.id }}</p>
      <p><strong>πŸŽ‰ Member since:</strong> {{ formattedDate }}</p>
    </div>
    <button @click="logout" class="logout-btn">Log Out</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      user: {}
    };
  },
  computed: {
    formattedDate() {
      return new Date(this.user.created_at).toLocaleDateString();
    }
  },
  async mounted() {
    try {
      const response = await axios.get('/api/user');
      this.user = response.data;
    } catch (error) {
      console.error("Profile load error:", error);
    }
  },
  methods: {
    logout() {
      axios.post('/api/logout');
      localStorage.removeItem('jwt_token');
      this.$router.push('/login');
    }
  }
};
</script>

<style scoped>
.profile-card {
  max-width: 500px;
  margin: 2rem auto;
  padding: 2rem;
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.1);
  background: white;
}

.details {
  text-align: left;
  padding: 1.5rem;
  background: #f9fafb;
  border-radius: 8px;
  margin: 1.5rem 0;
}

.logout-btn {
  background: #ef4444;
  color: white;
  border: none;
  padding: 12px 24px;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
  transition: background 0.3s;
}

.logout-btn:hover {
  background: #dc2626;
}
</style>

πŸ›£οΈ Step 5: Add Vue Router (If Missing)

Install Vue Router:

npm install vue-router@4

Create resources/js/router.js:

import { createRouter, createWebHistory } from 'vue-router';
import Login from './components/Login.vue';
import Profile from './components/Profile.vue';

const routes = [
  { path: '/', redirect: '/profile' },
  { path: '/login', component: Login },
  { path: '/profile', component: Profile, meta: { requiresAuth: true } }
];

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

// Auth protection
router.beforeEach((to, from, next) => {
  const hasToken = localStorage.getItem('jwt_token');
  if (to.meta.requiresAuth && !hasToken) {
    next('/login');
  } else {
    next();
  }
});

export default router;

Update app.js:

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

createApp(App).use(router).mount('#app');

πŸ”’ Step 6: Protect Laravel Profile Route

Ensure routes/api.php has:

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

πŸš€ Step 7: Test Your Flow

  1. Log in β†’ Token saves to localStorage
  2. Visit /profile β†’ See your details!
  3. Wait 1 hour (or change JWT_TTL to 1 minute) β†’ Next API call auto-refreshes token
  4. Click "Log Out" β†’ Token destroyed

πŸŽ‰ Key Takeaways

  • Token Refresh: Silent authentication renewal
  • Profile Page: Personalized user data display
  • Security: Protected routes on frontend/backend
  • UX: Seamless session management

"Good authentication is like oxygen – users only notice it when it’s missing."


LIVE MENTORSHIP ONLY 5 SPOTS

Laravel Mastery
Coaching Class Program

KritiMyantra

Transform from beginner to Laravel expert with our personalized Coaching Class starting June 13, 2025. Limited enrollment ensures focused attention.

Daily Sessions

1-hour personalized coaching

Real Projects

Build portfolio applications

Best Practices

Industry-standard techniques

Career Support

Interview prep & job guidance

Total Investment
$200
Duration
30 hours
1h/day

Enrollment Closes In

Days
Hours
Minutes
Seconds
Spots Available 5 of 10 remaining
Next cohort starts:
June 13, 2025

Join the Program

Complete your application to secure your spot

Application Submitted!

Thank you for your interest in our Laravel mentorship program. We'll contact you within 24 hours with next steps.

What happens next?

  • Confirmation email with program details
  • WhatsApp message from our team
  • Onboarding call to discuss your goals

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts