Skip to content

Commit eb0aced

Browse files
committed
fix: persist auth instance across hot reloads
Store betterAuth instance on globalThis in development to prevent session loss when Next.js hot reloads the module.
1 parent ffffd82 commit eb0aced

1 file changed

Lines changed: 120 additions & 109 deletions

File tree

  • templates/packages/auth/src/lib

templates/packages/auth/src/lib/auth.ts

Lines changed: 120 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { nextCookies } from "better-auth/next-js"
77
import { createAuthMiddleware, emailOTP } from "better-auth/plugins"
88
import { eq } from "drizzle-orm"
99

10+
type BetterAuthInstance = ReturnType<typeof betterAuth>
11+
12+
const globalForAuth = globalThis as unknown as { auth?: BetterAuthInstance }
13+
1014
async function sendVerificationOTP({
1115
email,
1216
otp
@@ -24,121 +28,128 @@ async function sendVerificationOTP({
2428
})
2529
}
2630

27-
export const auth = betterAuth({
28-
secret: process.env.BETTER_AUTH_SECRET,
29-
basePath: "/auth",
30-
trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS
31-
? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(",")
32-
: ["http://localhost:2274"],
33-
advanced: {
34-
generateId: () => crypto.randomUUID()
35-
},
36-
logger: {
37-
disabled: false,
38-
disableColors: false,
39-
level: "error",
40-
log: (level, message, ...args) => {
41-
// Custom logging implementation
42-
console.log(`[${level}] ${message}`, ...args)
43-
}
44-
},
45-
database: drizzleAdapter(dbSchema.db, {
46-
provider: "pg",
47-
schema: {
48-
user: dbSchema.users,
49-
account: dbSchema.accounts,
50-
session: dbSchema.sessions,
51-
verification: dbSchema.verifications
52-
}
53-
}),
54-
account: {
55-
accountLinking: {
56-
enabled: true
57-
}
58-
},
59-
user: {
60-
additionalFields: {
61-
firstName: {
62-
type: "string",
63-
required: false
64-
},
65-
lastName: {
66-
type: "string",
67-
required: false
68-
},
69-
phone: {
70-
type: "string",
71-
required: false
31+
function createAuth(): BetterAuthInstance {
32+
return betterAuth({
33+
secret: process.env.BETTER_AUTH_SECRET,
34+
basePath: "/auth",
35+
trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS
36+
? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(",")
37+
: ["http://localhost:2274"],
38+
advanced: {
39+
generateId: () => crypto.randomUUID()
40+
},
41+
logger: {
42+
disabled: false,
43+
disableColors: false,
44+
level: "error",
45+
log: (level, message, ...args) => {
46+
console.log(`[${level}] ${message}`, ...args)
7247
}
73-
}
74-
},
75-
hooks: {
76-
after: createAuthMiddleware(async (ctx) => {
77-
const { newSession, newUser } = ctx.context
48+
},
49+
database: drizzleAdapter(dbSchema.db, {
50+
provider: "pg",
51+
schema: {
52+
user: dbSchema.users,
53+
account: dbSchema.accounts,
54+
session: dbSchema.sessions,
55+
verification: dbSchema.verifications
56+
}
57+
}),
58+
account: {
59+
accountLinking: {
60+
enabled: true
61+
}
62+
},
63+
user: {
64+
additionalFields: {
65+
firstName: {
66+
type: "string",
67+
required: false
68+
},
69+
lastName: {
70+
type: "string",
71+
required: false
72+
},
73+
phone: {
74+
type: "string",
75+
required: false
76+
}
77+
}
78+
},
79+
hooks: {
80+
after: createAuthMiddleware(async (ctx) => {
81+
const { newSession, newUser } = ctx.context
7882

79-
if (newSession) {
80-
await dbSchema.db
81-
.update(dbSchema.users)
82-
.set({
83-
lastSeenAt: new Date()
84-
} as unknown as typeof dbSchema.users.$inferInsert)
85-
.where(eq(dbSchema.users.id, newSession.user.id))
83+
if (newSession) {
84+
await dbSchema.db
85+
.update(dbSchema.users)
86+
.set({
87+
lastSeenAt: new Date()
88+
} as unknown as typeof dbSchema.users.$inferInsert)
89+
.where(eq(dbSchema.users.id, newSession.user.id))
8690

87-
const [user] = await dbSchema.db
88-
.select({
89-
id: dbSchema.users.id,
90-
email: dbSchema.users.email,
91-
firstName: dbSchema.users.firstName,
92-
lastName: dbSchema.users.lastName
93-
})
94-
.from(dbSchema.users)
95-
.where(eq(dbSchema.users.id, newSession.user.id))
96-
.limit(1)
91+
const [user] = await dbSchema.db
92+
.select({
93+
id: dbSchema.users.id,
94+
email: dbSchema.users.email,
95+
firstName: dbSchema.users.firstName,
96+
lastName: dbSchema.users.lastName
97+
})
98+
.from(dbSchema.users)
99+
.where(eq(dbSchema.users.id, newSession.user.id))
100+
.limit(1)
97101

98-
if (user?.email) {
99-
await track({
100-
event: newUser ? "USER_SIGNED_UP" : "USER_SIGNED_IN",
101-
userId: user.id,
102-
user: {
103-
id: user.id,
104-
email: user.email,
105-
firstName: user.firstName,
106-
lastName: user.lastName
107-
}
108-
})
102+
if (user?.email) {
103+
await track({
104+
event: newUser ? "USER_SIGNED_UP" : "USER_SIGNED_IN",
105+
userId: user.id,
106+
user: {
107+
id: user.id,
108+
email: user.email,
109+
firstName: user.firstName,
110+
lastName: user.lastName
111+
}
112+
})
113+
}
109114
}
115+
})
116+
},
117+
plugins: [
118+
emailOTP({
119+
sendVerificationOTP
120+
}),
121+
nextCookies()
122+
],
123+
session: {
124+
expiresIn: 60 * 60 * 24 * 7,
125+
updateAge: 60 * 60 * 24,
126+
cookieCache: {
127+
enabled: true,
128+
maxAge: 60 * 5
110129
}
111-
})
112-
},
113-
plugins: [
114-
emailOTP({
115-
sendVerificationOTP
116-
}),
117-
nextCookies()
118-
],
119-
session: {
120-
expiresIn: 60 * 60 * 24 * 7,
121-
updateAge: 60 * 60 * 24,
122-
cookieCache: {
123-
enabled: true,
124-
maxAge: 60 * 5
125-
}
126-
},
127-
socialProviders: {
128-
google: {
129-
clientId: process.env.GOOGLE_CLIENT_ID || "",
130-
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
131-
enabled: Boolean(
132-
process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
133-
),
134-
mapProfileToUser: async (profile) => {
135-
return {
136-
name: `${profile.given_name} ${profile.family_name}`,
137-
firstName: profile.given_name,
138-
lastName: profile.family_name,
139-
image: profile.picture
130+
},
131+
socialProviders: {
132+
google: {
133+
clientId: process.env.GOOGLE_CLIENT_ID || "",
134+
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
135+
enabled: Boolean(
136+
process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
137+
),
138+
mapProfileToUser: async (profile) => {
139+
return {
140+
name: `${profile.given_name} ${profile.family_name}`,
141+
firstName: profile.given_name,
142+
lastName: profile.family_name,
143+
image: profile.picture
144+
}
140145
}
141146
}
142147
}
143-
}
144-
})
148+
})
149+
}
150+
151+
export const auth = globalForAuth.auth ?? createAuth()
152+
153+
if (process.env.NODE_ENV !== "production") {
154+
globalForAuth.auth = auth
155+
}

0 commit comments

Comments
 (0)