Implement signed cookie helpers
Provide a convenient wrapper for setting SHA-256 HMAC signed cookies and retreiving them with the signature validated. The secret key is configured in the NUXT_COOKIE_SECRET_KEY environment variable.
This commit is contained in:
parent
5044b7b58d
commit
8da4b02154
4 changed files with 60 additions and 10 deletions
17
generate-keys.mjs
Normal file
17
generate-keys.mjs
Normal file
|
@ -0,0 +1,17 @@
|
|||
import webPush from "web-push";
|
||||
|
||||
const vapidKeys = webPush.generateVAPIDKeys();
|
||||
const cookieSecretKey = Buffer.from(
|
||||
await crypto.subtle.exportKey(
|
||||
"raw",
|
||||
await crypto.subtle.generateKey(
|
||||
{ name: "HMAC", hash: "SHA-256" }, true, ["sign", "verify"]
|
||||
)
|
||||
)
|
||||
).toString("base64url");
|
||||
|
||||
console.log(`\
|
||||
NUXT_PUBLIC_VAPID_PUBLIC_KEY=${vapidKeys.publicKey}
|
||||
NUXT_VAPID_PRIVATE_KEY=${vapidKeys.privateKey}
|
||||
NUXT_COOKIE_SECRET_KEY=${cookieSecretKey}
|
||||
`);
|
|
@ -1,10 +0,0 @@
|
|||
import webPush from "web-push";
|
||||
import fs from "node:fs/promises";
|
||||
|
||||
const vapidKeys = webPush.generateVAPIDKeys();
|
||||
|
||||
const envData = `\
|
||||
NUXT_PUBLIC_VAPID_PUBLIC_KEY=${vapidKeys.publicKey}
|
||||
NUXT_VAPID_PRIVATE_KEY=${vapidKeys.privateKey}
|
||||
`;
|
||||
await fs.writeFile(".env", envData, "utf-8");
|
|
@ -3,6 +3,7 @@ export default defineNuxtConfig({
|
|||
compatibilityDate: '2024-11-01',
|
||||
devtools: { enabled: true },
|
||||
runtimeConfig: {
|
||||
cookieSecretKey: "",
|
||||
vapidPrivateKey: "",
|
||||
public: {
|
||||
vapidPublicKey: "",
|
||||
|
|
42
server/utils/signed-cookie.ts
Normal file
42
server/utils/signed-cookie.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import type { H3Event } from "h3";
|
||||
|
||||
let cachedCookieSecret: CryptoKey;
|
||||
export async function useCookieSecret(event: H3Event) {
|
||||
if (cachedCookieSecret)
|
||||
return cachedCookieSecret;
|
||||
|
||||
const runtimeConfig = useRuntimeConfig(event);
|
||||
return cachedCookieSecret = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
Buffer.from(runtimeConfig.cookieSecretKey, "base64url"),
|
||||
{ name: "HMAC", hash: "SHA-256" },
|
||||
false,
|
||||
["sign", "verify"],
|
||||
);
|
||||
}
|
||||
|
||||
export async function setSignedCookie(event: H3Event, name: string, value: string) {
|
||||
const secret = await useCookieSecret(event);
|
||||
const signature = await crypto.subtle.sign("HMAC", secret, Buffer.from(value));
|
||||
const cookie = `${value}.${Buffer.from(signature).toString("base64url")}`
|
||||
setCookie(event, name, cookie, { httpOnly: true, secure: true, sameSite: true });
|
||||
}
|
||||
|
||||
export async function getSignedCookie(event: H3Event, name: string) {
|
||||
const cookie = getCookie(event, name);
|
||||
if (!cookie)
|
||||
return;
|
||||
|
||||
const rightDot = cookie.lastIndexOf(".");
|
||||
if (rightDot === -1)
|
||||
return;
|
||||
|
||||
const value = cookie.slice(0, rightDot);
|
||||
const secret = await useCookieSecret(event);
|
||||
const signature = Buffer.from(cookie.slice(rightDot + 1), "base64url");
|
||||
const valid = await crypto.subtle.verify("HMAC", secret, signature, Buffer.from(value));
|
||||
if (!valid)
|
||||
return
|
||||
|
||||
return value;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue