Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: Cannot Login if url entered manually into the browser

I have a strange problem where you can navigate to the login page and login, but if you try to enter the address manually into the browser it will just reload the login page every time you hit send. Whats even more strange is that it will show you parts of the navigation that are only visible to logged in users after the first login attempt but it will redirect to the login page still. And if i try to go to other pages that are only for logged in users it won't show them.

If i hit refresh in the browser the login page will instantly start to work again and i can login. I'm using React Router on a docker container locally.

Is there a way i can fix this?

This is what my React side of things looks like:

App.js

import React, { useContext } from "react";
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Navigate
} from "react-router-dom";
import { AuthContext, AuthContextProvider } from './contexts/AuthContext'

import { FacilityDetail } from './components/FacilityDetail'
import { Settings } from './components/Settings'
import { Login } from './components/Login'
import { Reset } from './components/Reset'
import { Navbar } from "./components/Navbar";
import { FacilityUpdate } from "./components/FacilityUpdate";
import { Signup } from "./components/Signup"
import { ConfirmEmail } from "./components/ConfirmEmail";
import { FacilityList } from './components/FacilityList'
import { ResetConfirm } from './components/ResetConfirm'
import { Home } from "./components/Home";

const EnforceAuthOnRoute = ({ children }) => {
  const { shouldGoToLogin, user } = useContext(AuthContext)
  return user && !shouldGoToLogin ? children : <Navigate replace to="/login" />
}

export default function App() {
  return (
    <Router>
      <AuthContextProvider>
        <div>
          <Navbar />          

          {/* A <Routes> looks through its children <Route>s and
              renders the first one that matches the current URL. */}
          <div className="max-w-8xl mx-auto px-4 sm:px-6 md:px-8">
            <Routes>
              <Route path="/about" element={<About/>} />
              <Route path="/users" element={<Users />} />
              <Route path="/facilities/:id" element={<EnforceAuthOnRoute><FacilityDetail /></EnforceAuthOnRoute>} exact />
              <Route path="/facilities/:id/update" element={<EnforceAuthOnRoute><FacilityUpdate /></EnforceAuthOnRoute>} exact />
              <Route path="/settings" element={<EnforceAuthOnRoute><Settings /></EnforceAuthOnRoute>} exact />
              <Route path="/login" element={<Login />} exact />
              <Route path="/signup" element={<Signup />} exact />
              <Route path="/reset" element={<Reset />} exact />
              <Route path="/password-reset/confirm/:uid/:token" element={<ResetConfirm />} exact />
              <Route path="/accounts/confirm-email/:key" element={<ConfirmEmail />} exact />
              <Route path="/facilities" element={<EnforceAuthOnRoute><FacilityList /></EnforceAuthOnRoute>} exact />
              <Route path="/" element={<Home />} exact />
            </Routes>
          </div>
        </div>
      </AuthContextProvider>
    </Router>
  );
}

function About() {
  return <h2>About</h2>;
}

function Users() {
  return <h2>Users</h2>;
}

Login.js

import { useContext, useState } from 'react';
import { Formik, Field, Form } from 'formik';
import { useNavigate } from "react-router-dom"
import { AuthContext } from '../contexts/AuthContext'

export function Login() {
    const [loading, setLoading] = useState(false)
    const { login } = useContext(AuthContext)
    const navigate = useNavigate()
    
    function handleSubmit(values) {
        setLoading(true)
        login(values).then(() => {
            navigate('/facilities')
        }).finally(() => setLoading(false))
           
    }

    return (
        <div>
            {loading && "Loading..."}
            <Formik
                initialValues={{
                    email: '',
                    password: '',
                }}
                onSubmit={handleSubmit}>

                {({ errors, touched }) => (
                    <Form>
                        <Field name="email">
                            {({ field, form }) => (
                                <label className="mt-3 block">
                                    <span className="text-gray-700">Email</span>
                                    <input
                                    {...field}
                                    type="text"
                                    className="
                                        mt-1
                                        block
                                        w-full
                                        rounded-md
                                        border-gray-300
                                        shadow-sm
                                        focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
                                    "
                                    placeholder=""
                                    style={
                                        form.touched.email && form.errors.email ? (
                                            { border: '2px solid var(--primary-red)'}
                                        ) : null
                                    }
                                    />
                                </label>
                            )}
                        </Field>

                        <Field name="password">
                            {({ field, form }) => (
                                <label className="mt-3 block">
                                    <span className="text-gray-700">Password</span>
                                    <input
                                    {...field}
                                    type="password"
                                    className="
                                        mt-1
                                        block
                                        w-full
                                        rounded-md
                                        border-gray-300
                                        shadow-sm
                                        focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
                                    "
                                    placeholder=""
                                    style={
                                        form.touched.password && form.errors.password ? (
                                            { border: '2px solid var(--primary-red)'}
                                        ) : null
                                    }
                                    />
                                </label>
                            )}
                        </Field>

                        <button className="btn btn-gr" 
                            type="submit">
                            Submit
                        </button>
                    </Form>
                )}

            </Formik>
        </div>
    )

}

AuthContext.js

import React, { useEffect, useState } from 'react'
import { API } from "../api"
import axios from "axios"
import { isAfter, isEqual, parseISO, sub } from 'date-fns'

