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
- Log in โ Token saves to localStorage
- Visit
/profileโ See your details! - Wait 1 hour (or change JWT_TTL to 1 minute) โ Next API call auto-refreshes token
- 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."
Projects to explore next
Source-code projects from the KritiMyantra catalogue. Check each project's details before buying.
Flask Authentication System (Free Version) - Kritim Yantra
๐ Featuresโ Google & GitHub Authenticationโ User Registration & Loginโ Profile/Dashboard Accessโ Secure & Mi...
Flask Blog CMS with Admin Dashboard โ Python Source Code
A Flask-based blogging website with a built-in admin dashboard. Manage articles, images, categories, tags and featured p...
Laravel 12 + ReactJS Starter Kit: Add Multi-Language Support (Arabic, English, Spanish) with Blog CRUD
๐ Installation & Setup Download & Extract Get the ZIP from kritimyantra.com and extract it. Includes preconfigu...
Comments
No comments yet. Be the first to comment.
Sign in to join the discussion.