Search code examples
javascriptreactjsreact-hooksfetchbasic-authentication

React private routes for basic authentication


I'm trying to implement basic authentication in my react application. Where I sent the email and password in the header while making a GET request to my /users endpoint and according to the response we decide if the login is successful or not. If the login is successful (i.e the user exists at the endpoint) then we push to the <projects> component. But I want the user to only be able to access the /projects url if he's a valid user. How do I apply private routes

This is how my login page code looks function LogIn() {

const [password, setPassword] = React.useState("");
const [email, setEmail] = React.useState("");
const [err, setErr] = React.useState(null);

const history = useHistory();

const handleSubmit = async (event, password, email) => {
    event.preventDefault();

    var myHeaders = new Headers();
    myHeaders.set('Authorization', 'Basic ' + encode(email + ":" + password));

    var requestOptions = {
    method: 'GET',
    headers: myHeaders,
    redirect: 'follow'
    };

    let response;

    try {
        response = await fetch (`${APIlink}/users`, requestOptions)
    } catch (err) {
        setErr("Incorrect Password. Please Retry.");
        return;
    }

    const result = await response.text();
    console.log(result);
    const json = JSON.parse(result);
    console.log(json);
    console.log(response);
    

    if (response.status===200) {
        setErr(null);
        history.push("/Projects"); //valid user is redirected but /Projects is accessible by just
                                   //writing the url as well
        } else {
        setErr(json.error);
        console.log(json.error);
        }
    };

This is how my json object looks after sending the right credentials (email: abc, password: test)

{
  "password": "test",
  "rollno": "18am200",
  "email": "abc",
  "name": "mkyong",
  "user-uid": "7e7199247de1125a6dc1518dd78ba554"
}

This is how my response looks like

Response { type: "cors", url: "{APIlink/users", redirected: false, status: 200, ok: true, statusText: "OK", headers: Headers, body: ReadableStream, bodyUsed: true }

App.js

function App() {
    return (
      <div >
        <HashRouter basename ='/'>
        <Switch>
          <Route path="/" component = {Home} exact/>
          <Route path="/Login" component = {LogIn}/>
          <Route path="/Register" component = {Register}/>
          <Route path="/Projects" component = {ProjectComponent} />
          <Route path="/Application" component = {Project2Component} />
          <Route path="/Demo1" component = {Project3Component} />  
          <Route path="/Demo2" component = {Project4Component} />     
        </Switch>
        </HashRouter>
    </div>
    )
  }

export default App

Solution

  • You can set a persistent value (e.g. isAuthenticated = true) to your LocalStorage after a successful response of login. But make sure to remove the value after user logs out (e.g. isAuthenticated = false). Then you can check the value each time the user changes his/her route.

    I have added below some basic example for you -

    // Login.js
    
    /* other codes ... */
    if (response.status===200) {
        localStorage.setItem('isAuthenticated', true);
        history.push("/Projects");
    };
    /* other codes ... */
    
    // Logout.js
    
    localStorage.removeItem('isAuthenticated');
    
    // AuthRequired.js
    
    import React, { Fragment } from "react"
    import { Redirect } from "react-router-dom"
    
    export default function AuthRequired(props){
        const isAuthenticated = localStorage.getItem('isAuthenticated')
    
        if(isAuthenticated){
            return props.orComponent;
        } else {
            return <Redirect to="/Login"/>
        }
    }
    
    // App.js
    
    import { Route, Switch } from "react-router-dom"
    import AuthRequired from "./AuthRequired"
    
    /* Your other codes and imports.. */
    
    const publicRoutes = [
        {
            path: "/Login",
            exact: true,
            component: LogIn
        },
        {
            path: "/",
            exact: true,
            component: Home
        },
    ];
    
    const authRequiredRoutes = [
        {
            path: "/Projects",
            exact: true,
            component: <ProjectComponent/>
        },
        // ... other routes
    ]
    
    const pathsForLayout = routes => routes.map(route => route.path)
    
    function App() {
        return (
            <div >
                <HashRouter basename ='/'>
                    <Switch>
                        <Route exact path={pathsForLayout(publicRoutes)}>
                            <Switch>
                                {
                                    publicRoutes.map((route,index) => (
                                        <Route
                                            key={index}
                                            exact={route.exact}
                                            path={route.path}
                                            component={route.component}
                                        />
                                    ))
                                }
                            </Switch>
                        </Route>
                        <Route exact path={pathsForLayout(authRequiredRoutes)}>
                            <Switch>
                                {
                                    authRequiredRoutes.map((route,index) => (
                                        <Route
                                            key={index}
                                            exact={route.exact}
                                            path={route.path}
                                            render={() => (
                                                 <AuthRequired 
                                                     {...props}
                                                     orComponent={route.component}
                                                 />
                                            )}
                                        />
                                    ))
                                }
                            </Switch>
                        </Route>
                        <Route component={NotFound} /> {/* Your custom 404 page */}
                   </Switch>
               </HashRouter>
            </div>
        )
    }
    
    export default App
    

    Note - I've followed your naming convention for route. That's why I also kept these as PascalCase. Though the recommended way to to declare path of <Route/> is by using kebab-case. (e.g. path="/login").