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',
|
compatibilityDate: '2024-11-01',
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
|
cookieSecretKey: "",
|
||||||
vapidPrivateKey: "",
|
vapidPrivateKey: "",
|
||||||
public: {
|
public: {
|
||||||
vapidPublicKey: "",
|
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