Email verification (code) with lucia auth, next.js 15 #150159
Replies: 3 comments
-
Hi @Siddhisalvi, It looks like you're implementing email verification in Next.js 15 using Lucia Auth, Prisma, and TypeScript. Here are some steps to help you debug and improve your setup: 1. Ensure Database Has
|
Beta Was this translation helpful? Give feedback.
-
Hi thank you for your help I replied late because I was baffled when I just
learned lucia is deprecated I don't know what to do I thought to switch to
betterauth can you please suggest me something or should I continue to use
lucia and it wont really affect my website?
…On Fri, 31 Jan 2025 at 01:24, Outlaw007 ***@***.***> wrote:
*Hi @Siddhisalvi <https://github.com/Siddhisalvi>,*
It looks like you're implementing email verification in *Next.js 15*
using *Lucia Auth, Prisma, and TypeScript*. Here are some steps to help
you debug and improve your setup:
*1. Ensure Database Has email_verified Column*
-
Your *user table* should have an email_verified column as BOOLEAN.
-
Your lucia instance should retrieve this field correctly:
import { Lucia } from "lucia";
export const lucia = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: process.env.NODE_ENV === "production", // Ensures secure cookies in HTTPS
},
},
getUserAttributes: (attributes) => {
return {
emailVerified: attributes.email_verified,
email: attributes.email,
};
},});
declare module "lucia" {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: {
email: string;
email_verified: boolean;
};
}}
*2. Create Email Verification Code Table*
Make sure you have a separate table to store *email verification codes*:
CREATE TABLE email_verification_code (
id SERIAL PRIMARY KEY,
user_id UUID UNIQUE REFERENCES users(id),
email VARCHAR(255) NOT NULL,
code VARCHAR(8) NOT NULL,
expires_at TIMESTAMP NOT NULL
);
*3. Generate & Store Verification Codes*
Use oslo/crypto to generate a *random 8-digit numeric code*:
import { TimeSpan, createDate } from "oslo";import { generateRandomString, alphabet } from "oslo/crypto";
async function generateEmailVerificationCode(userId: string, email: string): Promise<string> {
await db.table("email_verification_code").where("user_id", "=", userId).deleteAll();
const code = generateRandomString(8, alphabet("0-9"));
await db.table("email_verification_code").insert({
user_id: userId,
email,
code,
expires_at: createDate(new TimeSpan(15, "m")) // 15 minutes expiry
});
return code;}
*4. Send Email Verification Code Upon Signup*
Modify your *signup function* to send a verification email:
import { generateIdFromEntropySize } from "lucia";
app.post("/signup", async () => {
const userId = generateIdFromEntropySize(10); // 16 characters long
await db.table("user").insert({
id: userId,
email,
password_hash: passwordHash,
email_verified: false
});
const verificationCode = await generateEmailVerificationCode(userId, email);
await sendVerificationCode(email, verificationCode);
const session = await lucia.createSession(userId, {});
const sessionCookie = lucia.createSessionCookie(session.id);
return new Response(null, {
status: 302,
headers: {
Location: "/",
"Set-Cookie": sessionCookie.serialize()
}
});});
*5. Verify the Email Code*
Ensure the *code is valid, not expired, and matches the user's email*:
import { isWithinExpirationDate } from "oslo";import type { User } from "lucia";
app.post("/email-verification", async () => {
const { user } = await lucia.validateSession(sessionId);
if (!user) return new Response(null, { status: 401 });
const code = formData.get("code");
if (typeof code !== "string") return new Response(null, { status: 400 });
const validCode = await verifyVerificationCode(user, code);
if (!validCode) return new Response(null, { status: 400 });
await lucia.invalidateUserSessions(user.id);
await db.table("user").where("id", "=", user.id).update({
email_verified: true
});
const session = await lucia.createSession(user.id, {});
const sessionCookie = lucia.createSessionCookie(session.id);
return new Response(null, {
status: 302,
headers: {
Location: "/",
"Set-Cookie": sessionCookie.serialize()
}
});});
async function verifyVerificationCode(user: User, code: string): Promise<boolean> {
await db.beginTransaction();
const databaseCode = await db
.table("email_verification_code")
.where("user_id", "=", user.id)
.get();
if (!databaseCode || databaseCode.code !== code) {
await db.commit();
return false;
}
await db.table("email_verification_code").where("id", "=", databaseCode.id).delete();
await db.commit();
if (!isWithinExpirationDate(databaseCode.expires_at)) return false;
if (databaseCode.email !== user.email) return false;
return true;}
*6. Implement Rate Limiting for Security*
- Prevent brute-force attacks by *limiting verification attempts per
IP and user ID*.
- Store the number of failed attempts and block users after multiple
failures.
Let me know if this helps you ??
—
Reply to this email directly, view it on GitHub
<#150159 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AV7AFF7D7RPQVT6GIWX7DFD2NJ7QHAVCNFSM6AAAAABWA5MCDOVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTEMBRGA4DINQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
hey @Siddhisalvi use better_auth on nodes.js |
Beta Was this translation helpful? Give feedback.
-
Body
Can anyone please help me with this I cant get the email verification right, I an using next.js 15, lucia auth, prisma with typescript not javascript please anyone?
Email verification codes
We recommend reading through the email verification guide in the Copenhagen Book.
Update database
User table
Add a
email_verified
column (boolean).Email verification code table
Create a table for storing for email verification codes.
Generate verification code
The code should be valid for few minutes and linked to a single email.
You can also use alphanumeric codes.
When a user signs up, set
email_verified
tofalse
, create and send a verification code, and create a new session.When resending verification emails, make sure to implement rate limiting based on user ID and IP address.
Verify code and email
Make sure to implement throttling to prevent brute-force attacks.
Validate the verification code by comparing it against your database and checking the expiration and email. Make sure to invalidate all user sessions.
Email verification codes
We recommend reading through the email verification guide in the Copenhagen Book.
Update database
User table
Add a email_verified column (boolean).
import { Lucia } from "lucia";
export const lucia = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: env === "PRODUCTION" // set
Secure
flag in HTTPS}
},
getUserAttributes: (attributes) => {
return {
emailVerified: attributes.email_verified,
email: attributes.email
};
}
});
declare module "lucia" {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: {
email: string;
email_verified: boolean;
};
}
}
Email verification code table
Create a table for storing for email verification codes.
column type attributes
id any auto increment, etc
code string
user_id any unique
email string
expires_at Date
Generate verification code
The code should be valid for few minutes and linked to a single email.
import { TimeSpan, createDate } from "oslo";
import { generateRandomString, alphabet } from "oslo/crypto";
async function generateEmailVerificationCode(userId: string, email: string): Promise {
await db.table("email_verification_code").where("user_id", "=", userId).deleteAll();
const code = generateRandomString(8, alphabet("0-9"));
await db.table("email_verification_code").insert({
user_id: userId,
email,
code,
expires_at: createDate(new TimeSpan(15, "m")) // 15 minutes
});
return code;
}
You can also use alphanumeric codes.
const code = generateRandomString(6, alphabet("0-9", "A-Z"));
When a user signs up, set email_verified to false, create and send a verification code, and create a new session.
import { generateIdFromEntropySize } from "lucia";
app.post("/signup", async () => {
// ...
});
When resending verification emails, make sure to implement rate limiting based on user ID and IP address.
Verify code and email
Make sure to implement throttling to prevent brute-force attacks.
Validate the verification code by comparing it against your database and checking the expiration and email. Make sure to invalidate all user sessions.
import { isWithinExpirationDate } from "oslo";
import type { User } from "lucia";
app.post("/email-verification", async () => {
// ...
const { user } = await lucia.validateSession(sessionId);
if (!user) {
return new Response(null, {
status: 401
});
}
});
async function verifyVerificationCode(user: User, code: string): Promise {
await db.beginTransaction();
const databaseCode = await db
.table("email_verification_code")
.where("user_id", "=", user.id)
.get();
if (!databaseCode || databaseCode.code !== code) {
await db.commit();
return false;
}
await db.table("email_verification_code").where("id", "=", databaseCode.id).delete();
await db.commit();
}
Guidelines
Beta Was this translation helpful? Give feedback.
All reactions