Sessions not working

I have Hono as my backend and Next as my frontend. Hono and Next are on different codebases, I'm not using Hono in Next, I'm using it seperately. These are the issues I'm facing: 1. When I logged in everything works as expected, however when I sign-out the session gets deleted in my database however the cookie stays and therefore I stay on the protected dashboard page 2. When I add secure cookies and/or add session caching, on login the session gets created in the backend but no cookie gets generated
Solution:
Marking this as solved as it's a problem made by me and not the library
Jump to solution
13 Replies
Kazz
KazzOP5d ago
Here is my hono auth.ts
import { db } from '@/db/client.js'
import { betterAuth } from 'better-auth'
import { openAPI, username } from 'better-auth/plugins'
import { mongodbAdapter } from 'better-auth/adapters/mongodb'

const origins = ['http://localhost:3000']

export const auth = betterAuth({
database: mongodbAdapter(db),

emailAndPassword: {
enabled: true,
autoSignIn: false,
},

user: {
additionalFields: {
role: { type: 'string', required: true, defaultValue: 'client' },
company: { type: 'string', required: false, defaultValue: null, references: { model: 'Company', field: 'id', onDelete: 'cascade' } },
},
},

session: { cookieCache: { enabled: true } },

advanced: { useSecureCookies: true },

trustedOrigins: origins,

plugins: [username(), openAPI()],
})
import { db } from '@/db/client.js'
import { betterAuth } from 'better-auth'
import { openAPI, username } from 'better-auth/plugins'
import { mongodbAdapter } from 'better-auth/adapters/mongodb'

const origins = ['http://localhost:3000']

export const auth = betterAuth({
database: mongodbAdapter(db),

emailAndPassword: {
enabled: true,
autoSignIn: false,
},

user: {
additionalFields: {
role: { type: 'string', required: true, defaultValue: 'client' },
company: { type: 'string', required: false, defaultValue: null, references: { model: 'Company', field: 'id', onDelete: 'cascade' } },
},
},

session: { cookieCache: { enabled: true } },

advanced: { useSecureCookies: true },

trustedOrigins: origins,

plugins: [username(), openAPI()],
})
. And here is my frontend auth-client
import { env } from '@/env'
import { createAuthClient } from 'better-auth/react'
import { inferAdditionalFields, usernameClient } from 'better-auth/client/plugins'

export const { signIn, signUp, signOut, updateUser, getSession, useSession } = createAuthClient({
baseURL: env.NEXT_PUBLIC_BACKEND_URL,
plugins: [inferAdditionalFields({ user: { role: { type: 'string', required: true }, company: { type: 'string', required: false } } }), usernameClient()],
})
import { env } from '@/env'
import { createAuthClient } from 'better-auth/react'
import { inferAdditionalFields, usernameClient } from 'better-auth/client/plugins'

export const { signIn, signUp, signOut, updateUser, getSession, useSession } = createAuthClient({
baseURL: env.NEXT_PUBLIC_BACKEND_URL,
plugins: [inferAdditionalFields({ user: { role: { type: 'string', required: true }, company: { type: 'string', required: false } } }), usernameClient()],
})
and my middleware.ts
import { getSessionCookie } from 'better-auth/cookies'
import { NextRequest, NextResponse } from 'next/server'

export async function middleware(request: NextRequest) {
const sessionCookie = getSessionCookie(request)

const isAuthPage = request.nextUrl.pathname.startsWith('/auth')

if (isAuthPage && !sessionCookie) return NextResponse.next()
if (isAuthPage && sessionCookie) return NextResponse.redirect(new URL('/dashboard', request.url))

if (!sessionCookie) return NextResponse.redirect(new URL('/', request.url))

return NextResponse.next()
}

export const config = { matcher: ['/dashboard', '/auth/:path'] }
import { getSessionCookie } from 'better-auth/cookies'
import { NextRequest, NextResponse } from 'next/server'

export async function middleware(request: NextRequest) {
const sessionCookie = getSessionCookie(request)

const isAuthPage = request.nextUrl.pathname.startsWith('/auth')

if (isAuthPage && !sessionCookie) return NextResponse.next()
if (isAuthPage && sessionCookie) return NextResponse.redirect(new URL('/dashboard', request.url))

if (!sessionCookie) return NextResponse.redirect(new URL('/', request.url))

return NextResponse.next()
}

