Skip to content

Commit d39d774

Browse files
committed
fix(auth): improve refresh token logic
1 parent de7a10c commit d39d774

File tree

5 files changed

+48
-19
lines changed

5 files changed

+48
-19
lines changed

components/Layout.vue

+4-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
</template>
99

1010
<script setup lang="ts">
11+
import { useIntervalFn } from "@vueuse/core";
12+
1113
const props = defineProps<{
1214
title: string;
1315
description: string;
@@ -23,7 +25,7 @@ useHead({
2325
const userStore = useAuthStore();
2426
const { user } = storeToRefs(userStore);
2527
26-
const pollRefresh = async () => {
28+
useIntervalFn(async () => {
2729
// eslint-disable-next-line no-useless-return
2830
if (!user.value.id) return;
2931
else {
@@ -32,9 +34,5 @@ const pollRefresh = async () => {
3234
);
3335
if (requestState.error) throw requestState.error;
3436
}
35-
};
36-
37-
onMounted(() => {
38-
setInterval(pollRefresh, 30_000); // poll refresh token API every 30s
39-
});
37+
}, 300_000); // update every 5 minutes when logged in
4038
</script>

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@ts-safeql/eslint-plugin": "^2.0.3",
3838
"@types/jsonwebtoken": "^9.0.5",
3939
"@types/uuid": "^9.0.8",
40+
"@vueuse/core": "^10.9.0",
4041
"autoprefixer": "^10.4.17",
4142
"eslint": "^8.56.0",
4243
"eslint-config-clarity": "^1.0.6",

server/api/auth/refresh.post.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,25 @@ export default defineEventHandler(async (event) => {
2121
runtimeConfig.jwtRefreshSecret,
2222
) as jwt.JwtPayload;
2323

24+
if (new Date().getTime() > payload.exp!)
25+
throw createError({
26+
statusCode: 403,
27+
statusMessage: "Forbidden: token is still valid",
28+
});
29+
2430
const savedRefreshToken = await findRefreshTokenById(payload.jti!);
2531

2632
if (!savedRefreshToken || savedRefreshToken.revoked)
2733
throw createError({
2834
statusCode: 401,
29-
statusMessage: "Unauthorized",
35+
statusMessage: "Unauthorized: token revoked",
3036
});
3137

3238
const hashedToken = hashToken(result.data.refreshToken);
3339
if (hashedToken !== savedRefreshToken.hashedToken)
3440
throw createError({
3541
statusCode: 401,
36-
statusMessage: "Unauthorized",
42+
statusMessage: "Unauthorized: refresh token mismatch",
3743
});
3844

3945
const user = await db.user.findFirst({
@@ -45,7 +51,7 @@ export default defineEventHandler(async (event) => {
4551
if (!user)
4652
throw createError({
4753
statusCode: 401,
48-
statusMessage: "Unauthorized",
54+
statusMessage: "Unauthorized: user not found",
4955
});
5056

5157
await deleteRefreshToken(savedRefreshToken.id);

utils/authStore.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,13 @@ export const useAuthStore = defineStore("auth", {
117117
error: null,
118118
};
119119
try {
120-
const data: RefreshState = await $fetch(
121-
"/api/auth/refresh",
122-
{
123-
method: "post",
124-
headers: { "Content-Type": "application/json" },
125-
body: {
126-
refreshToken,
127-
},
120+
const data: RefreshState = await $fetch("/api/auth/refresh", {
121+
method: "post",
122+
headers: { "Content-Type": "application/json" },
123+
body: {
124+
refreshToken,
128125
},
129-
);
126+
});
130127
requestState.loading = false;
131128

132129
this.user.refreshToken = data.refreshToken;

yarn.lock

+28-1
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,11 @@
17321732
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
17331733
integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==
17341734

1735+
"@types/web-bluetooth@^0.0.20":
1736+
version "0.0.20"
1737+
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
1738+
integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
1739+
17351740
"@typescript-eslint/eslint-plugin@^6.1.0":
17361741
version "6.21.0"
17371742
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3"
@@ -2073,6 +2078,28 @@
20732078
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1"
20742079
integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==
20752080

2081+
"@vueuse/core@^10.9.0":
2082+
version "10.9.0"
2083+
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.9.0.tgz#7d779a95cf0189de176fee63cee4ba44b3c85d64"
2084+
integrity sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==
2085+
dependencies:
2086+
"@types/web-bluetooth" "^0.0.20"
2087+
"@vueuse/metadata" "10.9.0"
2088+
"@vueuse/shared" "10.9.0"
2089+
vue-demi ">=0.14.7"
2090+
2091+
2092+
version "10.9.0"
2093+
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.9.0.tgz#769a1a9db65daac15cf98084cbf7819ed3758620"
2094+
integrity sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==
2095+
2096+
2097+
version "10.9.0"
2098+
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.9.0.tgz#13af2a348de15d07b7be2fd0c7fc9853a69d8fe0"
2099+
integrity sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==
2100+
dependencies:
2101+
vue-demi ">=0.14.7"
2102+
20762103
"@wale/general-sans@^1.0.0":
20772104
version "1.0.0"
20782105
resolved "https://npm.pkg.github.com/download/@wale/general-sans/1.0.0/31df4917e47fd8012e6d06ae3d366d5fc794a794#31df4917e47fd8012e6d06ae3d366d5fc794a794"
@@ -7855,7 +7882,7 @@ vue-bundle-renderer@^2.0.0:
78557882
dependencies:
78567883
ufo "^1.2.0"
78577884

7858-
vue-demi@>=0.14.5:
7885+
vue-demi@>=0.14.5, vue-demi@>=0.14.7:
78597886
version "0.14.7"
78607887
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.7.tgz#8317536b3ef74c5b09f268f7782e70194567d8f2"
78617888
integrity sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==

0 commit comments

Comments
 (0)