Home > Software design >  MERN app login working on localhost but not on Heroku
MERN app login working on localhost but not on Heroku

Time:02-12

I have created a MERN app that I deployed on Heroku but I can't login although I enter the correct credentials. When I submit the login form, the request to the login api is successful (I get the desired response) but there is no jwt_token cookie stored in the browser and the user context is not updated with the logged in user.

FRONTEND

App.js

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './routes/Home';
import ErrorPage from './routes/ErrorPage';
import LoginPage from './routes/LoginPage';
import RegisterPage from './routes/RegisterPage';
import Tournaments from './routes/Tournaments';
import PlayerStatistics from './routes/PlayerStatistics';
import SettingsPage from './routes/SettingsPage';
import TournamentPlay from './routes/TournamentPlay';
import TournamentCreate from './routes/TournamentCreate';
import MyData from './routes/MyData';
import Header from './components/Header';
import useFindPlayer from './hooks/useFindPlayer';
import { PlayerContext } from './context/PlayerContext';
import ThemeContextProvider from './context/ThemeContextProvider';
import PrivateRoute from './routes/PrivateRoute';
import RedirectLoggedIn from './routes/RedirectLoggedIn';

function App() {
  const { player, setPlayer, isLoading } = useFindPlayer();

  return (
    <div className="App">
      <div>
        <Router>
          <ThemeContextProvider>
            <PlayerContext.Provider value={{ player, setPlayer, isLoading }}>
              {player && <Header />}
              <Routes>
                <Route
                  exact
                  path="/"
                  element={
                    <PrivateRoute>
                      <Home />
                    </PrivateRoute>
                  }
                />
                <Route
                  exact
                  path="/tournaments/:id"
                  element={
                    <PrivateRoute>
                      <TournamentPlay />
                    </PrivateRoute>
                  }
                />
                <Route
                  exact
                  path="/login"
                  element={
                    <RedirectLoggedIn>
                      <LoginPage />
                    </RedirectLoggedIn>
                  }
                />
                <Route
                  exact
                  path="/register"
                  element={
                    <RedirectLoggedIn>
                      <RegisterPage />
                    </RedirectLoggedIn>
                  }
                />
                <Route
                  exact
                  path="/settings"
                  element={
                    <PrivateRoute>
                      <SettingsPage />
                    </PrivateRoute>
                  }
                />
                <Route element={<ErrorPage />} />
                <Route
                  exact
                  path="/tournaments"
                  element={
                    <PrivateRoute>
                      <Tournaments />
                    </PrivateRoute>
                  }
                />
                <Route
                  exact
                  path="/players"
                  element={
                    <PrivateRoute>
                      <PlayerStatistics />
                    </PrivateRoute>
                  }
                />
                <Route
                  exact
                  path="/tournaments/new"
                  element={
                    <PrivateRoute>
                      <TournamentCreate />
                    </PrivateRoute>
                  }
                />
                <Route
                  exact
                  path="/mydata"
                  element={
                    <PrivateRoute>
                      <MyData />
                    </PrivateRoute>
                  }
                />
              </Routes>
            </PlayerContext.Provider>
          </ThemeContextProvider>
        </Router>
      </div>
    </div>
  );
}

export default App;

useAuth.js

import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { PlayerContext } from '../context/PlayerContext';

export default function useAuth() {
  const navigate = useNavigate();
  const { setPlayer } = useContext(PlayerContext);
  const [error, setError] = useState(null);

  const setPlayerContext = async () => {
    return await axios
      .get('/api/auth/player')
      .then((res) => {
        setPlayer(res.data.currentPlayer);
        navigate('/');
      })
      .catch((err) => {
        console.error(err);
        setError(err.response.data);
      });
  };

  const registerPlayer = async (data) => {
    const { username, fullname, password, passwordCheck } = data;

    return axios
      .post('/api/auth/register', {
        username,
        fullname,
        password,
        passwordCheck,
      })
      .then(async () => {
        await setPlayerContext();
        navigate('/');
      })
      .catch((err) => {
        console.error(err);
        setError(err.response.data);
      });
  };

  //login player
  const loginPlayer = async (data) => {
    const { username, password } = data;
    return axios
      .post('/api/auth/login', {
        username,
        password,
      })
      .then(async () => {
        await setPlayerContext();
      })
      .catch((err) => {
        setError(err.response.data);
      });
  };

  const clearError = () => {
    setError(null);
  };

  return {
    registerPlayer,
    loginPlayer,
    clearError,
    error,
  };
}

useFindPlayer

import axios from 'axios';

export default function useFindPlayer() {
  const [player, setPlayer] = useState(null);
  const [isLoading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function findPlayer() {
      await axios
        .get('/api/auth/player')
        .then((res) => {
          setPlayer(res.data.currentPlayer);
          setLoading(false);
        })
        .catch((err) => {
          console.error(err);
          setError(err);
          setLoading(false);
        });
    }
    findPlayer();
  }, []);
  return {
    player,
    setPlayer,
    error,
    setError,
    isLoading,
  };
}

BACKEND

server.js

