Skip to content

Commit 68d1db7

Browse files
authored
feat: add auth refresh token flow if token expires (hoppscotch#5490)
1 parent 795cc82 commit 68d1db7

File tree

6 files changed

+69
-17
lines changed

6 files changed

+69
-17
lines changed

packages/hoppscotch-common/src/helpers/backend/GQLClient.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,20 @@ const createHoppClient = () => {
9696
willAuthError() {
9797
return platform.auth.willBackendHaveAuthError()
9898
},
99-
didAuthError() {
100-
return false
99+
didAuthError(error) {
100+
// Check for specific error patterns that indicate expired token
101+
return error.graphQLErrors.some(
102+
(e) =>
103+
e.message.includes("auth/fail") ||
104+
e.message.includes("jwt expired") ||
105+
e.extensions?.code === "UNAUTHENTICATED"
106+
)
101107
},
102108
async refreshAuth() {
103-
// TODO
109+
const refresh = platform.auth.refreshAuthToken
110+
// should we logout if refreshAuthToken is not defined?
111+
if (!refresh) return
112+
await refresh()
104113
},
105114
}
106115
}),

packages/hoppscotch-common/src/helpers/isValidUser.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,28 @@ export type ValidUserResponse = {
77

88
export const SESSION_EXPIRED = "Session expired. Please log in again."
99

10+
/**
11+
* Attempts to refresh the authentication token
12+
* @returns Promise resolving to a ValidUserResponse with the result
13+
*/
14+
const attemptTokenRefresh = async (): Promise<ValidUserResponse> => {
15+
if (!platform.auth.refreshAuthToken)
16+
return { valid: false, error: SESSION_EXPIRED }
17+
18+
try {
19+
const refreshSuccessful = await platform.auth.refreshAuthToken()
20+
return {
21+
valid: refreshSuccessful,
22+
error: refreshSuccessful ? "" : SESSION_EXPIRED,
23+
}
24+
} catch {
25+
return { valid: false, error: SESSION_EXPIRED }
26+
}
27+
}
28+
1029
/**
1130
* Validates user authentication and token validity by making an API call.
31+
* Refreshes tokens if they are expired.
1232
*
1333
* This function is kept separate from `handleTokenValidation()` to enable different use cases:
1434
* - Silent validation for conditional UI states (e.g., disabling components on token expiration)
@@ -23,22 +43,26 @@ export const SESSION_EXPIRED = "Session expired. Please log in again."
2343
export const isValidUser = async (): Promise<ValidUserResponse> => {
2444
const user = platform.auth.getCurrentUser()
2545

26-
if (user) {
27-
try {
28-
// If the platform provides a method to verify auth tokens, use it else assume tokens are valid (for central instance where firebase handles it)
29-
const hasValidTokens = platform.auth.verifyAuthTokens
30-
? await platform.auth.verifyAuthTokens()
31-
: true
46+
// If no user is logged in, consider it valid (allows public actions)
47+
if (!user) return { valid: true, error: "" }
48+
49+
try {
50+
// If the platform provides a method to verify auth tokens, use it
51+
if (platform.auth.verifyAuthTokens) {
52+
const hasValidTokens = await platform.auth.verifyAuthTokens()
3253

33-
return {
34-
valid: hasValidTokens,
35-
error: hasValidTokens ? "" : SESSION_EXPIRED,
54+
if (hasValidTokens) {
55+
return { valid: true, error: "" }
3656
}
37-
} catch (error) {
38-
return { valid: false, error: SESSION_EXPIRED }
57+
58+
// Try token refresh if verification failed
59+
return attemptTokenRefresh()
3960
}
40-
}
4161

42-
// allow user to perform actions without being logged in
43-
return { valid: true, error: "" }
62+
// For platforms without token verification capability
63+
return { valid: true, error: "" }
64+
} catch (error) {
65+
// Handle errors from token verification
66+
return attemptTokenRefresh()
67+
}
4468
}

packages/hoppscotch-common/src/platform/auth.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,10 @@ export type AuthPlatformDef = {
281281
* @returns True if tokens are valid, false otherwise
282282
*/
283283
verifyAuthTokens?: () => Promise<boolean>
284+
285+
/** Refreshes the authentication tokens for the current user
286+
* For self-hosted, this should refresh the tokens with the backend
287+
* @returns True if tokens were refreshed successfully, false otherwise
288+
*/
289+
refreshAuthToken?: () => Promise<boolean>
284290
}

packages/hoppscotch-selfhost-desktop/src/platform/auth.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,10 @@ export const def: AuthPlatformDef = {
395395
})
396396
},
397397

398+
async refreshAuthToken() {
399+
return refreshToken()
400+
},
401+
398402
/**
399403
* Verifies if the current user's authentication tokens are valid
400404
* @returns True if tokens are valid, false otherwise

packages/hoppscotch-selfhost-web/src/platform/auth/desktop/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,11 @@ export const def: AuthPlatformDef = {
527527
})
528528
},
529529

530+
async refreshAuthToken() {
531+
const refreshed = await refreshToken()
532+
return refreshed ?? false
533+
},
534+
530535
/**
531536
* Verifies if the current user's authentication tokens are valid
532537
* @returns True if tokens are valid, false otherwise

packages/hoppscotch-selfhost-web/src/platform/auth/web/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,10 @@ export const def: AuthPlatformDef = {
353353
})
354354
},
355355

356+
async refreshAuthToken() {
357+
return refreshToken()
358+
},
359+
356360
async processMagicLink() {
357361
if (this.isSignInWithEmailLink(window.location.href)) {
358362
const deviceIdentifier =

0 commit comments

Comments
 (0)