Skip to content

Commit b9b1389

Browse files
committed
Add auth-guard to prevent user from unauthorised back-tracking after log out
1 parent c23d1bd commit b9b1389

File tree

8 files changed

+190
-22
lines changed

8 files changed

+190
-22
lines changed

ai/usage-log.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,37 @@ Request to generate email regex validation.
361361

362362
---
363363

364+
## Entry 12
365+
366+
# Date/Time:
367+
2025-09-16 21:00
368+
369+
# Tool:
370+
GitHub Copilot (model: Claude Sonnet 4)
371+
372+
# Prompt/Command:
373+
Request to create a simple client-side auth guard that prevents browser back navigation to cached protected pages after logout.
374+
375+
# Output Summary:
376+
- Created AuthGuard component (/app/components/layout/AuthGuard.tsx) for layout-level protection
377+
- Implemented pathname-based token checking using Next.js usePathname hook
378+
- Added immediate redirect to login when no token found on protected routes
379+
- Integrated AuthGuard into root layout.tsx for automatic protection on all pages
380+
- Enhanced middleware with cookie clearing on redirects for complete logout state
381+
- Combined server-side cache control headers with client-side token verification
382+
383+
# Action Taken:
384+
- [ ] Accepted as-is
385+
- [x] Modified
386+
- [ ] Rejected
387+
388+
# Author Notes:
389+
- Validated AuthGuard catches browser back navigation edge case after logout
390+
- Confirmed minimal performance impact with simple token existence check
391+
- Security implications: client-side protection complements server-side middleware
392+
- UX improvements: immediate redirect prevents brief flash of protected content
393+
- Maintainability: simple, reusable component for future auth requirements
394+
395+
---
396+
364397

