Hey I was just hoping to get some advice/validation on code I've written for using Kinde with a bun and hono app. I'm using the typescript SDK.
My session manager is storing all the session items in cookies as the next.js libary does. Then I have some custom middleware for protected routes.
My backend is serving up a react app, the cookies get sent with every request, and everything is working there. I have a
/api/me
endpoint that checks if the user is logged in. The react app calls that endpoint when it first loads to check if the user is logged in.
// auth.ts
import {
createKindeServerClient,
GrantType,
SessionManager,
UserType,
} from "@kinde-oss/kinde-typescript-sdk";
import { Hono, Context, MiddlewareHandler } from "hono";
import { getCookie, setCookie, deleteCookie } from "hono/cookie";
export const kindeClient = createKindeServerClient(
GrantType.AUTHORIZATION_CODE,
{
authDomain: process.env.KINDE_DOMAIN!,
clientId: process.env.KINDE_CLIENT_ID!,
clientSecret: process.env.KINDE_CLIENT_SECRET!,
redirectURL: process.env.KINDE_REDIRECT_URI!,
logoutRedirectURL: process.env.KINDE_LOGOUT_REDIRECT_URI!,
}
);
export const sessionManager = (c: Context): SessionManager => ({
async getSessionItem(key: string) {
const result = getCookie(c, key);
return result;
},
async setSessionItem(key: string, value: unknown) {
if (typeof value === "string") {
setCookie(c, key, value);
} else {
setCookie(c, key, JSON.stringify(value));
}
},
async removeSessionItem(key: string) {
deleteCookie(c, key);
},
async destroySession() {
["id_token", "access_token", "user", "refresh_token"].forEach((key) => {
deleteCookie(c, key);
});
},
});
export const protectRoute: MiddlewareHandler = async (c, next) => {
try {
const manager = sessionManager(c);
const isAuthenticated = await kindeClient.isAuthenticated(manager);
if (!isAuthenticated) {
return c.json({ error: "Unauthorized" }, 401);
}
await next();
} catch (e) {
console.error(e);
return c.json({ error: "Unauthorized" }, 401);
}
};
export const getUser: MiddlewareHandler<{
Variables: {
user: UserType;
};
}> = async (c, next) => {
try {
const manager = sessionManager(c);
const isAuthenticated = await kindeClient.isAuthenticated(manager);
if (!isAuthenticated) {
return c.json({ error: "Unauthorized" }, 401);
}
const profile = await kindeClient.getUserProfile(manager);
c.set("user", profile);
await next();
} catch (e) {
console.error(e);
return c.json({ error: "Unauthorized" }, 401);
}
};
export const authRoutes = new Hono()
.get("/logout", async (c) => {
const logoutUrl = await kindeClient.logout(sessionManager(c));
return c.redirect(logoutUrl.toString());
})
.get("/login", async (c) => {
const loginUrl = await kindeClient.login(sessionManager(c));
return c.redirect(loginUrl.toString());
})
.get("/register", async (c) => {
const registerUrl = await kindeClient.register(sessionManager(c));
return c.redirect(registerUrl.toString());
})
.get("/callback", async (c) => {
await kindeClient.handleRedirectToApp(
sessionManager(c),
new URL(c.req.url)
);
return c.redirect("/");
});
// app.ts
import { Hono } from "hono";
import { serveStatic } from "hono/bun";
import { authRoutes, getUser } from "./auth";
import expenseRoute from "./expenses";
const app = new Hono();
const apiRoutes = app
.basePath("/api")
.route("/expenses", expenseRoute)
.get("/me", getUser, async (c) => {
const user = await c.var.user;
return c.json({ user });
});
app.route("/", authRoutes);
// app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))
app.get("*", serveStatic({ root: "./frontend/dist" }));
app.get("*", serveStatic({ path: "./frontend/dist/index.html" }));
export default app;
export type ApiRoutes = typeof apiRoutes;