H
Hono•3mo ago
sithu

Hono and Better-Auth

Hi there, I know this is the better-auth thing, but just asking for a help. This is my auth from api:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";

import { db } from "../db";
import { account, session, user, verification } from "../db/schemas/auth-table";
import {
FRONTEND_URL,
GOOGLE_CLIENT_DEV_ID,
GOOGLE_CLIENT_DEV_SECRET,
GOOGLE_CLIENT_PROD_ID,
GOOGLE_CLIENT_PROD_SECRET,
NODE_ENV
} from "../env";

const isProd = NODE_ENV === "production";

export const auth = betterAuth({
socialProviders: {
google: {
clientId: isProd ? GOOGLE_CLIENT_PROD_ID : GOOGLE_CLIENT_DEV_ID,
clientSecret: isProd ? GOOGLE_CLIENT_PROD_SECRET : GOOGLE_CLIENT_DEV_SECRET
}
},
database: drizzleAdapter(db, {
provider: "pg",
schema: { user, session, account, verification }
}),
session: {
expiresIn: 60 * 60 * 24 * 30,
freshAge: 60 * 60 * 24 * 1
},
trustedOrigins: [FRONTEND_URL]

});
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";

import { db } from "../db";
import { account, session, user, verification } from "../db/schemas/auth-table";
import {
FRONTEND_URL,
GOOGLE_CLIENT_DEV_ID,
GOOGLE_CLIENT_DEV_SECRET,
GOOGLE_CLIENT_PROD_ID,
GOOGLE_CLIENT_PROD_SECRET,
NODE_ENV
} from "../env";

const isProd = NODE_ENV === "production";

export const auth = betterAuth({
socialProviders: {
google: {
clientId: isProd ? GOOGLE_CLIENT_PROD_ID : GOOGLE_CLIENT_DEV_ID,
clientSecret: isProd ? GOOGLE_CLIENT_PROD_SECRET : GOOGLE_CLIENT_DEV_SECRET
}
},
database: drizzleAdapter(db, {
provider: "pg",
schema: { user, session, account, verification }
}),
session: {
expiresIn: 60 * 60 * 24 * 30,
freshAge: 60 * 60 * 24 * 1
},
trustedOrigins: [FRONTEND_URL]

});
This is my Hono root route:
const app = new Hono<Context>().basePath("/api");

