Loss of Raw Request and Context in Middleware
Problem Statement
While integrating Clerk as an authentication service for our proof-of-concept, I encountered critical issues stemming from the loss of the original request object and context. This results in middleware not receiving the correct data needed for authentication, ultimately forcing me to abandon the use of JStack for this project.
Detailed Description
I examined JStack's current implementation and understood the design choices made. However, when developing the POC functionality, I ran into several issues related to authorization. My approach involved setting up secure connections between the client and server by creating a custom middleware for procedures.
For this POC, I decided to use Clerk as the authentication service. Initially, I followed Clerk's recommended approach (see Clerk Official Guide). However, the method failed because the context within the procedure did not match the context of the underlying Hono application. Consequently, I could not access the necessary authentication data in the middleware.
Example of Issue in Middleware
export const withClerkGuardMiddleware = j.middleware(async ({ c, next }) => {
const auth = getAuth(c); // auth is undefined because of altered context
// ...
});
Usage in procedures
export const privateProcedure = j.procedure.use(withClerkGuardMiddleware);
Second chance
Next, I attempted to use the @clerk/backend package directly in the middleware by creating a Clerk client:
export const withClerkGuardMiddleware = j.middleware(async ({ c, next }) => {
const clerkClient = createClerkClient({
secretKey: process.env.CLERK_SECRET_KEY,
publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
});
const { isSignedIn } = await clerkClient.authenticateRequest(c.req.raw);
});
Core Issue
The root problem is that the original request object (c.req.raw) has been altered in router.ts and the registerSubrouterMiddleware method. This modification results in the loss of critical properties such as request.url and host, making the authentication data incorrect. This behavior contradicts both common sense and the immutability principles promoted by frameworks like Hono and Next, leading to persistent errors and blocking further development.
Expected Behavior
- The raw request object should remain unmodified or be correctly forwarded in its original form.
- Middleware should have access to the full, unaltered request context to reliably retrieve authentication data.
- This would enable the use of Clerk's authentication methods and support secure middleware implementations without compromising the integrity of request data.
Impact
- Authentication Failures: Inability to access authentication data leads to failed Clerk integrations.
- Development Blockers: The architectural decisions in JStack hinder progress and force developers to seek alternatives.
- Inconsistent Request Data: Altered properties like url and host cause discrepancies that break expected behavior and introduce errors.
Hello wallwhite,
I am using clerk Middleware in jstack and I am not encountering the issue that you mentioned.
I am sharing my setup for clerk, let me know if it helps.
//jstack.ts
const authMiddleware = j.middleware(async ({ c, next }) => {
const { NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY } = env(c) as {
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: string;
CLERK_SECRET_KEY: string;
};
await honoClerkMiddleware({
publishableKey: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
secretKey: CLERK_SECRET_KEY,
})(c, async () => {
const auth = getAuth(c);
if (!auth?.userId) {
c.json({ message: "You are not logged in." }, 401);
return;
}
await next({ auth });
});
});
.
.
.
export const baseProcedure = j.procedure.use(databaseMiddleware);
export const publicProcedure = baseProcedure;
export const privateProcedure = publicProcedure.use(authMiddleware);
//middlware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isPublicRoute = createRouteMatcher([
"/sign-in(.*)",
"/sign-up(.*)",
"/api/webhooks(.*)",
]);
export default clerkMiddleware(async (auth, request) => {
if (!isPublicRoute(request)) {
await auth.protect();
}
});
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
// Always run for API routes
"/(api|trpc)(.*)",
],
};
Then when i use it inside procedure it works.
const auth = getAuth(c);
I got similar issue with NextAuth, generally when I call api, authorization is not in headers. I can get server session in server components. Any idea?
import { getServerSession } from "next-auth";
export const userRouter = j.router({
getProfile: publicProcedure.query(async ({ c, ctx }) => {
const session = await getServerSession(authOptions); //get null here
return c.superjson(session);
}),
});