Skip to content

Commit

Permalink
Enhance JWT Token Management and Authentication Flow (#720)
Browse files Browse the repository at this point in the history
- Implement extended token lifetime for "Remember Me" functionality
- Add token expiration details to authentication responses
- Update client-side token handling to support dynamic expiration
- Modify authentication middleware to handle token initialization more robustly
- Configure JWT configuration to support longer token lifetimes
  • Loading branch information
JhumanJ authored Mar 10, 2025
1 parent 06328a4 commit a516219
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 20 deletions.
3 changes: 2 additions & 1 deletion api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

JWT_TTL=1440
JWT_TTL=10080
JWT_REMEMBER_TTL=43200
JWT_SECRET=

STRIPE_KEY=
Expand Down
2 changes: 2 additions & 0 deletions api/app/Http/Controllers/Admin/ImpersonationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public function impersonate($userId)

return $this->success([
'token' => $token,
'token_type' => 'bearer',
'expires_in' => auth()->getPayload()->get('exp') - time(),
]);
}
}
10 changes: 9 additions & 1 deletion api/app/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@ public function __construct()
*/
protected function attemptLogin(Request $request)
{
$token = $this->guard()->attempt($this->credentials($request));
// Only set custom TTL if remember me is checked
$guard = $this->guard();

if ($request->remember) {
// Use the extended TTL from config for "Remember me"
$guard->setTTL(config('jwt.remember_ttl'));
}

$token = $guard->attempt($this->credentials($request));

if (! $token) {
return false;
Expand Down
14 changes: 13 additions & 1 deletion api/config/jwt.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,19 @@
|
*/

'ttl' => (int) env('JWT_TTL', 60),
'ttl' => (int) env('JWT_TTL', 60 * 24 * 7),

/*
|--------------------------------------------------------------------------
| Extended JWT time to live (Remember Me)
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token will be valid for
| when the "Remember me" option is selected. Defaults to 30 days.
|
*/

'remember_ttl' => (int) env('JWT_REMEMBER_TTL', 60 * 24 * 30),

/*
|--------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions client/components/pages/admin/ImpersonateUser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const impersonate = () => {
opnFetch(`/moderator/impersonate/${props.user.id}`).then(async (data) => {
loading.value = false
// Save the token.
authStore.setToken(data.token, false)
// Save the token with its expiration time.
authStore.setToken(data.token, data.expires_in)
// Fetch the user.
const userData = await opnFetch('user')
Expand Down
6 changes: 3 additions & 3 deletions client/components/pages/auth/components/LoginForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,10 @@ export default {
// Submit the form.
this.loading = true
this.form
.post("login")
.post("login", { data: { remember: this.remember } })
.then(async (data) => {
// Save the token.
this.authStore.setToken(data.token)
// Save the token with its expiration time
this.authStore.setToken(data.token, data.expires_in)
const [userDataResponse, workspacesResponse] = await Promise.all([
opnFetch("user"),
Expand Down
6 changes: 3 additions & 3 deletions client/components/pages/auth/components/RegisterForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,10 @@ export default {
}
// Log in the user.
const tokenData = await this.form.post("/login")
const tokenData = await this.form.post("/login", { data: { remember: true } })
// Save the token.
this.authStore.setToken(tokenData.token)
// Save the token with its expiration time.
this.authStore.setToken(tokenData.token, tokenData.expires_in)
const userData = await opnFetch("user")
this.authStore.setUser(userData)
Expand Down
8 changes: 7 additions & 1 deletion client/middleware/01.check-auth.global.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { fetchAllWorkspaces } from "~/stores/workspaces.js"

export default defineNuxtRouteMiddleware(async () => {
const authStore = useAuthStore()
authStore.initStore(useCookie("token").value, useCookie("admin_token").value)

// Get tokens from cookies
const tokenValue = useCookie("token").value
const adminTokenValue = useCookie("admin_token").value

// Initialize the store with the tokens
authStore.initStore(tokenValue, adminTokenValue)

if (authStore.token && !authStore.user) {
const workspaceStore = useWorkspacesStore()
Expand Down
2 changes: 1 addition & 1 deletion client/pages/oauth/callback.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function handleCallback() {
form.code = route.query.code
form.utm_data = $utm.value
form.post(`/oauth/${provider}/callback`).then(async (data) => {
authStore.setToken(data.token)
authStore.setToken(data.token, data.expires_in)
const [userDataResponse, workspacesResponse] = await Promise.all([
opnFetch("user"),
fetchAllWorkspaces(),
Expand Down
28 changes: 21 additions & 7 deletions client/stores/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ export const useAuthStore = defineStore("auth", {
},
// Stop admin impersonation
stopImpersonating() {
this.setToken(this.admin_token)
// When stopping impersonation, we don't have expiration info for the admin token
// Use a default long expiration (24 hours) to ensure the admin can continue working
this.setToken(this.admin_token, 60 * 60 * 24)
this.setAdminToken(null)
},

setToken(token) {
this.setCookie("token", token)
setToken(token, expiresIn) {
// Set cookie with expiration if provided
const cookieOptions = {}

if (expiresIn) {
// expiresIn is in seconds, maxAge also needs to be in seconds
cookieOptions.maxAge = expiresIn
}

this.setCookie("token", token, cookieOptions)
this.token = token
},

Expand All @@ -35,9 +45,9 @@ export const useAuthStore = defineStore("auth", {
this.admin_token = token
},

setCookie(name, value) {
setCookie(name, value, options = {}) {
if (import.meta.client) {
useCookie(name).value = value
useCookie(name, options).value = value
}
},

Expand All @@ -49,7 +59,8 @@ export const useAuthStore = defineStore("auth", {
setUser(user) {
if (!user) {
console.error("No user, logging out.")
this.setToken(null)
// When logging out due to no user, clear the token with maxAge 0
this.setToken(null, 0)
}

this.user = user
Expand All @@ -73,7 +84,10 @@ export const useAuthStore = defineStore("auth", {
opnFetch("logout", { method: "POST" }).catch(() => {})

this.user = null
this.setToken(null)

// Clear the token cookie by setting maxAge to 0
this.setCookie("token", null, { maxAge: 0 })
this.token = null
},

// async fetchOauthUrl() {
Expand Down

0 comments on commit a516219

Please sign in to comment.