const express = require('express');
const app = express();
const cors = require('cors');
const PORT = process.env.PORT || 8080;
const mongoose = require('mongoose');
const auth = require('./utils/auth');
const bodyParser = require('body-parser');
const database = findDatabase(process.env.NODE_ENV);
const databaseName = database.split('/')[3].split('?')[0].toUpperCase();
const player = require('./routes/playerRoute');
const game = require('./routes/gameRoute');
const tournament = require('./routes/tournamentRoute');
const userAuth = require('./routes/authRoute');
const cookieParser = require('cookie-parser');
const jwtSecret = process.env.JWT_SECRET;
const path = require('path');

mongoose.connect(database);

function findDatabase(env) {
  switch (env) {
    case 'production':
      return process.env.MONGODB_URI;
    case 'development':
      return process.env.MONGO_DEV_URI;
    case 'demo':
      return process.env.MONGO_DEMO_URI;
    default:
      return process.env.MONGO_DEV_URI;
  }
}

const db = mongoose.connection;
const urlEncodedParser = bodyParser.urlencoded({ extended: false });
db.on('error', () => console.error('Error'));
db.once('open', () => {
  console.log(`Database ${databaseName} connected...`);
});

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(bodyParser.json(), urlEncodedParser);
app.use(cookieParser(jwtSecret));

// Authentication
app.post('/api/auth/register', userAuth.registerPlayer);
app.post('/api/auth/login', userAuth.loginPlayer);
app.get('/api/auth/logout', userAuth.logoutPlayer);
app.get('/api/auth/player', userAuth.checkPlayer);

// Players
app.post('/api/players', player.add);
app.get('/api/players', player.list);
app.get('/api/players/stats/:id', player.stats);
app.put('/api/players/:id', player.update);

// Tournaments
app.post('/api/tournaments', tournament.add);
app.get('/api/tournaments/complete/:id', tournament.finalize);
app.get('/api/tournaments/:id', tournament.show);
app.get('/api/tournaments', tournament.list);
app.put('/api/tournaments/:id', tournament.update);
app.delete('/api/tournaments/:id', tournament.cancel);

// Games
app.post('/api/games', game.add);
app.get('/api/games', game.list);
app.get('/api/games/:id', game.getOne);
app.delete('/api/tournaments/:tid/game/:gid', game.delete);


//fix login auth and blank page
if (process.env.NODE_ENV === 'production') {
  const root = require('path').join(__dirname, 'client', 'build');
  app.use(express.static(root));
  app.get('*', (req, res) => {
    res.sendFile('index.html', { root });
  });
} else {
  app.get('/', (req, res) => {
    res.send('API running');
  });
}

app.listen(PORT, () => {
  console.log(`Serving on port ${PORT}`);
});

authRoute.js

const catchAsync = require('../utils/catchAsync');
const jwt = require('jsonwebtoken');
const { promisify } = require('util');

const signToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });
};

const createPlayerToken = async (player, code, req, res) => {
  const token = signToken(player._id);

  let d = new Date();
  d.setDate(d.getDate()   30);

  res.cookie('jwt_token', token, {
    expires: d,
    httpOnly: true,
    secure:
      req.secure ||
      req.headers['x-forwarded-proto'] === 'https' ||
      req.headers['x-forwarded-proto'] === 'http',
    sameSite: 'none',
  });

  player.password = undefined;
  res.status(code).json({
    status: 'success',
    token,
    data: {
      player,
    },
  });
};

exports.registerPlayer = async (req, res, next) => {
  try {
    let { username, password, passwordCheck, fullname } = req.body;
    if (!username || !password || !passwordCheck)
      return res.status(400).json({ msg: 'Not all fields have been entered.' });

    if (password !== passwordCheck) {
      return res
        .status(400)
        .json({ msg: 'Enter the same password twice for verification.' });
    }

    if (!fullname) {
      fullname = username;
    }

    const newPlayer = await Player.create({
      fullname: fullname,
      username: username,
      password: password,
      passwordCheck: passwordCheck,
    });
    createPlayerToken(newPlayer, 201, req, res);
  } catch (err) {
    console.log(err);
    next(err);
  }
};

exports.loginPlayer = catchAsync(async (req, res, next) => {
  const { username, password } = req.body;

  if (!username || !password) {
    return res
      .status(400)
      .send({ msg: 'Please provide a username and password!' });
  }

  const player = await Player.findOne({ username }).select(' password');
  let correctPassword;
  if (player) {
    correctPassword = await player.correctPassword(password, player.password);
  }
  if (!player || !correctPassword) {
    return res.status(401).send({ msg: 'Incorrect username or password' });
  }
  createPlayerToken(player, 200, req, res);
});

exports.checkPlayer = catchAsync(async (req, res, next) => {
  let currentPlayer;
  if (req.cookies.jwt_token) {
    const token = req.cookies.jwt_token;
    const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
    currentPlayer = await Player.findById(decoded.id);
  } else {
    currentPlayer = null;
  }
  res.status(200).send({ currentPlayer });
});
//log user out
exports.logoutPlayer = catchAsync(async (req, res) => {
  res.cookie('jwt_token', 'loggedout', {
    expires: new Date(Date.now()   10 * 1000),
    httpOnly: true,
  });
  res.status(200).send('user is logged out');
});

CodePudding user response:

When using sameSite=None on a cookie, then you have to use the secure flag. From samesite docs:

Cookies will be sent in all contexts, i.e. in responses to both first-party and cross-origin requests. If SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked).

Otherwise, the cookie gets blocked.

  • Related