Skip to content

Commit 4b63c82

Browse files
committed
feat: nextjs middleware and session types
1 parent 6c02b7b commit 4b63c82

File tree

5 files changed

+95
-31
lines changed

5 files changed

+95
-31
lines changed

examples/opennextjs/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opennextjs",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"private": true,
55
"scripts": {
66
"clean": "rm -rf .open-next && rm -rf .wrangler && rm -rf node_modules && rm -rf .next",
@@ -39,8 +39,8 @@
3939
"tailwind-merge": "^3.2.0"
4040
},
4141
"devDependencies": {
42-
"@cloudflare/workers-types": "4.20250606.0",
43-
"@opennextjs/cloudflare": "^1.0.1",
42+
"@cloudflare/workers-types": "^4.20250813.0",
43+
"@opennextjs/cloudflare": "^1.6.5",
4444
"@tailwindcss/postcss": "^4",
4545
"@types/node": "^20",
4646
"@types/react": "^19",

examples/opennextjs/src/app/page.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,11 @@ import authClient from "@/auth/authClient";
44
import { Button } from "@/components/ui/button";
55
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
66
import { Github, Package } from "lucide-react";
7-
import { useRouter } from "next/navigation";
8-
import { useEffect, useState } from "react";
7+
import { useState } from "react";
98

109
export default function Home() {
1110
const { data: session, error: sessionError } = authClient.useSession();
1211
const [isAuthActionInProgress, setIsAuthActionInProgress] = useState(false);
13-
const router = useRouter();
14-
15-
// Redirect to dashboard if already logged in
16-
useEffect(() => {
17-
if (session) {
18-
router.push("/dashboard");
19-
}
20-
}, [session, router]);
2112

2213
const handleAnonymousLogin = async () => {
2314
setIsAuthActionInProgress(true);
@@ -29,9 +20,9 @@ export default function Home() {
2920
setIsAuthActionInProgress(false);
3021
alert(`Anonymous login failed: ${result.error.message}`);
3122
} else {
32-
// Login succeeded, redirect to dashboard
33-
// Don't reset loading state here - let the redirect happen
34-
window.location.href = "/dashboard";
23+
// Login succeeded - middleware will handle redirect to dashboard
24+
// Force a page refresh to trigger middleware redirect
25+
window.location.reload();
3526
}
3627
} catch (e: any) {
3728
setIsAuthActionInProgress(false);
Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,76 @@
1-
import { NextResponse } from "next/server";
1+
import type { CloudflareSessionResponse } from "better-auth-cloudflare";
22
import type { NextRequest } from "next/server";
3-
import { initAuth } from "@/auth"; // Adjust if your auth init is elsewhere
3+
import { NextResponse } from "next/server";
44

55
export async function middleware(request: NextRequest) {
66
const { pathname } = request.nextUrl;
77

8-
// Protect the /dashboard route
9-
if (pathname.startsWith("/dashboard")) {
8+
// Routes that require authentication
9+
const protectedRoutes = ["/dashboard"];
10+
// Routes that should redirect to dashboard if already authenticated
11+
const authRoutes = ["/", "/sign-in"];
12+
13+
const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route));
14+
const isAuthRoute = authRoutes.includes(pathname);
15+
16+
// Only check session for routes that need auth logic
17+
if (isProtectedRoute || isAuthRoute) {
1018
try {
11-
const authInstance = await initAuth();
12-
const session = await authInstance.api.getSession({ headers: request.headers });
19+
// Use the auth API route instead of importing better-auth directly
20+
// This avoids Edge Runtime dynamic code evaluation issues with @opennextjs/cloudflare
21+
const sessionResponse = await fetch(new URL("/api/auth/get-session", request.url), {
22+
method: "GET",
23+
headers: {
24+
cookie: request.headers.get("cookie") || "",
25+
},
26+
});
27+
28+
const isAuthenticated = sessionResponse.ok;
29+
let sessionData: CloudflareSessionResponse | null = null;
1330

14-
if (!session) {
15-
// User is not authenticated, redirect to home page
31+
if (isAuthenticated) {
32+
try {
33+
sessionData = await sessionResponse.json();
34+
// Double-check that we have a valid session
35+
if (!sessionData?.session || !sessionData.session.userId) {
36+
sessionData = null;
37+
}
38+
} catch {
39+
sessionData = null;
40+
}
41+
}
42+
43+
// Handle protected routes - redirect to home if not authenticated
44+
if (isProtectedRoute && !sessionData) {
1645
const url = request.nextUrl.clone();
1746
url.pathname = "/";
1847
return NextResponse.redirect(url);
1948
}
49+
50+
// Handle auth routes - redirect to dashboard if already authenticated
51+
if (isAuthRoute && sessionData) {
52+
const url = request.nextUrl.clone();
53+
url.pathname = "/dashboard";
54+
return NextResponse.redirect(url);
55+
}
56+
57+
// Optional: Log geolocation data for authenticated users
58+
if (sessionData) {
59+
console.log("Authenticated request from:", {
60+
country: sessionData.session.country,
61+
city: sessionData.session.city,
62+
timezone: sessionData.session.timezone,
63+
});
64+
}
2065
} catch (error) {
2166
console.error("Middleware error:", error);
22-
// Optional: redirect to an error page or home on error
23-
const url = request.nextUrl.clone();
24-
url.pathname = "/"; // Or an error page like '/auth-error'
25-
return NextResponse.redirect(url);
67+
68+
// On error, only redirect protected routes to avoid redirect loops
69+
if (isProtectedRoute) {
70+
const url = request.nextUrl.clone();
71+
url.pathname = "/";
72+
return NextResponse.redirect(url);
73+
}
2674
}
2775
}
2876

@@ -32,5 +80,7 @@ export async function middleware(request: NextRequest) {
3280
export const config = {
3381
matcher: [
3482
"/dashboard/:path*", // Protects /dashboard and all its sub-routes
83+
"/", // Home page - redirect to dashboard if authenticated
84+
"/sign-in", // Sign-in page - redirect to dashboard if authenticated
3585
],
3686
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "better-auth-cloudflare",
3-
"version": "0.2.3",
3+
"version": "0.2.4",
44
"description": "Seamlessly integrate better-auth with Cloudflare Workers, D1, Hyperdrive, KV, R2, and geolocation services.",
55
"author": "Zach Grimaldi",
66
"repository": {

src/types.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { KVNamespace } from "@cloudflare/workers-types";
2-
import type { AuthContext } from "better-auth";
2+
import type { AuthContext, Session, User } from "better-auth";
33
import type { DrizzleAdapterConfig } from "better-auth/adapters/drizzle";
44
import type { FieldAttribute } from "better-auth/db";
55
import type { drizzle as d1Drizzle } from "drizzle-orm/d1";
6-
import type { drizzle as postgresDrizzle } from "drizzle-orm/postgres-js";
76
import type { drizzle as mysqlDrizzle } from "drizzle-orm/mysql2";
7+
import type { drizzle as postgresDrizzle } from "drizzle-orm/postgres-js";
88

99
export interface CloudflarePluginOptions {
1010
/**
@@ -81,6 +81,29 @@ export interface CloudflareGeolocation {
8181
longitude?: string | null;
8282
}
8383

84+
/**
85+
* Session type enhanced with Cloudflare geolocation data
86+
* This is what gets returned by /api/auth/get-session when using better-auth-cloudflare
87+
*/
88+
export interface CloudflareSession extends Session {
89+
timezone?: string | null;
90+
city?: string | null;
91+
country?: string | null;
92+
region?: string | null;
93+
regionCode?: string | null;
94+
colo?: string | null;
95+
latitude?: string | null;
96+
longitude?: string | null;
97+
}
98+
99+
/**
100+
* The response structure from /api/auth/get-session
101+
*/
102+
export interface CloudflareSessionResponse {
103+
session: CloudflareSession;
104+
user: User;
105+
}
106+
84107
/**
85108
* Minimal R2Bucket interface - only what we actually need for file storage
86109
* Avoids complex type conflicts between DOM and Cloudflare Worker types

0 commit comments

Comments
 (0)