export const config = { matcher: ['/dashboard', '/auth/:path'] }
I even tried signing in and out using the /api/auth/reference, however in the cookies of the backend url as well, same thing happens. 1. Cookie stays 2. Getting session returns null 3. Signing our returns success but nothing happens in terms of deleting the cookie. As for trying it with secure cookie or cached cookie. 1. Login success 2. Getting session = null :0
We’re trying to add a Cookie header, but browsers often omit them for cross-origin requests for various security reasons. If it’s not working, that’s probably why. Here are the requirements for it to work:
- The browser URL must be on the same domain as the server URL.
- The connection must be made over HTTPS.
We’re trying to add a Cookie header, but browsers often omit them for cross-origin requests for various security reasons. If it’s not working, that’s probably why. Here are the requirements for it to work:
- The browser URL must be on the same domain as the server URL.
- The connection must be made over HTTPS.
bekacru
bekacru5d ago
is it on production or local? for prod you need to congiure cross sub domain cookies or if it's across domains, you'd need to use something like a bearer token instead to aovid using cross domain cookies
Kazz
KazzOP5d ago
Im not on production. Im currently on development, i created a simple project just to make sure everything is working fine for auth Frontend (Nextjs): http://localhost:3000 Backend (Hono node): http://localhost:4000 - In my frontend i have simply email sign-in and up with a protected dashboard page with sign out button (I only have auth client on the frontend) - In my backend i added the default settings/middleware/cors like in the documentation for Hono My plan for later on is to add my backend to api.mydomain.com and my frontend to mydomain.com as well as connecting an Expo app to my backend. But I still haven't reached that part yet
bekacru
bekacru5d ago
remove useSecureCookies if you're on local
Kazz
KazzOP5d ago
I did, same result with cache cookie And even without cache cookie, the cookies are not being deleted from the browser cookie despite them being deleted from the database
bekacru
bekacru5d ago
cookie cache a separte concern first make sure the session cookie is being set on your browser you can disable cookie cache for the time being to get this working as well in localhost, there should be no difference cookie wise between two ports
Kazz
KazzOP5d ago
Without cache and secure cookies 1. The cookie gets set 2. On sign out it doesnt get deleted
bekacru
bekacru5d ago
that's why it doesn't require setting up a subdomain cookie secure cookie shouldn't work locally at all it requires https connection
Kazz
KazzOP5d ago
Yes i removed it 😅
bekacru
bekacru5d ago
cookie cache is just a caching layer. don't worry about it now. get the session token working first
Kazz
KazzOP5d ago
I think i found the problem It something I did and totally forgot about for Hono itself. I've changed the way the Hono serves the application by bypassing some stuff so that my socket works
const httpServer = createServer(async (req, res) => {
try {
// Handle WebSocket upgrade requests separately
if (req.headers.upgrade && req.headers.upgrade.toLowerCase() === 'websocket') {
// Let the WebSocketServer handle this
return
}

// Convert Node.js request to Web standard Request
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`)
const headers = new Headers()
Object.entries(req.headers).forEach(([key, value]) => {
if (value) headers.set(key, Array.isArray(value) ? value.join(', ') : value)
})

const body = req.method !== 'GET' && req.method !== 'HEAD' ? req : null
// Create readable stream from request body
let bodyStream = null
if (body) {
bodyStream = new ReadableStream({
start(controller) {
body.on('data', chunk => controller.enqueue(chunk))
body.on('message', chunk => controller.enqueue(chunk))
body.on('end', () => controller.close())
body.on('error', err => controller.error(err))
},
})
}

const request = new Request(url.toString(), { method: req.method, headers, body: bodyStream })

const response = await app.fetch(request)

// Write the response back to the Node.js response object
res.writeHead(response.status, Object.fromEntries(response.headers.entries()))

const responseBody = await response.arrayBuffer()
res.end(Buffer.from(responseBody))
} catch (err) {
console.error('Error handling request:', err)
res.writeHead(500)
res.end('Internal Server Error')
}
})
const httpServer = createServer(async (req, res) => {
try {
// Handle WebSocket upgrade requests separately
if (req.headers.upgrade && req.headers.upgrade.toLowerCase() === 'websocket') {
// Let the WebSocketServer handle this
return
}

// Convert Node.js request to Web standard Request
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`)
const headers = new Headers()
Object.entries(req.headers).forEach(([key, value]) => {
if (value) headers.set(key, Array.isArray(value) ? value.join(', ') : value)
})

const body = req.method !== 'GET' && req.method !== 'HEAD' ? req : null
// Create readable stream from request body
let bodyStream = null
if (body) {
bodyStream = new ReadableStream({
start(controller) {
body.on('data', chunk => controller.enqueue(chunk))
body.on('message', chunk => controller.enqueue(chunk))
body.on('end', () => controller.close())
body.on('error', err => controller.error(err))
},
})
}

const request = new Request(url.toString(), { method: req.method, headers, body: bodyStream })

const response = await app.fetch(request)

// Write the response back to the Node.js response object
res.writeHead(response.status, Object.fromEntries(response.headers.entries()))

const responseBody = await response.arrayBuffer()
res.end(Buffer.from(responseBody))
} catch (err) {
console.error('Error handling request:', err)
res.writeHead(500)
res.end('Internal Server Error')
}
})
I'll figure out how to allow better-auth to bypass this as well
Solution
Kazz
Kazz5d ago
Marking this as solved as it's a problem made by me and not the library
Henrik
Henrik5d ago
I have a very similar set up and I either run into cors issues or my cookies don't get send to the backend. Is it recommended to have the hono server fully decoupled from my frontend (next.js) and still use sessions?

Did you find this page helpful?