export const AuthContext = React.createContext(null)

export function AuthContextProvider({ children }) {

    const [accessTokenExpiration, setAccessTokenExpiraton] = useState(undefined);

    const getUser = () => {
        return JSON.parse(localStorage.getItem('user'))
    }

    const isLoggedIn = () => {
        return localStorage.getItem('user') !== null
    }

    const [user, setUser] = useState(() => {
        return isLoggedIn() ? getUser() : null;
    })

    const [shouldGoToLogin, setShouldGoToLogin] = useState(() => {
        if (!user || !user.access_token || !user.refresh_token) {
            return true;
        }

        return false;
    })

    const logout = async () => {
        if (!user) {
            return;
        }

        const { access_token } = user;
        localStorage.removeItem('user')
        setUser(null);

        return axios.post(API.auth.logout, {
            headers: {
                "Authorization": `Bearer ${access_token}`,
                "Content-Type": "application/json"
            },
            withCredentials: true
        });
    }
    
    const login = async (values) => {
        console.log(values);
        const correctedValues = { ...values, username: values.email };
        return axios.post(API.auth.login, correctedValues)
            .then(res => {
                const data = res.data;
                processApiData(data);
            })
    }

    const refreshToken = async () => {
        const user = getUser();

        const redirectToLogout = () => {
            localStorage.clear(); // Clear our localStorage
            setShouldGoToLogin(true);
        };

        if (!user) { // No user
            redirectToLogout();
        }

        console.log(API.auth.refreshToken);
        const resp = await fetch(API.auth.refreshToken, {
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({'refresh': user?.refresh_token}),
            method: "POST",
            withCredentials: true
        })

        console.log("status", resp.status);
        if (resp.status === 200) {
            const data = await resp.json(); // Convert to JSON
            console.log("refresh token data", data);
            processApiData(data);
        } else {
            redirectToLogout();
        }
    }

    const resetPassword = async (values) => {
        return axios.post(API.auth.passwordReset, values);
    }

    const processApiData = (resp) => {
        let newUser = { ...user, ...resp };
        delete(newUser.user); // Delete the user sub-object since we merged that directly into the top-level object
        saveUser(newUser); // Save the user

        const { access_token_expiration } = newUser;

        if (access_token_expiration) {
            console.log("have expiration", access_token_expiration);
            const nextExpiration = parseISO(access_token_expiration); // Convert from ISO 8601 to a Date Object
            const earlyRefreshTime = sub(nextExpiration, { minutes: 55 }); // Do an hourish early
            setAccessTokenExpiraton(earlyRefreshTime); // Set the upcoming expiraton
        }
    }

    const saveUser = async (newUser) => {
        localStorage.setItem('user', JSON.stringify(newUser))
        setUser(newUser)
    }

    const signup = async (values) => {
        return axios.post(API.auth.signup, values);
    }

    useEffect(() => {
        if (!user) {
            return;
        }

        const interval = setInterval(()=> {
            if(!user){
                return false;
            }

            if (accessTokenExpiration) {
                const now = new Date(); // Get the current time
                console.log(now);
                console.log(accessTokenExpiration);
                if (isAfter(now, accessTokenExpiration) || isEqual(now, accessTokenExpiration)) { // If we are late to the party or the stars have aligned
                    refreshToken(); // Refresh the token
                }
            } else { // We do not have an access token expiration yet
                refreshToken(); // Refresh the token immediately so we get a time
            }
        }, 1000 * 15)
        return ()=> clearInterval(interval)
    }, [accessTokenExpiration, refreshToken, user])

    return (
        <AuthContext.Provider value={{
            getUser,
            isLoggedIn,
            logout,
            login,
            resetPassword,
            signup,
            user,
            shouldGoToLogin
        }}>
            {children}
        </AuthContext.Provider>
    )
}
like image 769
Matthias Avatar asked Oct 29 '25 07:10

Matthias


2 Answers

My hunch is, that you are setting the shouldGoToLogin state in AuthContext.js in several places that get mixed up.

This might cause the EnforceAuthOnRoute function to run before shouldGoToLogin is set to false.

Try setting the default simply to true and handle the logic of when it is set to false somewhere else:

const [shouldGoToLogin, setShouldGoToLogin] = useState(true);

Alternatively try to have the handleSubmit await shouldGoToLogin to be false before calling navigate():

    const { shouldGoToLogin } = useContext(AuthContext)
 
    useEffect(() => {
        if(shouldGoToLogin) navigate('/facilities');
    },[shouldGoToLogin])

    function handleSubmit(values) {
        setLoading(true)
        login(values).finally(() => setLoading(false))
like image 132
flair Avatar answered Oct 31 '25 21:10

flair


Your question is bit unclear. As far as i have understood the question, here goes the following response.

If you want the reacter-router to work even when typed manually in the browser. Do the following changes in the webpack server like this.

devServer: {
   historyApiFallback: true,
   contentBase: './',
   hot: true
},

The historyApiFallback would fix the issue. Now routing would work correctly and you can refresh the page or type in the URL directly. The above answer works if you used Webpack.

For further details follow this link

like image 40
krishnaacharyaa Avatar answered Oct 31 '25 22:10

krishnaacharyaa



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!