Skip to content

Commit 41df5c6

Browse files
authored
Merge pull request #76 from refactor-group/optimistic_nextjs_middleware_auth_check
Add a middleware layer, handle optimistic auth check on basic routes
2 parents 6b89869 + fee8f3a commit 41df5c6

File tree

4 files changed

+76
-10
lines changed

4 files changed

+76
-10
lines changed

src/lib/api/user-session.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const loginUser = async (
2020
var userSession: UserSession = defaultUserSession();
2121
var err: string = "";
2222

23-
const data = await axios
23+
await axios
2424
.post(
2525
`${siteConfig.env.backendServiceURL}/login`,
2626
{
@@ -45,8 +45,8 @@ export const loginUser = async (
4545
// deserialize successfully, then downstream operations will see the default
4646
// userSessionData in state and we will experience subtle Bugs. We should consider
4747
// how best we want to handle this. Ex. clear auth cookie?
48-
console.debug("userSessionData: ", userSessionData)
49-
throw { message: 'Login Failed to produce valid User Session data' }
48+
console.debug("userSessionData: ", userSessionData);
49+
throw { message: "Login Failed to produce valid User Session data" };
5050
}
5151
})
5252
.catch(function (error: AxiosError) {
@@ -73,7 +73,7 @@ export const logoutUser = async (): Promise<string> => {
7373
const data = await axios
7474
.get(`${siteConfig.env.backendServiceURL}/logout`, {
7575
withCredentials: true,
76-
setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend
76+
setTimeout: 5000, // 5 seconds before timing out trying to log out with the backend
7777
})
7878
.then(function (response: AxiosResponse) {
7979
// handle success

src/lib/providers/auth-store-provider.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
"use client";
12
// The purpose of this provider is to provide compatibility with
23
// Next.js re-rendering and component caching
3-
"use client";
44

5-
import { type ReactNode, createContext, useRef, useContext } from "react";
5+
import {
6+
type ReactNode,
7+
createContext,
8+
useRef,
9+
useContext,
10+
useMemo,
11+
useEffect,
12+
useState,
13+
} from "react";
614
import { type StoreApi, useStore } from "zustand";
715

816
import { type AuthStore, createAuthStore } from "@/lib/stores/auth-store";
@@ -15,9 +23,22 @@ export interface AuthStoreProviderProps {
1523
}
1624

1725
export const AuthStoreProvider = ({ children }: AuthStoreProviderProps) => {
18-
const storeRef = useRef<StoreApi<AuthStore>>(undefined);
19-
if (!storeRef.current) {
20-
storeRef.current = createAuthStore();
26+
const storeRef = useRef<StoreApi<AuthStore> | null>(null);
27+
const [isInitialized, setIsInitialized] = useState(false);
28+
29+
useEffect(() => {
30+
if (typeof window !== "undefined") {
31+
// Now safe to access localStorage
32+
const storedValue = localStorage.getItem("auth-store");
33+
const initialState = storedValue ? JSON.parse(storedValue).state : null;
34+
storeRef.current = createAuthStore(initialState);
35+
setIsInitialized(true);
36+
}
37+
}, []);
38+
39+
// Ensure store is initialized before rendering the provider
40+
if (!isInitialized) {
41+
return null; // or return a loading component
2142
}
2243

2344
return (

src/lib/stores/auth-store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const createAuthStore = (initState: AuthState = defaultInitState) => {
5050
}),
5151
{
5252
name: "auth-store",
53-
storage: createJSONStorage(() => sessionStorage),
53+
storage: createJSONStorage(() => localStorage),
5454
}
5555
)
5656
)

src/middleware.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
3+
// 1. Specify protected and public routes
4+
const protectedRoutes = [
5+
"/dashboard",
6+
"/coaching-sessions",
7+
"/settings",
8+
"/profile",
9+
];
10+
const publicRoutes = ["/"];
11+
12+
export default async function middleware(req: NextRequest) {
13+
// 2. Check if the current route is protected or public
14+
const path = req.nextUrl.pathname;
15+
const isProtectedRoute = protectedRoutes.some((route) =>
16+
path.startsWith(route)
17+
);
18+
const isPublicRoute = publicRoutes.some((route) => path === route);
19+
20+
// 3. Get the session from the cookie
21+
const sessionCookie = req.cookies.get("id");
22+
const session = sessionCookie?.value;
23+
24+
// 4. Redirect to / if the user is not authenticated
25+
// 4b. TODO: Check session validity/expiration?
26+
if (isProtectedRoute && !session) {
27+
return NextResponse.redirect(new URL("/", req.nextUrl));
28+
}
29+
30+
// 5. Redirect to /dashboard if the user is authenticated
31+
if (
32+
isPublicRoute &&
33+
session &&
34+
!req.nextUrl.pathname.startsWith("/dashboard")
35+
) {
36+
return NextResponse.redirect(new URL("/dashboard", req.nextUrl));
37+
}
38+
39+
return NextResponse.next();
40+
}
41+
42+
// Routes Middleware should not run on
43+
export const config = {
44+
matcher: ["/((?!api|_next/static|_next/image|favicon.ico|.*\\.png$).*)"],
45+
};

0 commit comments

Comments
 (0)