Skip to content

Commit

Permalink
feat(auth): add JWT /refresh endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
wale committed Mar 7, 2024
1 parent 0b569cd commit de7a10c
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 0 deletions.
18 changes: 18 additions & 0 deletions components/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,22 @@ useHead({
title: title.value,
meta: [{ name: "description", content: description.value }],
});
const userStore = useAuthStore();
const { user } = storeToRefs(userStore);
const pollRefresh = async () => {
// eslint-disable-next-line no-useless-return
if (!user.value.id) return;
else {
const { requestState } = await userStore.refresh(
user.value.refreshToken,
);
if (requestState.error) throw requestState.error;
}
};
onMounted(() => {
setInterval(pollRefresh, 30_000); // poll refresh token API every 30s
});
</script>
63 changes: 63 additions & 0 deletions server/api/auth/refresh.post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import jwt from "jsonwebtoken";
import { v4 } from "uuid";
import { z } from "zod";

const schema = z.object({
refreshToken: z.string(),
});

export default defineEventHandler(async (event) => {
const result = await readValidatedBody(event, (body) =>
schema.safeParse(body),
);

// eslint-disable-next-line @typescript-eslint/no-throw-literal
if (!result.success) throw result.error.issues;

const runtimeConfig = useRuntimeConfig();

const payload = jwt.verify(
result.data.refreshToken,
runtimeConfig.jwtRefreshSecret,
) as jwt.JwtPayload;

const savedRefreshToken = await findRefreshTokenById(payload.jti!);

if (!savedRefreshToken || savedRefreshToken.revoked)
throw createError({
statusCode: 401,
statusMessage: "Unauthorized",
});

const hashedToken = hashToken(result.data.refreshToken);
if (hashedToken !== savedRefreshToken.hashedToken)
throw createError({
statusCode: 401,
statusMessage: "Unauthorized",
});

const user = await db.user.findFirst({
where: {
id: payload.userId,
},
});

if (!user)
throw createError({
statusCode: 401,
statusMessage: "Unauthorized",
});

await deleteRefreshToken(savedRefreshToken.id);
const jti = v4();
const { accessToken, refreshToken: newRefreshToken } = generateTokens(
user,
jti,
);
await addRefreshToken(jti, newRefreshToken, user);

return {
accessToken,
refreshToken: newRefreshToken,
};
});
35 changes: 35 additions & 0 deletions utils/authStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ interface RequestState {
error: Error | null;
}

interface RefreshState {
accessToken: string;
refreshToken: string;
}

interface UserState {
id: string;
username: string;
Expand Down Expand Up @@ -104,5 +109,35 @@ export const useAuthStore = defineStore("auth", {
return { data: {} as UserState, requestState };
}
},
async refresh(
refreshToken: string,
): Promise<{ data: RefreshState; requestState: RequestState }> {
const requestState: RequestState = {
loading: true,
error: null,
};
try {
const data: RefreshState = await $fetch(

Check failure on line 120 in utils/authStore.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `⏎····················"/api/auth/refresh",⏎···················` with `"/api/auth/refresh",`
"/api/auth/refresh",
{
method: "post",

Check failure on line 123 in utils/authStore.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `························` with `····················`
headers: { "Content-Type": "application/json" },

Check failure on line 124 in utils/authStore.ts

View workflow job for this annotation

GitHub Actions / Lint

Delete `····`
body: {

Check failure on line 125 in utils/authStore.ts

View workflow job for this annotation

GitHub Actions / Lint

Delete `····`
refreshToken,

Check failure on line 126 in utils/authStore.ts

View workflow job for this annotation

GitHub Actions / Lint

Delete `····`
},

Check failure on line 127 in utils/authStore.ts

View workflow job for this annotation

GitHub Actions / Lint

Delete `····`
},

Check failure on line 128 in utils/authStore.ts

View workflow job for this annotation

GitHub Actions / Lint

Replace `····················},⏎················` with `················}`
);
requestState.loading = false;

this.user.refreshToken = data.refreshToken;
this.user.accessToken = data.accessToken;

return { data, requestState };
} catch (error) {
requestState.loading = false;
requestState.error = error as Error;
return { data: {} as RefreshState, requestState };
}
},
},
});

0 comments on commit de7a10c

Please sign in to comment.