KindeK
Kinde3y ago
9 replies
saM69420

Kinde + Bun + Hono + React

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;
Was this page helpful?