@auth/core
Advanced tools
Comparing version 0.23.0 to 0.24.0
@@ -188,7 +188,7 @@ type ErrorOptions = Error | Record<string, unknown>; | ||
/** | ||
* Auth.js requires a secret to be set, but none was not found. This is used to encrypt cookies, JWTs and other sensitive data. | ||
* Auth.js requires a secret or multiple secrets to be set, but none was not found. This is used to encrypt cookies, JWTs and other sensitive data. | ||
* | ||
* :::note | ||
* If you are using a framework like Next.js, we try to automatically infer the secret from the `AUTH_SECRET` environment variable. | ||
* Alternatively, you can also explicitly set the [`AuthConfig.secret`](https://authjs.dev/reference/core#secret). | ||
* If you are using a framework like Next.js, we try to automatically infer the secret from the `AUTH_SECRET`, `AUTH_SECRET_1`, etc. environment variables. | ||
* Alternatively, you can also explicitly set the [`AuthConfig.secret`](https://authjs.dev/reference/core#secret) option. | ||
* ::: | ||
@@ -198,6 +198,3 @@ * | ||
* :::tip | ||
* You can generate a good secret value: | ||
* - On Unix systems: type `openssl rand -hex 32` in the terminal | ||
* - Or generate one [online](https://generate-secret.vercel.app/32) | ||
* | ||
* To generate a random string, you can use the Auth.js CLI: `npx auth secret` | ||
* ::: | ||
@@ -204,0 +201,0 @@ */ |
@@ -204,7 +204,7 @@ /** | ||
/** | ||
* Auth.js requires a secret to be set, but none was not found. This is used to encrypt cookies, JWTs and other sensitive data. | ||
* Auth.js requires a secret or multiple secrets to be set, but none was not found. This is used to encrypt cookies, JWTs and other sensitive data. | ||
* | ||
* :::note | ||
* If you are using a framework like Next.js, we try to automatically infer the secret from the `AUTH_SECRET` environment variable. | ||
* Alternatively, you can also explicitly set the [`AuthConfig.secret`](https://authjs.dev/reference/core#secret). | ||
* If you are using a framework like Next.js, we try to automatically infer the secret from the `AUTH_SECRET`, `AUTH_SECRET_1`, etc. environment variables. | ||
* Alternatively, you can also explicitly set the [`AuthConfig.secret`](https://authjs.dev/reference/core#secret) option. | ||
* ::: | ||
@@ -214,6 +214,3 @@ * | ||
* :::tip | ||
* You can generate a good secret value: | ||
* - On Unix systems: type `openssl rand -hex 32` in the terminal | ||
* - Or generate one [online](https://generate-secret.vercel.app/32) | ||
* | ||
* To generate a random string, you can use the Auth.js CLI: `npx auth secret` | ||
* ::: | ||
@@ -220,0 +217,0 @@ */ |
@@ -73,8 +73,12 @@ /** | ||
* A random string used to hash tokens, sign cookies and generate cryptographic keys. | ||
* To generate a random string, you can use the following command: | ||
* | ||
* - On Unix systems, type `openssl rand -hex 32` in the terminal | ||
* - Or generate one [online](https://generate-secret.vercel.app/32) | ||
* To generate a random string, you can use the Auth.js CLI: `npx auth secret` | ||
* | ||
* @note | ||
* You can also pass an array of secrets, in which case the first secret that successfully | ||
* decrypts the JWT will be used. This is useful for rotating secrets without invalidating existing sessions. | ||
* The newer secret should be added to the start of the array, which will be used for all new sessions. | ||
* | ||
*/ | ||
secret?: string; | ||
secret?: string | string[]; | ||
/** | ||
@@ -81,0 +85,0 @@ * Configure your session like if you want to use JWT or a database, |
13
jwt.d.ts
@@ -96,3 +96,3 @@ /** | ||
/** Used in combination with `salt`, to derive the encryption secret for JWTs. */ | ||
secret: string; | ||
secret: string | string[]; | ||
/** The JWT payload. */ | ||
@@ -104,4 +104,11 @@ token?: Payload; | ||
salt: string; | ||
/** Used in combination with `salt`, to derive the encryption secret for JWTs. */ | ||
secret: string; | ||
/** | ||
* Used in combination with `salt`, to derive the encryption secret for JWTs. | ||
* | ||
* @note | ||
* You can also pass an array of secrets, in which case the first secret that successfully | ||
* decrypts the JWT will be used. This is useful for rotating secrets without invalidating existing sessions. | ||
* The newer secret should be added to the start of the array, which will be used for all new sessions. | ||
*/ | ||
secret: string | string[]; | ||
/** The Auth.js issued JWT to be decoded */ | ||
@@ -108,0 +115,0 @@ token?: string; |
23
jwt.js
@@ -39,3 +39,3 @@ /** | ||
import { hkdf } from "@panva/hkdf"; | ||
import { EncryptJWT, jwtDecrypt } from "jose"; | ||
import { EncryptJWT, base64url, calculateJwkThumbprint, jwtDecrypt } from "jose"; | ||
import { SessionStore } from "./lib/utils/cookie.js"; | ||
@@ -51,6 +51,8 @@ import { MissingSecret } from "./errors.js"; | ||
const { token = {}, secret, maxAge = DEFAULT_MAX_AGE, salt } = params; | ||
const encryptionSecret = await getDerivedEncryptionKey(enc, secret, salt); | ||
const secrets = Array.isArray(secret) ? secret : [secret]; | ||
const encryptionSecret = await getDerivedEncryptionKey(enc, secrets[0], salt); | ||
const thumbprint = await calculateJwkThumbprint({ kty: "oct", k: base64url.encode(encryptionSecret) }, `sha${encryptionSecret.byteLength << 3}`); | ||
// @ts-expect-error `jose` allows any object as payload. | ||
return await new EncryptJWT(token) | ||
.setProtectedHeader({ alg, enc }) | ||
.setProtectedHeader({ alg, enc, kid: thumbprint }) | ||
.setIssuedAt() | ||
@@ -64,8 +66,19 @@ .setExpirationTime(now() + maxAge) | ||
const { token, secret, salt } = params; | ||
const secrets = Array.isArray(secret) ? secret : [secret]; | ||
if (!token) | ||
return null; | ||
const { payload } = await jwtDecrypt(token, async ({ enc }) => await getDerivedEncryptionKey(enc, secret, salt), { | ||
const { payload } = await jwtDecrypt(token, async ({ kid, enc }) => { | ||
for (const secret of secrets) { | ||
const encryptionSecret = await getDerivedEncryptionKey(enc, secret, salt); | ||
if (kid === undefined) | ||
return encryptionSecret; | ||
const thumbprint = await calculateJwkThumbprint({ kty: "oct", k: base64url.encode(encryptionSecret) }, `sha${encryptionSecret.byteLength << 3}`); | ||
if (kid === thumbprint) | ||
return encryptionSecret; | ||
} | ||
throw new Error("no matching decryption secret"); | ||
}, { | ||
clockTolerance: 15, | ||
keyManagementAlgorithms: [alg], | ||
contentEncryptionAlgorithms: [enc, "A256GCM"] | ||
contentEncryptionAlgorithms: [enc, "A256GCM"], | ||
}); | ||
@@ -72,0 +85,0 @@ return payload; |
@@ -33,3 +33,3 @@ import * as checks from "./checks.js"; | ||
as = { | ||
issuer: provider.issuer ?? "https://authjs.dev", | ||
issuer: provider.issuer ?? "https://authjs.dev", // TODO: review fallback issuer | ||
token_endpoint: token?.url.toString(), | ||
@@ -36,0 +36,0 @@ userinfo_endpoint: userinfo?.url.toString(), |
@@ -86,6 +86,4 @@ import * as jwt from "../jwt.js"; | ||
jwt: { | ||
// Asserted in assert.ts | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
secret: authOptions.secret, | ||
maxAge: authOptions.session?.maxAge ?? maxAge, | ||
secret: authOptions.secret, // Asserted in assert.ts | ||
maxAge: authOptions.session?.maxAge ?? maxAge, // default to same as `session.maxAge` | ||
encode: jwt.encode, | ||
@@ -92,0 +90,0 @@ decode: jwt.decode, |
{ | ||
"name": "@auth/core", | ||
"version": "0.23.0", | ||
"version": "0.24.0", | ||
"description": "Authentication for the Web.", | ||
@@ -33,4 +33,3 @@ "keywords": [ | ||
"providers", | ||
"src", | ||
"!vitest.config.js" | ||
"src" | ||
], | ||
@@ -67,4 +66,4 @@ "exports": { | ||
"@panva/hkdf": "^1.1.1", | ||
"@types/cookie": "0.6.0", | ||
"cookie": "0.6.0", | ||
"@types/cookie": "0.6.0", | ||
"jose": "^5.1.3", | ||
@@ -89,5 +88,3 @@ "oauth4webapi": "^2.4.0", | ||
"postcss": "8.4.19", | ||
"postcss-nested": "6.0.0", | ||
"vite": "^5.0.12", | ||
"vitest": "^1.2.1" | ||
"postcss-nested": "6.0.0" | ||
}, | ||
@@ -99,5 +96,5 @@ "scripts": { | ||
"dev": "pnpm css && pnpm providers && tsc -w", | ||
"test": "vitest", | ||
"test": "vitest -c ../utils/vitest.config.ts", | ||
"providers": "node scripts/generate-providers" | ||
} | ||
} |
@@ -82,3 +82,3 @@ /** | ||
name: profile.name, | ||
email: null, | ||
email: null, // trakt does not provide user emails | ||
image: profile.images.avatar.full, // trakt does not allow hotlinking | ||
@@ -85,0 +85,0 @@ }; |
@@ -261,7 +261,7 @@ type ErrorOptions = Error | Record<string, unknown> | ||
/** | ||
* Auth.js requires a secret to be set, but none was not found. This is used to encrypt cookies, JWTs and other sensitive data. | ||
* Auth.js requires a secret or multiple secrets to be set, but none was not found. This is used to encrypt cookies, JWTs and other sensitive data. | ||
* | ||
* :::note | ||
* If you are using a framework like Next.js, we try to automatically infer the secret from the `AUTH_SECRET` environment variable. | ||
* Alternatively, you can also explicitly set the [`AuthConfig.secret`](https://authjs.dev/reference/core#secret). | ||
* If you are using a framework like Next.js, we try to automatically infer the secret from the `AUTH_SECRET`, `AUTH_SECRET_1`, etc. environment variables. | ||
* Alternatively, you can also explicitly set the [`AuthConfig.secret`](https://authjs.dev/reference/core#secret) option. | ||
* ::: | ||
@@ -271,6 +271,3 @@ * | ||
* :::tip | ||
* You can generate a good secret value: | ||
* - On Unix systems: type `openssl rand -hex 32` in the terminal | ||
* - Or generate one [online](https://generate-secret.vercel.app/32) | ||
* | ||
* To generate a random string, you can use the Auth.js CLI: `npx auth secret` | ||
* ::: | ||
@@ -277,0 +274,0 @@ */ |
@@ -214,8 +214,12 @@ /** | ||
* A random string used to hash tokens, sign cookies and generate cryptographic keys. | ||
* To generate a random string, you can use the following command: | ||
* | ||
* - On Unix systems, type `openssl rand -hex 32` in the terminal | ||
* - Or generate one [online](https://generate-secret.vercel.app/32) | ||
* To generate a random string, you can use the Auth.js CLI: `npx auth secret` | ||
* | ||
* @note | ||
* You can also pass an array of secrets, in which case the first secret that successfully | ||
* decrypts the JWT will be used. This is useful for rotating secrets without invalidating existing sessions. | ||
* The newer secret should be added to the start of the array, which will be used for all new sessions. | ||
* | ||
*/ | ||
secret?: string | ||
secret?: string | string[] | ||
/** | ||
@@ -222,0 +226,0 @@ * Configure your session like if you want to use JWT or a database, |
@@ -40,3 +40,3 @@ /** | ||
import { hkdf } from "@panva/hkdf" | ||
import { EncryptJWT, jwtDecrypt } from "jose" | ||
import { EncryptJWT, base64url, calculateJwkThumbprint, jwtDecrypt } from "jose" | ||
import { SessionStore } from "./lib/utils/cookie.js" | ||
@@ -54,2 +54,3 @@ import { Awaitable } from "./types.js" | ||
const enc = "A256CBC-HS512" | ||
type Digest = Parameters<typeof calculateJwkThumbprint>[1] | ||
@@ -59,6 +60,12 @@ /** Issues a JWT. By default, the JWT is encrypted using "A256CBC-HS512". */ | ||
const { token = {}, secret, maxAge = DEFAULT_MAX_AGE, salt } = params | ||
const encryptionSecret = await getDerivedEncryptionKey(enc, secret, salt) | ||
const secrets = Array.isArray(secret) ? secret : [secret] | ||
const encryptionSecret = await getDerivedEncryptionKey(enc, secrets[0], salt) | ||
const thumbprint = await calculateJwkThumbprint( | ||
{ kty: "oct", k: base64url.encode(encryptionSecret) }, | ||
`sha${encryptionSecret.byteLength << 3}` as Digest | ||
) | ||
// @ts-expect-error `jose` allows any object as payload. | ||
return await new EncryptJWT(token) | ||
.setProtectedHeader({ alg, enc }) | ||
.setProtectedHeader({ alg, enc, kid: thumbprint }) | ||
.setIssuedAt() | ||
@@ -75,10 +82,28 @@ .setExpirationTime(now() + maxAge) | ||
const { token, secret, salt } = params | ||
const secrets = Array.isArray(secret) ? secret : [secret] | ||
if (!token) return null | ||
const { payload } = await jwtDecrypt( | ||
token, | ||
async ({ enc }) => await getDerivedEncryptionKey(enc, secret, salt), | ||
async ({ kid, enc }) => { | ||
for (const secret of secrets) { | ||
const encryptionSecret = await getDerivedEncryptionKey( | ||
enc, | ||
secret, | ||
salt | ||
) | ||
if (kid === undefined) return encryptionSecret | ||
const thumbprint = await calculateJwkThumbprint( | ||
{ kty: "oct", k: base64url.encode(encryptionSecret) }, | ||
`sha${encryptionSecret.byteLength << 3}` as Digest | ||
) | ||
if (kid === thumbprint) return encryptionSecret | ||
} | ||
throw new Error("no matching decryption secret") | ||
}, | ||
{ | ||
clockTolerance: 15, | ||
keyManagementAlgorithms: [alg], | ||
contentEncryptionAlgorithms: [enc, "A256GCM"] | ||
contentEncryptionAlgorithms: [enc, "A256GCM"], | ||
} | ||
@@ -218,3 +243,3 @@ ) | ||
/** Used in combination with `salt`, to derive the encryption secret for JWTs. */ | ||
secret: string | ||
secret: string | string[] | ||
/** The JWT payload. */ | ||
@@ -227,4 +252,11 @@ token?: Payload | ||
salt: string | ||
/** Used in combination with `salt`, to derive the encryption secret for JWTs. */ | ||
secret: string | ||
/** | ||
* Used in combination with `salt`, to derive the encryption secret for JWTs. | ||
* | ||
* @note | ||
* You can also pass an array of secrets, in which case the first secret that successfully | ||
* decrypts the JWT will be used. This is useful for rotating secrets without invalidating existing sessions. | ||
* The newer secret should be added to the start of the array, which will be used for all new sessions. | ||
*/ | ||
secret: string | string[] | ||
/** The Auth.js issued JWT to be decoded */ | ||
@@ -237,5 +269,7 @@ token?: string | ||
* The secret used to encode/decode the Auth.js issued JWT. | ||
* It can be an array of secrets, in which case the first secret that successfully | ||
* decrypts the JWT will be used. This is useful for rotating secrets without invalidating existing sessions. | ||
* @internal | ||
*/ | ||
secret: string | ||
secret: string | string[] | ||
/** | ||
@@ -242,0 +276,0 @@ * The maximum age of the Auth.js issued JWT in seconds. |
@@ -134,5 +134,3 @@ import * as jwt from "../jwt.js" | ||
jwt: { | ||
// Asserted in assert.ts | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
secret: authOptions.secret!, | ||
secret: authOptions.secret!, // Asserted in assert.ts | ||
maxAge: authOptions.session?.maxAge ?? maxAge, // default to same as `session.maxAge` | ||
@@ -139,0 +137,0 @@ encode: jwt.encode, |
@@ -556,3 +556,3 @@ /** | ||
csrfTokenVerified?: boolean | ||
secret: string | ||
secret: string | string[] | ||
theme: Theme | ||
@@ -559,0 +559,0 @@ debug: boolean |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1404168
6
466
35562