3

I'm using passport-discord and passport to allow users to authenticate using their discord accounts.

For some reason, cookies aren't being stored in the browser (on frontend website), I use axios (withCredentials set to true in request options) to make a request to my express server which gets the authenticated user from the request (req.user), the session is stored in the database (mongodb atlas), but the cookies aren't being stored in the browser. This works locally, but not when I host the website on a platform like vercel. The cookies are being stored in the API page, but not on the frontend website, which is very weird.

Main file for the api:

const app = require('express')();
const routes = require('./src/routes'); // all the routes
const passport = require('passport');
const mongoose = require('mongoose');
const session = require('express-session');
const cors = require('cors');
const MongoStore = require('connect-mongo');

require('./src/strategies/discord'); // this is where the users are serialized, deserialized
mongoose.connect('database uri');

app.use(
    cors({
        origin: [
            'api url',
            'frontend website url',
        ],
        credentials: true,
    })
);
app.use(
    session({
        secret: 'session secret',
        cookie: {
            maxAge: 30000,
        },
        resave: false,
        saveUninitialized: false,
        store: MongoStore.create({
            mongoUrl: 'database uri',
        }),
    })
);
app.use(passport.initialize());
app.use(passport.session());
app.use('/api', routes);

app.listen(3000, () => {
    console.log(`[API] Running on Port 3000`);
});

/strategies/discord.js file (where the users are serialized and deserialized:

const passport = require('passport');
const DiscordStrategy = require('passport-discord');
const model = require('../models/User');

passport.serializeUser((user, done) => {
    console.log(`Serialized: ${user._id}`);
    done(null, user._id);
});

passport.deserializeUser(async (id, done) => {
  console.log(`Deserialized: ${id}`)
    try {
        const user = await model.findById(id);
        return user ? done(null, user) : done(null, null);
    } catch (err) {
        console.log(err);
        done(err, null);
    }
});

passport.use(
    new DiscordStrategy(
        {
            clientID: 'the client id',
            clientSecret: 'the app client secret',
            callbackURL: `https://apiurl/discord/redirect`,
            scope: ['identify', 'guilds'],
        },
        async (accessToken, refreshToken, profile, done) => {
            const { id, username, discriminator, avatar, guilds } = profile;
            try {
                const found = await model.findByIdAndUpdate(
                    id,
                    {
                        discordTag: `${username}#${discriminator}`,
                        avatar,
                        guilds,
                    },
                    {
                        new: true,
                    }
                );

                if (found) {
                    console.log(`User was found`);
                    return done(null, found);
                } else {
                    const newUser = await new model({
                        _id: id,
                        discordTag: `${username}#${discriminator}`,
                        avatar,
                        guilds,
                    }).save();
                    return done(null, newUser);
                }
            } catch (err) {
                console.log(err);
                return done(err, null);
            }
        }
    )
);

auth.js file (where the users are redirected to the Oauth2 page):

const router = require('express').Router();
const passport = require('passport');

router.get('/discord', passport.authenticate('discord'));
router.get(
    '/discord/redirect',
    passport.authenticate('discord'),
    (req, res) => {
        res.redirect(`http://localhost:3001/guilds`);
    }
);

router.get('/', (req, res) => {
    if (req.user) {
        res.send(req.user);
    } else {
        return res.status(401).send('Unauthorized'); // I make a request to this url with axios from my frontend website (withCredentials set to true, so the cookies can be sent)
    }
});

module.exports = router;

As you can see in the auth.js file, the user is redirected to the guilds page on the frontend website, once they're logged in. I make a request to my API again, to get the mutual guilds of the discord.js client and the user, where I check if the user is logged in (req.user exists), and if they are, I send the mutual guilds or just a 401 response and redirect the user back to the home page.

Once I login to the website, I am redirected to the guilds page, then immediately redirected to the home page, when I check the console, there is a 401 response (from the API). When I check the "Applications" tab of my website, there are no cookies, but the API page has the cookies.

I am not sure if this is an issue with my code, or one of the packages I'm using, any help is appreciated.

FC5570
  • 163
  • 4
  • 12

2 Answers2

2

I know I am very late in response to this, as I stumbled upon your thread looking for the answer for the very same issue. I had secure set to true, and after swapping it to false it seems to have fixed the issue.

app.use(
  session({
    secret: "secret",
    resave: false,
    saveUninitialized: true,
    cookie: {
      maxAge: 1000 * 60 * 60 * 24 * 7,
      secure: true,
    },
    store: store.create({
      mongoUrl: `mongoUrl`,
    }),
  })
);

After setting it to false, it began saving. From what I've read it appears that without it being set, it attempts to infer whether it should be true/false if the connection is over HTTP or HTTPs, but in my case I set it explicitly to true without fully understanding what it would do. If you have yet to resolve the issue, try setting it to false and see if that resolves the issue. As it will not save the cookie over HTTP conditions if it set secure to true by default.

AustinF
  • 21
  • 2
0

I had the same problem as the cookie were not being saved when deployed. If your backend server and frontend app are hosted on different domain i.e. different IP.

  • Then you need to set sameSite:'none' which indicates whether a cookie is intended to be used in a cross-site context.
  • Also, Secure attribute must be set to true secure: true when the SameSite attribute has been set to 'none'. To protect user data from cross-site request forgery, policy adopted by most browser which will in turn prevent your cookie from being saved. for more info check docs
  • If secure is set true, and your request to the server is sent over HTTP, the cookie will not be saved in the browser. check mdn docs
  • If you have your node.js behind a proxy and are using secure: true, you need to set "trust proxy" in express:
// app.set('trust proxy', 1); //either use this or set proxy:true when setting up 
app.use(
    session({
        secret: 'session secret',
        cookie: {
            sameSite: 'none', //add
            secure: true,  //add
            maxAge: 30000
        },
        proxy: true, //or use this
        resave: false,
        saveUninitialized: false,
        store: MongoStore.create({
            mongoUrl: 'database uri',
        }),
    })
);
roxylius
  • 31
  • 3