feat: add composable proxy/middleware helpers#348
Conversation
50149a9 to
1a9b2bb
Compare
The manual header-handling example was superseded by the handleAuthkitHeaders helper documented earlier in Setup.
| export default async function proxy(request: NextRequest) { | ||
| // For Next.js ≤15, use: export default async function middleware(request: NextRequest) { | ||
| // Get session, headers, and the WorkOS authorization URL for sign-in redirects | ||
| const { session, headers, authorizationUrl } = await authkit(request); |
There was a problem hiding this comment.
Maybe a silly question!
I'm looking at Vercel's example for setting headers in a proxy. The general shape of proxies seems to be do some stuff and then return a Response or NextResponse. It feels like we're hiding that a bit here with handleAuthkitHandlers.
What's preventing us from having authkit return headers that we can use directly in a more conventional-looking proxy? I'm sure this is missing something, but at a high level I'm thinking something more like...
export default async function proxy(request: NextRequest) {
const {
session,
requestHeaders,
responseHeaders,
authorizationUrl
} = await authkit(request);
const { pathname } = request.nextUrl;
// Redirect unauthenticated users on protected routes
if (pathname.startsWith('/app') && !session.user && authorizationUrl) {
return NextResponse.redirect(authorizationUrl)
}
return NextResponse.next({ request: { headers: requestHeaders} }, headers: responseHeaders })
}There was a problem hiding this comment.
Not a silly question at all!
You can do exactly what you're describing! We export the primitives:
import { authkit, partitionAuthkitHeaders, applyResponseHeaders } from '@workos-inc/authkit-nextjs';
export default async function proxy(request: NextRequest) {
const { session, headers, authorizationUrl } = await authkit(request);
const { requestHeaders, responseHeaders } = partitionAuthkitHeaders(request, headers);
if (!session.user && authorizationUrl) {
return applyResponseHeaders(NextResponse.redirect(authorizationUrl), responseHeaders);
}
return applyResponseHeaders(
NextResponse.next({ request: { headers: requestHeaders } }),
responseHeaders
);
}handleAuthkitHeaders() is just a convenience wrapper around this pattern.
As for why authkit() doesn't return requestHeaders/responseHeaders directly - it already returns headers and changing that would be a breaking change. We could add them alongside headers and deprecate, but that felt like more complexity than the helper approach.
The reason applyResponseHeaders() exists at all is Set-Cookie handling - you need append() not set(), otherwise only the last cookie survives. Easy footgun.
Should we update the README to show both approaches? Happy to make handleAuthkitHeaders feel less like a black box.
There was a problem hiding this comment.
Thanks for the explanation! Good to know about the Set-Cookie footgun. I think the README is good as-is. Just wanted a gut check on whether this approach would feel at least somewhat familiar to folks used to writing Next.js proxies.
| export default async function proxy(request: NextRequest) { | ||
| // For Next.js ≤15, use: export default async function middleware(request: NextRequest) { | ||
| // Get session, headers, and the WorkOS authorization URL for sign-in redirects | ||
| const { session, headers, authorizationUrl } = await authkit(request); |
There was a problem hiding this comment.
Thanks for the explanation! Good to know about the Set-Cookie footgun. I think the README is good as-is. Just wanted a gut check on whether this approach would feel at least somewhat familiar to folks used to writing Next.js proxies.
Summary
handleAuthkitHeaders(),partitionAuthkitHeaders(), andapplyResponseHeaders()helpers for composing custom proxy/middleware with AuthKitsession.tsto use new helpers internally (dogfooding)Why
authkitMiddleware()is opaque. Users could not inject custom logic (rate limiting, redirects, A/B testing) while preserving AuthKit's session handling. This caused:NextResponsex-workos-session) leaked to browserSet-Cookierequiresappend(), notset())Cache-Control: no-storewhen cookies presentUsage