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:
Hornwitser 2025-03-06 22:07:51 +01:00
parent 5044b7b58d
commit 8da4b02154
4 changed files with 60 additions and 10 deletions

17
generate-keys.mjs Normal file
View 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}
`);

View file

@ -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");

View file

@ -3,6 +3,7 @@ export default defineNuxtConfig({
compatibilityDate: '2024-11-01',
devtools: { enabled: true },
runtimeConfig: {
cookieSecretKey: "",
vapidPrivateKey: "",
public: {
vapidPublicKey: "",

View 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;
}