app.use(
"*",
cors({
origin: [FRONTEND_URL],
allowHeaders: ["Content-Type", "Authorization"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length"],
maxAge: 600,
credentials: true
})
);

app.on(["POST", "GET"], "/auth/**", (c) => auth.handler(c.req.raw));

app.route("/authorize", authroize);
const app = new Hono<Context>().basePath("/api");

app.use(
"*",
cors({
origin: [FRONTEND_URL],
allowHeaders: ["Content-Type", "Authorization"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length"],
maxAge: 600,
credentials: true
})
);

app.on(["POST", "GET"], "/auth/**", (c) => auth.handler(c.req.raw));

app.route("/authorize", authroize);
This is my Hono middleware:
import { createMiddleware } from "hono/factory";

import { auth } from "../../libs/auth";
import type { ErrorResponse } from "../../libs/types";
import type { Context } from "../context";

export const loggedIn = createMiddleware<Context>(async (c, next) => {
const session = await auth.api.getSession({ headers: c.req.raw.headers });

if (!session) {
return c.json<ErrorResponse>({ success: false, message: "Unauthorized" }, 401);
}

c.set("user", session.user);
c.set("session", session.session);

await next();
});
import { createMiddleware } from "hono/factory";

import { auth } from "../../libs/auth";
import type { ErrorResponse } from "../../libs/types";
import type { Context } from "../context";

export const loggedIn = createMiddleware<Context>(async (c, next) => {
const session = await auth.api.getSession({ headers: c.req.raw.headers });

if (!session) {
return c.json<ErrorResponse>({ success: false, message: "Unauthorized" }, 401);
}

c.set("user", session.user);
c.set("session", session.session);

await next();
});
This is auth client from web:
import { PUBLIC_API } from "$env/static/public";
import { createAuthClient } from "better-auth/svelte";

export const authClient = createAuthClient({ baseURL: `${PUBLIC_API}/auth` });
import { PUBLIC_API } from "$env/static/public";
import { createAuthClient } from "better-auth/svelte";

export const authClient = createAuthClient({ baseURL: `${PUBLIC_API}/auth` });
This is requireAuth function to use in +page.server.ts:
export async function requireAuth() {
const res = await fetch(`${PUBLIC_API}/authorize/current`, {
method: "POST",
credentials: "include"
});

if (!res.ok) {
console.error("Failed to authorize", res);
throw new Error("Unauthorized from web");
}

const json = (await res.json()) as SuccessResponse<SuccessType>;

const data = {
session: json.data.session,
user: json.data.user
};

const user = data.user;
console.log("data user", user);
}
export async function requireAuth() {
const res = await fetch(`${PUBLIC_API}/authorize/current`, {
method: "POST",
credentials: "include"
});

if (!res.ok) {
console.error("Failed to authorize", res);
throw new Error("Unauthorized from web");
}

const json = (await res.json()) as SuccessResponse<SuccessType>;

const data = {
session: json.data.session,
user: json.data.user
};

const user = data.user;
console.log("data user", user);
}
But I am getting Unauthorized responsive back although I successfully logged in. I can do sign-in and sign-out, but I am just getting authorized when I use loggedIn middelware. How can I fix that?
3 Replies
ambergristle
ambergristle•3mo ago
sounds like an issue with the cookies could be cross-origin, or something about how sveltekit handles cookies i would start by checking whether - credentials are included in the response server-side - whether they're also available client side - that they're being included in the outgoing (non-login) requests
Oliver Pan
Oliver Pan•3mo ago
I've spent hours trying to figure this out. Found a couple of interesting things: 1. Better auth sign-up and login works 2. auth.api.getSession doesn't appear to be triggered in Hono middleware. Despite having the Bearer token passed as an authorization header, the bearer-auth function doesn't trigger. 3. Since better auth use baseURL to then check for auth, is it because the better auth client in the middleware is trying to call another Hono route endpoint, causing loops? Workaround ## We can just use db to fetch the session and user data ourselves:
const getSession = async (headers: Headers) => {
const token = headers.get("authorization")?.split(" ")[1];
const sessionData = await db
.select()
.from(session)
.where(eq(session.token, token || ""))
.limit(1);

if (!sessionData) {
return null;
}

const [userData] = await db
.select()
.from(members)
.where(eq(members.id, sessionData[0].userId))
.limit(1);

return {
session: sessionData[0],
user: userData,
};
};

// Authentication middleware
export const authMiddleware = createMiddleware(async (c, next) => {
const headers = c.req.raw.headers;

const sessionData = await getSession(headers);

if (!sessionData) {
throw new HTTPException(401, { message: "Not authenticated" });
}

// Store session in context for later use
c.set("session", sessionData.session);
c.set("user", sessionData.user);

await next();
});
const getSession = async (headers: Headers) => {
const token = headers.get("authorization")?.split(" ")[1];
const sessionData = await db
.select()
.from(session)
.where(eq(session.token, token || ""))
.limit(1);

if (!sessionData) {
return null;
}

const [userData] = await db
.select()
.from(members)
.where(eq(members.id, sessionData[0].userId))
.limit(1);

return {
session: sessionData[0],
user: userData,
};
};

// Authentication middleware
export const authMiddleware = createMiddleware(async (c, next) => {
const headers = c.req.raw.headers;

const sessionData = await getSession(headers);

if (!sessionData) {
throw new HTTPException(401, { message: "Not authenticated" });
}

// Store session in context for later use
c.set("session", sessionData.session);
c.set("user", sessionData.user);

await next();
});
Happy to see if others figured why 🤔
Arjix
Arjix•3mo ago
you prob don't want the || "", sounds like a bug waiting to happen if there is no token, then there is no session simple as and even if it cannot cause a bug, you are wasting precious time by querying the database for a row that matches where session.token = ""

Did you find this page helpful?