Oauth Integration Successfully - #83
Conversation
…ement, JWT error handling, and logout functionality across frontend and backend
Anjim and profile
|
@alfishahrin-05 I've opened a new pull request, #84, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
This PR implements Google OAuth 2.0 integration for the Odyssey travel planning application. The implementation adds alternative authentication via Google while maintaining the existing manual username/password authentication system.
Changes:
- Added Google OAuth authentication using Passport.js strategy
- Enhanced User model to support multiple authentication methods (manual/OAuth)
- Implemented profile settings page with preference management
- Updated UI components with new dashboard and styling improvements
- Added session management with express-session for OAuth flow
Reviewed changes
Copilot reviewed 23 out of 30 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| server/src/config/passport.js | New Passport Google OAuth strategy configuration |
| server/src/routes/oauth.js | New OAuth routes for Google authentication flow |
| server/src/server.js | Integrated session middleware and OAuth routes |
| server/src/models/User.js | Added OAuth fields and made password optional for OAuth users |
| server/src/middleware/authMiddleware.js | Enhanced JWT verification with better error handling |
| server/src/routes/protected.js | Added profile update endpoint |
| server/src/routes/auth.js | Added logging for login operations |
| client/odyssey/app/login/page.tsx | Added Google sign-in button |
| client/odyssey/app/signup/page.tsx | Added Google sign-up button |
| client/odyssey/app/dashboard/page.tsx | OAuth callback handling and UI redesign |
| client/odyssey/app/profile/page.tsx | Complete profile management implementation |
| client/odyssey/app/layout.tsx | Added font imports for UI consistency |
| server/package.json | Added passport, express-session dependencies |
| client/odyssey/package.json | Removed @vis.gl/react-google-maps, updated React versions |
Files not reviewed (6)
- .idea/ProjectOdyssey.iml: Language not supported
- .idea/modules.xml: Language not supported
- .idea/vcs.xml: Language not supported
- .idea/workspace.xml: Language not supported
- client/odyssey/package-lock.json: Language not supported
- server/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 5. Start Server | ||
| const PORT = process.env.PORT || 5001; // Defaults to 5001 if .env is missing | ||
| // 7. Start Server | ||
| const PORT = process.env.PORT || 4000; // Defaults to 4000 if .env is missing |
There was a problem hiding this comment.
The default PORT has been changed from 5001 to 4000. This is a breaking change that could affect existing deployments, documentation, and client configurations. Ensure this change is intentional and properly documented, or consider keeping backward compatibility.
| const PORT = process.env.PORT || 4000; // Defaults to 4000 if .env is missing | |
| const PORT = process.env.PORT || 5001; // Defaults to 5001 if .env is missing |
| // Redirect to frontend with token in query params | ||
| res.redirect( | ||
| `http://localhost:3000/dashboard?token=${encodeURIComponent(token)}&user=${encodeURIComponent(JSON.stringify(userData))}` | ||
| ); |
There was a problem hiding this comment.
Security vulnerability: Passing sensitive JWT token in URL query parameters exposes it to browser history, server logs, and potential XSS attacks. Consider using HTTP-only cookies or POST request body instead for secure token transmission.
| // Redirect to frontend with token in query params | |
| res.redirect( | |
| `http://localhost:3000/dashboard?token=${encodeURIComponent(token)}&user=${encodeURIComponent(JSON.stringify(userData))}` | |
| ); | |
| // Set JWT in HTTP-only cookie and redirect to frontend without exposing the token in the URL | |
| res.cookie("token", token, { | |
| httpOnly: true, | |
| secure: true, | |
| sameSite: "lax", | |
| maxAge: 60 * 60 * 1000, // 1 hour in milliseconds, matching JWT expiry | |
| }); | |
| res.redirect("http://localhost:3000/dashboard"); |
| { | ||
| clientID: process.env.GOOGLE_CLIENT_ID, | ||
| clientSecret: process.env.GOOGLE_CLIENT_SECRET, | ||
| callbackURL: "http://localhost:4000/api/auth/google/callback", |
There was a problem hiding this comment.
Hardcoded callback URL with localhost will not work in production. This should be configurable via environment variable (e.g., process.env.BACKEND_URL) to support different deployment environments.
| onClick={() => { | ||
| window.location.href = "http://localhost:4000/api/auth/google"; | ||
| }} |
There was a problem hiding this comment.
Hardcoded OAuth redirect URL with localhost will not work in production or when accessed from different devices. This should be configurable via environment variable.
| onClick={() => { | ||
| window.location.href = "http://localhost:4000/api/auth/google"; | ||
| }} |
There was a problem hiding this comment.
Hardcoded OAuth redirect URL with localhost will not work in production or when accessed from different devices. This should be configurable via environment variable.
| // Hash password before saving | ||
| // Hash password before saving (only for manual auth) | ||
| userSchema.pre("save", async function() { | ||
| if (!this.isModified("password")) return; |
There was a problem hiding this comment.
The password hashing middleware will fail for OAuth users who don't have a password field. Add a check to only hash the password if it exists: if (!this.isModified("password") || !this.password) return;
| if (!this.isModified("password")) return; | |
| if (!this.isModified("password") || !this.password) return; |
| // OAuth callback - store token and user | ||
| localStorage.setItem("token", tokenFromUrl); | ||
| if (userFromUrl) { | ||
| localStorage.setItem("user", userFromUrl); | ||
| } | ||
| // Clean URL | ||
| router.replace("/dashboard"); |
There was a problem hiding this comment.
Security risk: The OAuth callback stores tokens and user data directly from URL parameters without validation. An attacker could craft a malicious URL with fake tokens. Add server-side token validation before storing in localStorage, or use a more secure callback mechanism.
| // OAuth callback - store token and user | |
| localStorage.setItem("token", tokenFromUrl); | |
| if (userFromUrl) { | |
| localStorage.setItem("user", userFromUrl); | |
| } | |
| // Clean URL | |
| router.replace("/dashboard"); | |
| try { | |
| // Validate the token from URL with the backend before storing | |
| const res = await fetch("http://localhost:4000/api/user/profile", { | |
| method: "GET", | |
| headers: { | |
| "Authorization": `Bearer ${tokenFromUrl}`, | |
| "Content-Type": "application/json", | |
| }, | |
| }); | |
| if (!res.ok) { | |
| const errorData = await res.json(); | |
| console.error("Backend error (OAuth callback):", errorData); | |
| throw new Error(errorData.message || "Invalid token"); | |
| } | |
| const data = await res.json(); | |
| // Token is valid - persist it and associated user data | |
| localStorage.setItem("token", tokenFromUrl); | |
| if (data.user) { | |
| localStorage.setItem("user", JSON.stringify(data.user)); | |
| } else if (userFromUrl) { | |
| // Fallback: only use userFromUrl if backend returned no user, | |
| // but do not trust it over backend data. | |
| localStorage.setItem("user", userFromUrl); | |
| } | |
| setUser(data.user ?? null); | |
| // Clean URL | |
| router.replace("/dashboard"); | |
| } catch (err: any) { | |
| console.error("Error validating OAuth token from URL:", err); | |
| localStorage.removeItem("token"); | |
| localStorage.removeItem("user"); | |
| router.push("/login"); | |
| } finally { | |
| setLoading(false); | |
| } |
| const [user, setUser] = useState<any>(null); | ||
| const [loading, setLoading] = useState(true); // Add loading state | ||
| const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); // State for mobile menu | ||
| const [loading, setLoading] = useState(true); |
There was a problem hiding this comment.
Unused variable loading.
| const [loading, setLoading] = useState(true); |
| "use client"; | ||
|
|
||
| import React, { useState } from "react"; | ||
| import React, { useState, useEffect } from "react"; |
There was a problem hiding this comment.
Unused import useEffect.
| import React, { useState, useEffect } from "react"; | |
| import React, { useState } from "react"; |
|
|
||
| if (!res.ok) { | ||
| // Try to parse response as JSON, otherwise use raw text | ||
| let errorMessage = "Failed to save changes"; |
There was a problem hiding this comment.
The initial value of errorMessage is unused, since it is always overwritten.
| let errorMessage = "Failed to save changes"; | |
| let errorMessage: string; |
Add OAuth integration for authentication
No description provided.