everyone, so I finished building my web application and I deployed my server and frontend separately.
So the problem seems to be that my server is not sending the session data to the client (that’s what I think, I am not sure).
I am going to share parts of my code relevant to the problem.
so this is my messages/+page.svelte –
<script>
import { onMount } from 'svelte';
onMount(async () => {
try {
console.log('Fetching logged in user ID');
const authResponse = await fetch('https://messenger-tu85.onrender.com/api/loggedInUserId', {
credentials: 'include'
});
console.log('Auth Response:', authResponse);
if (!authResponse.ok) {
window.location.href = '/signin';
return;
}
// other javascript code
</script>
As can be seen in the code snippet above, in my messages page, I am trying to prevent unauthenticated users from accessing the page. I do this by sending a fetch request to an endpoint on the server to determine if the user is logged in or not. This is the endpoint –
app.get('/api/loggedInUserId', async (req, res) => {
console.log('Session:', req.session);
console.log('User:', req.user);
try {
const user = await User.findById(req.user._id);
if (!user) {
throw new Error('User not found');
}
res.json({ loggedInUserId: user._id });
} catch (error) {
console.error('Error fetching user:', error);
res.status(500).json({ error: 'Server Error' });
}
});
The problem is that my application signs a user in after authentication with Google using Passport, but even after successfully logging in with Google, I cant access the messages page and the endpoint from the server prints the following out on the console –
Session: Session {
cookie: {
path: '/',
_expires: 2024-07-17T15:28:12.066Z,
originalMaxAge: 86400000,
httpOnly: false,
secure: true,
sameSite: 'none'
}
}
User: undefined
Error fetching user: TypeError: Cannot read properties of undefined (reading '_id')
This is my Passport.js, please forgive me for posting everything, I can’t figure out what part to leave out –
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const User = require('../models/User');
module.exports = function(passport) {
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'https://messenger-tu85.onrender.com/auth/google/callback',
},
(accessToken, refreshToken, profile, done) => {
console.log('Google profile:', profile);
User.findOne({ googleId: profile.id })
.then(existingUser => {
if (existingUser) {
console.log('Existing user found:', existingUser);
return done(null, existingUser);
} else {
const tempUser = {
googleId: profile.id,
email: profile.emails[0].value
};
console.log('Creating new temp user:', tempUser);
return done(null, tempUser);
}
})
.catch(err => {
console.error('Error finding user:', err);
return done(err);
});
}
));
passport.serializeUser((user, done) => {
console.log('Serialize user:', user);
if (user._id) {
done(null, user._id);
} else {
done(null, user);
}
});
passport.deserializeUser((id, done) => {
console.log('Deserialize user id:', id);
if (typeof id === 'object' && id.googleId) {
done(null, id);
} else {
User.findById(id)
.then(user => {
console.log('Deserialized user:', user);
done(null, user);
})
.catch(err => {
console.error('Error fetching user:', err);
done(err);
});
}
});
};
Now, get this, here “console.log(‘Existing user found:’, existingUser);” my server console prints out –
Existing user found: {
_id: new ObjectId('6693d7a09886ac04b8666b2e'),
googleId: 'prints out my google id here',
email: 'prints out my email here',
username: 'prints out my username i chose when i registered on my own application',
selectedUsers: [ new ObjectId('6693da089886ac04b8666b58') ],
__v: 0
}
Also this part “console.log(‘Serialize user:’, user);” prints out –
Serialize user: {
_id: new ObjectId('6693d7a09886ac04b8666b2e'),
googleId: 'my google id',
email: 'my email',
username: 'my username',
selectedUsers: [ new ObjectId('6693da089886ac04b8666b58') ],
__v: 0
}
So, based on my understanding, this serialization is meant to set the data in the session, and the fact that the logged in user’s data was serialized means that they exist in the session, right?
However, the passport.deserializeUser is never reached because the console statements there are never printed out.
This is my session.js –
const session = require('express-session');
const MongoStore = require('connect-mongo');
module.exports = (passport) => {
return session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
collectionName: 'sessions'
}),
cookie: {
maxAge: 24 * 60 * 60 * 1000, // 1 day
secure: process.env.NODE_ENV === 'production', // Set to true in production
httpOnly: false,
sameSite: 'none' // Adjust based on your cross-origin requirements
}
});
};
Once again, I apologize if I am making you read through lots of code, I just don’t know what parts to omit that might be the source of my problem.
This is my server.js where I suspect the problem might lie –
// imports at top of the file
dotenv.config();
const connectDB = require('./config/mongoose');
connectDB();
// Initialize Passport
const initializePassport = require('./config/passport');
initializePassport(passport);
const server = createServer(app);
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(require('./middleware/session')(passport));
app.use(cors({
origin: 'https://svelte-of1p.onrender.com',
credentials: true
}));
app.use(session(passport)); // does not work even if i remove this line
app.use(passport.initialize());
app.use(passport.session());
app.get('/api/loggedInUserId', async (req, res) => {
console.log('Session:', req.session);
console.log('User:', req.user);
try {
const user = await User.findById(req.user._id);
if (!user) {
throw new Error('User not found');
}
res.json({ loggedInUserId: user._id });
} catch (error) {
console.error('Error fetching user:', error);
res.status(500).json({ error: 'Server Error' });
}
});
That’s it. Hopefully my problem was well explained and someone can provide a solution.
Thanks in advance.
6
So the issue is that my client and server are deployed separately on different domains. My server handles authentication but there has to be a way to communicate with the client that a particular user is authenticated. How would the client know this?
When I sign in on the server, a session is created for the user with a unique session ID.
I put that session ID in a cookie and I send that cookie to the client –
router.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: 'https://svelte-of1p.onrender.com/signin' }),
(req, res) => {
if (req.user) {
// Manually set the cookie
res.cookie('connect.sid', req.sessionID, {
maxAge: 24 * 60 * 60 * 1000, // 1 day
secure: process.env.NODE_ENV === 'production', // True in production
httpOnly: false, // Allow client-side access
sameSite: 'None' // Allow cross-site cookies
});
if (req.user.username) {
res.redirect('https://svelte-of1p.onrender.com');
} else {
res.redirect('https://svelte-of1p.onrender.com/select');
}
} else {
res.redirect('https://svelte-of1p.onrender.com/signin');
}
}
);
When the client sends a request to the server, the cookie is deserialized. We extract the session ID from the cookie (remember we placed the unique session ID when creating the cookie), after extracting the session ID we then look for the user who is associated with that unique session id.
If a user exists then we know they are logged in and we can authorize them
function deserializeUser(req, res, next) {
try {
const cookie = req.headers.cookie;
if (!cookie || !cookie.includes('connect.sid')) {
return res.status(401).json({ error: 'Unauthorized' });
}
const sessionID = cookie.split('connect.sid=')[1].split(';')[0].trim();
req.sessionStore.get(sessionID, (err, session) => {
if (err || !session) {
return res.status(500).json({ error: 'Session retrieval failed' });
}
if (!session.passport || !session.passport.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
req.user = { _id: session.passport.user };
next();
});
} catch (error) {
res.status(500).json({ error: 'Server Error' });
}
}
module.exports = deserializeUser;
The above is a middleware. To then apply the middleware to all my api endpoints, in my server.js I have –
const deserializeUser = require('./middleware/deserializeUser');
app.use('/api', deserializeUser);
It always seems so simple after you spent two days debugging it 😂
See how easy it sounds but it had me frustrated.
Anyway, posting this here in case anyone sees this in the future while encountering a similar issue.