frontend/src/app/components/auth/LoginComponent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export default function LoginForm() {
127127
</div>
128128
</div>
129129

130-
<div className="flex justify-center ">
130+
<div className="flex justify-center mt-5">
131131
<Button onClick={handleLogin} className="w-full">
132132
Login
133133
</Button>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* AI Assistance Disclosure:
3+
* Tool: GitHub Copilot (model: Claude Sonnet 4), date: 2025-09-16
4+
* Purpose: To create a simple client-side auth guard that prevents browser back navigation to cached protected pages after logout.
5+
* Author Review: I validated correctness, security, and performance of the code.
6+
*
7+
*/
8+
9+
/**
10+
* Simple client-side auth guard that checks for token on page navigation.
11+
* This catches cases where browser back button bypasses middleware cache controls.
12+
*/
13+
14+
"use client";
15+
16+
import { useEffect } from "react";
17+
import { usePathname, useRouter } from "next/navigation";
18+
import { getToken } from "@/services/userServiceCookies";
19+
20+
export default function AuthGuard() {
21+
const pathname = usePathname();
22+
const router = useRouter();
23+
24+
// Check token immediately on mount and pathname changes
25+
useEffect(() => {
26+
// Skip auth routes
27+
if (pathname.startsWith("/auth")) {
28+
return;
29+
}
30+
31+
// Check for token
32+
const token = getToken();
33+
if (!token) {
34+
console.log("AuthGuard: No token found, redirecting to login");
35+
// Use setTimeout to ensure this runs after render
36+
setTimeout(() => {
37+
router.push("/auth/login");
38+
}, 0);
39+
}
40+
}, [pathname, router]);
41+
42+
// Add popstate listener specifically for browser back/forward navigation
43+
useEffect(() => {
44+
const handlePopState = () => {
45+
if (!pathname.startsWith("/auth")) {
46+
const token = getToken();
47+
if (!token) {
48+
console.log("AuthGuard: No token found on popstate (back/forward), redirecting to login");
49+
router.push("/auth/login");
50+
}
51+
}
52+
};
53+
54+
window.addEventListener("popstate", handlePopState);
55+
return () => window.removeEventListener("popstate", handlePopState);
56+
}, [pathname, router]);
57+
58+
// Add visibility change listener to catch tab switching scenarios
59+
useEffect(() => {
60+
const handleVisibilityChange = () => {
61+
if (!document.hidden && !pathname.startsWith("/auth")) {
62+
const token = getToken();
63+
if (!token) {
64+
console.log("AuthGuard: No token found on visibility change, redirecting to login");
65+
router.push("/auth/login");
66+
}
67+
}
68+
};
69+
70+
document.addEventListener("visibilitychange", handleVisibilityChange);
71+
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
72+
}, [pathname, router]);
73+
74+
// Add focus listener for additional protection
75+
useEffect(() => {
76+
const handleFocus = () => {
77+
if (!pathname.startsWith("/auth")) {
78+
const token = getToken();
79+
if (!token) {
80+
console.log("AuthGuard: No token found on focus, redirecting to login");
81+
router.push("/auth/login");
82+
}
83+
}
84+
};
85+
86+
window.addEventListener("focus", handleFocus);
87+
return () => window.removeEventListener("focus", handleFocus);
88+
}, [pathname, router]);
89+
90+
return null; // This component renders nothing
91+
}

frontend/src/app/components/layout/Navbar.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import Link from "next/link";
1212
import { useState } from "react";
13+
import { useRouter } from "next/navigation";
1314
import { Button } from "@/components/ui/button";
1415
import { User, LogOut, ChevronDown } from "lucide-react";
1516
import { removeToken } from "@/services/userServiceCookies";
@@ -27,16 +28,28 @@ import {
2728
export default function Navbar() {
2829
const [activeTab, setActiveTab] = useState("Dashboard");
2930
const { user, setUser } = useUser();
31+
const router = useRouter();
3032

3133
const navItems = [
3234
{ name: "Dashboard", href: "/home" },
3335
{ name: "Find Partner", href: "/match" },
3436
];
3537

3638
const handleLogout = () => {
37-
removeToken();
39+
// Clear user context first
3840
setUser(null);
39-
window.location.href = "/auth/login";
41+
42+
// Clear token from cookies
43+
removeToken();
44+
45+
// Clear all localStorage and sessionStorage
46+
if (typeof window !== 'undefined') {
47+
localStorage.clear();
48+
sessionStorage.clear();
49+
}
50+
51+
// Use Next.js router.push for client-side navigation
52+
router.push("/auth/login");
4053
};
4154

4255
return (

frontend/src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Geist, Geist_Mono } from "next/font/google";
1010
import { Toaster } from "sonner";
1111
import { UserProvider } from "@/contexts/UserContext";
1212
import NavbarWrapper from "./components/layout/NavbarWrapper";
13+
import AuthGuard from "./components/layout/AuthGuard";
1314
import "./globals.css";
1415

1516
const geistSans = Geist({
@@ -38,6 +39,7 @@ export default function RootLayout({
3839
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
3940
>
4041
<UserProvider>
42+
<AuthGuard />
4143
<NavbarWrapper />
4244
{children}
4345
</UserProvider>

frontend/src/app/match/page.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
import { IoIosSettings } from "react-icons/io";
2-
import { CiBookmark } from "react-icons/ci";
3-
import {
4-
Card,
5-
CardHeader,
6-
CardTitle,
7-
CardDescription,
8-
CardContent,
9-
} from "@/components/ui/card";
101
import { Button } from "@/components/ui/button";
112
import TopicsComponent from "../components/match/TopicsComponent";
123
import DifficultyComponent from "../components/match/DifficultyComponent";

frontend/src/app/page.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
export default function Home() {
2-
return (
3-
<main>
4-
<h1>hi</h1>
5-
</main>
6-
);
1+
import { redirect } from "next/navigation";
2+
3+
export default function RootRedirect() {
4+
redirect("/home");
5+
return null;
76
}

frontend/src/middleware.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
/**
2+
* AI Assistance Disclosure:
3+
* Tool: GitHub Copilot (model: Claude Sonnet 4), date: 2025-09-16
4+
* Purpose: To fix a bug where users can navigate back to protected pages after logout by implementing cache control headers in middleware.
5+
* Author Review: I validated correctness, security, and performance of the code.
6+
*/
7+
18
import { NextResponse } from "next/server";
29
import type { NextRequest } from "next/server";
310
import { verifyToken } from "@/services/userServiceApi";
@@ -22,7 +29,15 @@ export async function middleware(request: NextRequest) {
2229
// If no token, redirect to login
2330
if (!token) {
2431
const loginUrl = new URL("/auth/login", request.url);
25-
return NextResponse.redirect(loginUrl);
32+
const response = NextResponse.redirect(loginUrl);
33+
34+
// Clear any existing token cookie to ensure clean state
35+
response.cookies.set("token", "", {
36+
path: "/",
37+
expires: new Date(0),
38+
});
39+
40+
return response;
2641
}
2742

2843
try {
@@ -32,15 +47,39 @@ export async function middleware(request: NextRequest) {
3247
if (response.status !== 200) {
3348
// Token is invalid, redirect to login
3449
const loginUrl = new URL("/auth/login", request.url);
35-
return NextResponse.redirect(loginUrl);
50+
const redirectResponse = NextResponse.redirect(loginUrl);
51+
52+
// Clear invalid token cookie
53+
redirectResponse.cookies.set("token", "", {
54+
path: "/",
55+
expires: new Date(0),
56+
});
57+
58+
return redirectResponse;
3659
}
3760

38-
return NextResponse.next();
61+
// Token is valid, allow access but add cache control headers
62+
const response_next = NextResponse.next();
63+
64+
// Prevent browser caching of protected pages to avoid back navigation issues
65+
response_next.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
66+
response_next.headers.set('Pragma', 'no-cache');
67+
response_next.headers.set('Expires', '0');
68+
69+
return response_next;
3970
} catch (error) {
4071
console.error("Token verification failed:", error);
4172
// On error, redirect to login
4273
const loginUrl = new URL("/auth/login", request.url);
43-
return NextResponse.redirect(loginUrl);
74+
const redirectResponse = NextResponse.redirect(loginUrl);
75+
76+
// Clear potentially corrupted token cookie
77+
redirectResponse.cookies.set("token", "", {
78+
path: "/",
79+
expires: new Date(0),
80+
});
81+
82+
return redirectResponse;
4483
}
4584
}
4685

0 commit comments

Comments
 (0)