Enhance JWT Token Management and Authentication Flow (#720)
- 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
This commit is contained in:
parent
06328a47ab
commit
a5162192b1
|
|
@ -55,7 +55,8 @@ PUSHER_APP_CLUSTER=mt1
|
||||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||||
|
|
||||||
JWT_TTL=1440
|
JWT_TTL=10080
|
||||||
|
JWT_REMEMBER_TTL=43200
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
|
|
||||||
STRIPE_KEY=
|
STRIPE_KEY=
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ class ImpersonationController extends Controller
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'token' => $token,
|
'token' => $token,
|
||||||
|
'token_type' => 'bearer',
|
||||||
|
'expires_in' => auth()->getPayload()->get('exp') - time(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,15 @@ class LoginController extends Controller
|
||||||
*/
|
*/
|
||||||
protected function attemptLogin(Request $request)
|
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) {
|
if (! $token) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,19 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'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),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ const impersonate = () => {
|
||||||
opnFetch(`/moderator/impersonate/${props.user.id}`).then(async (data) => {
|
opnFetch(`/moderator/impersonate/${props.user.id}`).then(async (data) => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|
||||||
// Save the token.
|
// Save the token with its expiration time.
|
||||||
authStore.setToken(data.token, false)
|
authStore.setToken(data.token, data.expires_in)
|
||||||
|
|
||||||
// Fetch the user.
|
// Fetch the user.
|
||||||
const userData = await opnFetch('user')
|
const userData = await opnFetch('user')
|
||||||
|
|
|
||||||
|
|
@ -141,10 +141,10 @@ export default {
|
||||||
// Submit the form.
|
// Submit the form.
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.form
|
this.form
|
||||||
.post("login")
|
.post("login", { data: { remember: this.remember } })
|
||||||
.then(async (data) => {
|
.then(async (data) => {
|
||||||
// Save the token.
|
// Save the token with its expiration time
|
||||||
this.authStore.setToken(data.token)
|
this.authStore.setToken(data.token, data.expires_in)
|
||||||
|
|
||||||
const [userDataResponse, workspacesResponse] = await Promise.all([
|
const [userDataResponse, workspacesResponse] = await Promise.all([
|
||||||
opnFetch("user"),
|
opnFetch("user"),
|
||||||
|
|
|
||||||
|
|
@ -244,10 +244,10 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log in the user.
|
// Log in the user.
|
||||||
const tokenData = await this.form.post("/login")
|
const tokenData = await this.form.post("/login", { data: { remember: true } })
|
||||||
|
|
||||||
// Save the token.
|
// Save the token with its expiration time.
|
||||||
this.authStore.setToken(tokenData.token)
|
this.authStore.setToken(tokenData.token, tokenData.expires_in)
|
||||||
|
|
||||||
const userData = await opnFetch("user")
|
const userData = await opnFetch("user")
|
||||||
this.authStore.setUser(userData)
|
this.authStore.setUser(userData)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,13 @@ import { fetchAllWorkspaces } from "~/stores/workspaces.js"
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async () => {
|
export default defineNuxtRouteMiddleware(async () => {
|
||||||
const authStore = useAuthStore()
|
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) {
|
if (authStore.token && !authStore.user) {
|
||||||
const workspaceStore = useWorkspacesStore()
|
const workspaceStore = useWorkspacesStore()
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ function handleCallback() {
|
||||||
form.code = route.query.code
|
form.code = route.query.code
|
||||||
form.utm_data = $utm.value
|
form.utm_data = $utm.value
|
||||||
form.post(`/oauth/${provider}/callback`).then(async (data) => {
|
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([
|
const [userDataResponse, workspacesResponse] = await Promise.all([
|
||||||
opnFetch("user"),
|
opnFetch("user"),
|
||||||
fetchAllWorkspaces(),
|
fetchAllWorkspaces(),
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,22 @@ export const useAuthStore = defineStore("auth", {
|
||||||
},
|
},
|
||||||
// Stop admin impersonation
|
// Stop admin impersonation
|
||||||
stopImpersonating() {
|
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)
|
this.setAdminToken(null)
|
||||||
},
|
},
|
||||||
|
|
||||||
setToken(token) {
|
setToken(token, expiresIn) {
|
||||||
this.setCookie("token", token)
|
// 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
|
this.token = token
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -35,9 +45,9 @@ export const useAuthStore = defineStore("auth", {
|
||||||
this.admin_token = token
|
this.admin_token = token
|
||||||
},
|
},
|
||||||
|
|
||||||
setCookie(name, value) {
|
setCookie(name, value, options = {}) {
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
useCookie(name).value = value
|
useCookie(name, options).value = value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -49,7 +59,8 @@ export const useAuthStore = defineStore("auth", {
|
||||||
setUser(user) {
|
setUser(user) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.error("No user, logging out.")
|
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
|
this.user = user
|
||||||
|
|
@ -73,7 +84,10 @@ export const useAuthStore = defineStore("auth", {
|
||||||
opnFetch("logout", { method: "POST" }).catch(() => {})
|
opnFetch("logout", { method: "POST" }).catch(() => {})
|
||||||
|
|
||||||
this.user = null
|
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() {
|
// async fetchOauthUrl() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue