BA
Better Auth•2mo ago
Ido

noobie question whats the better auth secret used for? is it just managing sessions?

cuz for security reasons i dont want to forever use the same key in my app so id ideally want to be able to rotate it every now and then, but then the other question, if its only used for sessions, would changing it force logout all users? if so, could i gradually do it over time and insert an array of old secret and new secret and slowly transition to the new one?
16 Replies
nikatune
nikatune•2mo ago
The secret to use for encryption signing and hashing
nikatune
nikatune•2mo ago
as far i know it will logout all users
No description
Ido
IdoOP•2mo ago
is there a way for me run an array? so like there will be BETTER_AUTH_SECRET and BETTER_AUTH_SECRET_OLD passed in an array like this
const secrets = [process.env.BETTER_AUTH_SECRET];

// add old if exists
if (process.env.BETTER_AUTH_SECRET_OLD) {
secrets.push(process.env.BETTER_AUTH_SECRET_OLD);
}

// Initialize BetterAuth with an array of secrets
const auth = new BetterAuth({
secret: secrets,
// ... other config
});
const secrets = [process.env.BETTER_AUTH_SECRET];

// add old if exists
if (process.env.BETTER_AUTH_SECRET_OLD) {
secrets.push(process.env.BETTER_AUTH_SECRET_OLD);
}

// Initialize BetterAuth with an array of secrets
const auth = new BetterAuth({
secret: secrets,
// ... other config
});
then this way i can slowly roll out the new secret im not sure if its possible or not and not have to log out all users cuz i feel like its isnt a great user experience
nikatune
nikatune•2mo ago
it will not work
Ido
IdoOP•2mo ago
Hmmm Is there a solution or just gonna have to log out users?
nikatune
nikatune•2mo ago
probably you have to logout all users but lets ask it to @Ping
Ido
IdoOP•2mo ago
i see
Ping
Ping•2mo ago
Yeah you can't do much about this, once the secret is changed Better-auth wouldn't know how to decrypt data correctly, or hash values correctly. Furthermore, given that secrets are used for hashing, it means you can't rotate secrets since hashes can't be unhashed and converted to a new hash via new secret. The only way if you want this is to make all of your users enter their password again in order for the new secret to be used during hashing
Ido
IdoOP•2mo ago
wait just to confirm my understanding youre saying this is only for the session not for passwords? like changing the secret would make the users have to login again but its not used to encrypt the passwords correct?
Ping
Ping•2mo ago
No, I'm essentially saying we can't have secret rotation because passwords are hashed. Once it's hashed you can't unhash it, so if you were to swap the secret to another then when the user signs in with their password, it wouldn't let them in, because the hash outcome would be different.
Ido
IdoOP•2mo ago
Hi, thanks so much for the detailed explanation yesterday, I really appreciate you taking the time. I ran a quick test to make sure I fully understood the behavior, and I saw something interesting that I wanted to clarify. I signed up and logged in successfully. While I was logged in, I changed the BETTER_AUTH_SECRET in my environment and restarted my server. When I refreshed my dashboard, my session was invalid (my user info disappeared), which makes perfect sense. However, I was still able to log back in using the same password. This seems to suggest that the BETTER_AUTH_SECRET is only used for signing session cookies, and not for hashing the passwords themselves. Is my understanding correct? If so, I was curious about the design choice. Many auth libraries use a per-user salt stored in the database for password hashing. I'd love to understand the reasoning behind the approach that is currently implemented Finally, a feature suggestion: It would be amazing if the main betterAuth constructor could accept an array of secrets (e.g., secret: [newKey, oldKey]). This would allow for zero-downtime secret rotation for user sessions, which is a huge win for security hygiene. Is that something you'd consider for a future release?
LightTab2
LightTab2•2mo ago
this is /src/crypto/password.ts
const config = {
N: 16384,
r: 16,
p: 1,
dkLen: 64,
};

async function generateKey(password: string, salt: string) {
return await scryptAsync(password.normalize("NFKC"), salt, {
N: config.N,
p: config.p,
r: config.r,
dkLen: config.dkLen,
maxmem: 128 * config.N * config.r * 2,
});
}

export const hashPassword = async (password: string) => {
const salt = hex.encode(getRandomValues(new Uint8Array(16)));
const key = await generateKey(password, salt);
return `${salt}:${hex.encode(key)}`;
};
const config = {
N: 16384,
r: 16,
p: 1,
dkLen: 64,
};

async function generateKey(password: string, salt: string) {
return await scryptAsync(password.normalize("NFKC"), salt, {
N: config.N,
p: config.p,
r: config.r,
dkLen: config.dkLen,
maxmem: 128 * config.N * config.r * 2,
});
}

export const hashPassword = async (password: string) => {
const salt = hex.encode(getRandomValues(new Uint8Array(16)));
const key = await generateKey(password, salt);
return `${salt}:${hex.encode(key)}`;
};
It indeed does not seem to use BETTER_AUTH_SECRET to hash it, but only if options.emailAndPassword?.password?.hash is not provided in AuthContext
hash: options.emailAndPassword?.password?.hash || hashPassword
hash: options.emailAndPassword?.password?.hash || hashPassword
so unless you pass a custom hashing function I think it won't I know it is used to encrypt private jwks If you need to get some encrypted data back when changing the secret you can use something like this:
const privateDataEncrypted = '';
const secret = '';

import { createHash } from '@better-auth/utils/hash';
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { hexToBytes } from '@noble/ciphers/utils';
import { managedNonce } from '@noble/ciphers/webcrypto';

// magic
const symmetricDecrypt = async ({ key, data } : { key : string, data: string }) =>
{
const keyAsBytes = await createHash("SHA-256").digest(key);
const dataAsBytes = hexToBytes(data);
const chacha$1 = managedNonce(xchacha20poly1305)(new Uint8Array(keyAsBytes));
return new TextDecoder().decode(chacha$1.decrypt(dataAsBytes));
};

export const data = JSON.stringify(
await symmetricDecrypt({
key: secret,
data: privateDataEncrypted
}));

console.log(data);
const privateDataEncrypted = '';
const secret = '';

import { createHash } from '@better-auth/utils/hash';
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { hexToBytes } from '@noble/ciphers/utils';
import { managedNonce } from '@noble/ciphers/webcrypto';

// magic
const symmetricDecrypt = async ({ key, data } : { key : string, data: string }) =>
{
const keyAsBytes = await createHash("SHA-256").digest(key);
const dataAsBytes = hexToBytes(data);
const chacha$1 = managedNonce(xchacha20poly1305)(new Uint8Array(keyAsBytes));
return new TextDecoder().decode(chacha$1.decrypt(dataAsBytes));
};

export const data = JSON.stringify(
await symmetricDecrypt({
key: secret,
data: privateDataEncrypted
}));

console.log(data);
Ido
IdoOP•2mo ago
okay seemingly i was correct it is for jwt so now, i guess is there a reason we cant let an array of secrets? this way we can do graceful rotation
LightTab2
LightTab2•2mo ago
I think it is safest if you force people to store it in .env or something alike, changing .env usually forces to restart the app anyways. I don't really see much benefits from rotating it, it is better to protect it, so even your workers don't accesss it and it's a black box. The only reason to rotate it is quantum computers breaking crypto world (long way to go still) or fear of leakage (hence protecting). Bruteforcing AES256 takes more time than the current age of universe. That being said, we all are humans and mistakes will happens, so rotating is a good practice anyways... But for the sake of security I can't think of this process to be at least semi-manual and it'd be hard to implement it with smooth transition of all systems that better-auth has. So it probably would mean stopping some of it to change secrets. And this maintentance is often what many of the websites don't want to/can't afford. With this and some coding on your part (like 'translating' old sessions to 'new secret', but only if the 'old secret' hasn't expired) you can implement it yourself. But its a code that's tedious and risky to maintain and besides, I believe most of the users assume (wrongly) they don't need it It's to secure endpoints too, since cookies safety is based on it
Ido
IdoOP•2mo ago
im using doppler for env variables its more secure, but its still hygenic to rotate apis every now and then
LightTab2
LightTab2•2mo ago
If better-auth was to implement this I think it would take a lot time and effort, because it's something that potentially increases attack surface and needs to be done carefully, I bet they have more important things to do for now. The reason might be simply practical or it's a legacy no one bothered to improve upon Reward per cost seems small even to me 😛

Did you find this page helpful?