简体   繁体   中英

Authentication with context API, keep redirecting to login page

I try to do RequierAuth lock to protect against an unauthorized user. First created the context where auth should accept the token :

AuthProvider.js:

const AuthContext = createContext({});

export const AuthProvider = ({ children }) => {
    const [auth, setAuth] = useState({});

    return (
        <AuthContext.Provider value={{ auth, setAuth }}>
            {children}
        </AuthContext.Provider>
    )
}

export default AuthContext;

Next created a hook for the above context:

useAuth.js

const useAuth = () => {
    return useContext(AuthContext);
}

export default useAuth;

Next, the actual "lock" for protection, where I check if there is a token and return either the page or send the user to the login page:

RequierAuth.js

const RequierAuth = () => {
    const {auth} = useAuth();
    const location = useLocation();
    return (
        auth.token?<Outlet/> : <Navigate to = "/auth" state = {{from:location}} replace />
    );

}
export default RequierAuth;

App.js


function App() {
  return (
    <>
    ...
    <div className="App">
     <BrowserRouter>
      <Routes>
          <Route path="/" element={<Layout />}>
            <Route path="auth" element={<Login />} />
            <Route path="reg" element={<Register />} />
            <Route element = {<RequierAuth/>}>
                <Route path="home" element={<Home />} />
            </Route>
          </Route>
      </Routes>
    </BrowserRouter>
    </div>
   </>
  );
}

export default App;

index.js

root.render(
  <React.StrictMode>
     <AuthProvider>
      <App />
     </AuthProvider>
  
  </React.StrictMode>
);

And actually the question is that now when I call setAuth on the login page:

LoginForm.js

const Login = () =>{

  const {auth,setAuth} = useAuth();

  const [authResponce,setAuthResponce] = useState(null);
  const [login,setLogin] = useState("");
  const onChangeLogin = (e) => {
        e.preventDefault();
        const username = e.target.value;
        setLogin(username);
  };

  const [password,setPassword] = useState("");
  const onChangePassword = (e) => {
        const password = e.target.value;
        setPassword(password);
  }; 

  const instance = axios.create({
    baseURL: "http://localhost:8080",
    headers: {
      "Content-Type": "application/json",
    },
  });

  const postUser = async (user) =>{
    return instance.post("http://localhost:8080/auth", user);
  }

  const onLogin = (e) =>{
    e.preventDefault();

    const user = {
      login: login,
      password: password,
    };

    (async() => {
      const response = await postUser(user);
      const data =  response.data;
      console.log(data);
      const token = data.token;
      console.log(token);
      setAuth({token});
      console.log(auth);
      console.log(auth.token);
    })();
    
};


  return (
    <div className="Auth-form-container">
      <form className="Auth-form" onSubmit={onLogin}>
        <div className="Auth-form-content">
          <h3 className="Auth-form-title">Sign In</h3>
          <div className="form-group mt-3">
            <label>Login</label>
            <input
              type="login"
              className="form-control mt-1"
              placeholder="Enter login"
              value={login}
              onChange={onChangeLogin}
            />
          </div>
          <div className="form-group mt-3">
            <label>Password</label>
            <input
              type="password"
              className="form-control mt-1"
              placeholder="Enter password"
              value={password}
              onChange={onChangePassword}
            />
          </div>
          <div className="d-grid gap-2 mt-3">
            <button type="submit" className="btn btn-primary">
              Submit
            </button>
          </div>
        </div>
      </form>
    </div>
  )
};
export default Login;

First, if you get a token for the first time, then why is the value not printed in the console, if you get it again on the form, it already prints the expected value, but when you switch to home , it sends it back to the login.

I set breakpoint and at the moment of checking auth.token? indicates that the value is not set, although setAuth has set the value.

The check itself seems to work, if you put the default value in auth and try to compare with it, then we will normally get to /home .

I've only recently started studying and I can't figure out what the error is, so I'll be glad for help to figure it out.

Issue

First, updating a state is an asynchronous tasks. A re-render is needed in order to have the updated value. Which is why you are not seeing change whith those lines that you have inside onLogin :

setAuth({token});
console.log(auth);

Second, after the login process, you said in the comments that you are using the browser to redirect to /home . Well you should know that, doing so refreshes the page, so all your states come to their initial values, so auth would be {} . This is why it's redirecting to "/auth" .


Solution

You should use React Router Dom's redirection mechanism, useNavigate for example. Change LoginForm.js slightly, like so (I added comments in the code):

import { useEffect } from "react"; // line to add
import { useNavigate } from "react-router-dom"; // line to add
const Login = () => {
  const navigate = useNavigate(); // line to add
  const { auth, setAuth } = useAuth();

  const [authResponce, setAuthResponce] = useState(null);
  const [login, setLogin] = useState("");
  const onChangeLogin = (e) => {
    e.preventDefault();
    const username = e.target.value;
    setLogin(username);
  };

  const [password, setPassword] = useState("");
  const onChangePassword = (e) => {
    const password = e.target.value;
    setPassword(password);
  };

  const instance = axios.create({
    baseURL: "http://localhost:8080",
    headers: {
      "Content-Type": "application/json",
    },
  });

  const postUser = async (user) => {
    return instance.post("http://localhost:8080/auth", user);
  };

  // useEffect to add
  useEffect(() => {
    if (auth) {
      console.log(auth); // add your logs here to see the updates after re-render
      navigate("/home");
    }
  }, [auth]);
  const onLogin = (e) => {
    e.preventDefault();

    const user = {
      login: login,
      password: password,
    };

    (async () => {
      const response = await postUser(user);
      const data = response.data;
      console.log(data);
      const token = data.token;
      console.log(token);
      setAuth({ token });
    })();
  };

  return (
    <div className="Auth-form-container">
      <form className="Auth-form" onSubmit={onLogin}>
        <div className="Auth-form-content">
          <h3 className="Auth-form-title">Sign In</h3>
          <div className="form-group mt-3">
            <label>Login</label>
            <input
              type="login"
              className="form-control mt-1"
              placeholder="Enter login"
              value={login}
              onChange={onChangeLogin}
            />
          </div>
          <div className="form-group mt-3">
            <label>Password</label>
            <input
              type="password"
              className="form-control mt-1"
              placeholder="Enter password"
              value={password}
              onChange={onChangePassword}
            />
          </div>
          <div className="d-grid gap-2 mt-3">
            <button type="submit" className="btn btn-primary">
              Submit
            </button>
          </div>
        </div>
      </form>
    </div>
  );
};
export default Login;

Improvement

The above solution would work, but if you refresh the page manually, there is no way to remember that a user has been logged in. If you want that feature, you should use something like localStorage . For that, change that useEffect I added inside LoginForm.js to:

useEffect(() => {
  if (auth) {
    console.log(auth); // add your logs here to see the updates after re-render
    localStorage.setItem("token", auth.token); // so you get it later
    navigate("/home");
  }
}, [auth]);

Change AuthProvider.js so you get the token form localStorage if there is one:

const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState({ token: localStorage.getItem("token") });
  return <AuthContext.Provider value={{ auth, setAuth }}>{children}</AuthContext.Provider>;
};

export default AuthContext;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM