How to add login authentication to a Flask and React application.
Tags: javascript, react, python, webdev
Published: 2021-12-21 08:14:51

In `flask`, adding authentication has been made quite easy with the `@login_required` decorator in the flask extension `Flask-login`. I have an article on how to add basic authentication to your flask application that you can read up on [here](https://dev.to/nagatodev/adding-authentication-to-a-flask-application-53ep)

However, since you will be working with API endpoints you can't use the approach above because the `@login_required` decorator redirects to the application to an `HTML page` when it discovers a user that is not authenticated trying to access a protected page. This defeats the idea of creating API endpoints as APIs are only designed to return data in `json` format.

In this part of the series, you'll be learning how to add authentication to the connected React and Flask application you built in the previous part of the series. Authentication will be done with the flask extension: [flask-jwt-extended](https://flask-jwt-extended.readthedocs.io/en/stable/basic_usage/)

##Prerequisites
1) Beginner-level understanding of the flask framework. If you are new to `Flask` you can check out my [article](https://dev.to/nagatodev/getting-started-with-flask-1kn1) on how to set up your flask project and use it with the [jinja](https://palletsprojects.com/p/jinja/) template engine. 

2) I strongly advise you to read the previous [article](hello). You can also get the files in the Github [repo](https://github.com/Faruqt/React-Flask). 

3) Familiarity with the basics of `ReactJs`. You will be making use of the `useState` hook, fetching data from API endpoints using `axios` and also using `react-router-dom` to handle routing of components.

**Let's get started!!**

##Flask Backend

### Installing the flask extension.
Navigate into the `backend` directory and run:
```javascript
pip install flask-jwt-extended
```
**note:** If you cloned the repo, you don't need to run the command above, just set up your flask application with the instructions in the `README.md` file.

### base.py
You'll be adding authentication to the `/profile` API endpoint created in the previous tutorial. Navigate to the `base.py` script you created in the backend directory of your application to create the token(login) and logout API endpoints.
### token(login) API endpoint
```javascript
import json
from flask import Flask, request, jsonify
from datetime import datetime, timedelta, timezone
from flask_jwt_extended import create_access_token,get_jwt,get_jwt_identity, \
							   unset_jwt_cookies, jwt_required, JWTManager


api = Flask(__name__)

api.config["JWT_SECRET_KEY"] = "please-remember-to-change-me"
jwt = JWTManager(api)

@api.route('/token', methods=["POST"])
def create_token():
    email = request.json.get("email", None)
    password = request.json.get("password", None)
    if email != "test" or password != "test":
        return {"msg": "Wrong email or password"}, 401

    access_token = create_access_token(identity=email)
    response = {"access_token":access_token}
    return response

@api.route('/profile')
def my_profile():
    response_body = {
        "name": "Nagato",
        "about" :"Hello! I'm a full stack developer that loves python and javascript"
    }

    return response_body
```

Let's go through the code above:

First, the required functions are imported from the installed `flask_jwt_extended` extension.
```javascript
from flask_jwt_extended import create_access_token,get_jwt,get_jwt_identity, \
							   unset_jwt_cookies, jwt_required, JWTManager
```
Next, the flask application instance is configured with the `JWT` secret key then passed as an argument to the `JWTManager` function and assigned to the `jwt` variable.

```javascript
api.config["JWT_SECRET_KEY"] = "please-remember-to-change-me"
jwt = JWTManager(api)
```

The `token` API endpoint will have a `POST` request method. Whenever the user submits a login request, the email and password are extracted and compared with the hardcoded email(test) and password(test). Please **note** that in an ideal scenario you are going to compare the extracted login details with data in your database.

If the login details are not correct, the error message `Wrong email or password` with the status code `401` which means `UNAUTHORIZED Error` is sent back to the user.
```javascript
return {"msg": "Wrong email or password"}, 401
``` 
Else if the login details are confirmed to be correct, an access token is created for that particular email address by assigning the `email` to the `identity` variable. Finally, the token is returned to the user.
```javascript
access_token = create_access_token(identity=email)

response = {"access_token":access_token}
return response
``` 
To test this, start your backend server with 
```javascript
npm run start-backend
```
Please **note** that the command above was specified in the `package.json` file in the react frontend. This was done in the previous part of the series. If you have not checked it out yet, please head [there](https://dev.to/nagatodev/how-to-connect-flask-to-reactjs-1k8i) so you can learn how to set it up. However if you have already cloned the repo, let's proceed.

Next, open up [postman](https://web.postman.co/) and send a `POST` request to this API endpoint:
```javascript
http://127.0.0.1:5000/token
```
You'll get a `500 internal server` error 👇
![500 internal server error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6evm5ix4axj1rp2mb12o.png) 
Check your terminal and you'll see the error as well 👇

![terminal nonetype error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7typdk0o3cso8ayb8e91.png)
`AttributeError: 'NoneType' object has no attribute 'get'` the error occurred because you did not specify the login details when you made the `POST` request to the API endpoint thus a `None` value was passed as an argument to the `request.json.get` function. 

Return to `POSTMAN` and pass the login details along with the `POST` request.
![login details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iifymp0j2ymp9rv8snlw.png)
 Please ensure you adjust your settings as circled in the image above.

After making the request you should get your access token in the form:
```javascript
"access_token":"your access token will be here"
```
You can try to pass in a wrong email or password to see the `401 UNAUTHORIZED error`
![401 UNAUTHORIZED error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6yccpwbuhbi27bq0isz4.png)

### Logout API endpoint
```javascript
@api.route("/logout", methods=["POST"])
def logout():
    response = jsonify({"msg": "logout successful"})
    unset_jwt_cookies(response)
    return response
```
When the `logout` API endpoint is called, `response` is passed to the `unset_jwt_cookies` function which deletes the cookies containing the access token for the user and finally returns the success message to the user.

Head over to `Postman` once again and make a POST request to the `logout` API endpoint:
```javascript
http://127.0.0.1:5000/logout
```
You should get the response below 👇
![logout api call](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwhgdiyrmqlzkse9juro.png)
 
### Refreshing tokens
The generated token always has a `lifespan` after which it expires. To ensure that this does not happen while the user is logged in, you have to create a function that refreshes the token when it is close to the end of its lifespan. 

First, specify the `lifespan` for your generated tokens and add it as a new configuration for your application.
**Note:**You can change the time to suit your application.
```javascript
api.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
```
Then, create the function below 👇, above the `create_token` function:
```javascript
@api.after_request
def refresh_expiring_jwts(response):
    try:
        exp_timestamp = get_jwt()["exp"]
        now = datetime.now(timezone.utc)
        target_timestamp = datetime.timestamp(now + timedelta(minutes=30))
        if target_timestamp > exp_timestamp:
            access_token = create_access_token(identity=get_jwt_identity())
            data = response.get_json()
            if type(data) is dict:
                data["access_token"] = access_token 
                response.data = json.dumps(data)
        return response
    except (RuntimeError, KeyError):
        # Case where there is not a valid JWT. Just return the original respone
        return response
```
The `after_request` decorator ensures that the `refresh_expiring_jwts` function runs after a request has been made to the protected API endpoint `/profile`. The function takes as an argument, the response from the `/profile` API call.

Then, the current expiry timestamp for the user's token is obtained and compared with the specified `timestamp` for the token which is set at 30 minutes. You can change this as well.

If the expiry timestamp for the user's token happens to be  30minutes away from expiration, the token for that user is changed to a new one with the specified 1hr lifespan, and the new token is appended to the response returned to the user. But if the token is not close to expiration, the original response is sent to the user.

To conclude the backend setup, you need to add the `@jwt_required()` decorator to the `my_profile` function to prevent unauthenticated users from making requests to the API endpoint. But first, test the `/profile` API endpoint by making a `GET` request to the URL below using `Postman`:
```javascript
http://127.0.0.1:5000/profile
```
You should still get the json form of the dictionary created in the last article.
![json form of profile api call](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zisjb8jrudw85xo1gn6u.png)

Next, add the `@jwt_required()` decorator 
```javascript
@api.route('/profile')
@jwt_required() #new line
def my_profile():
    response_body = {
        "name": "Nagato",
        "about" :"Hello! I'm a full stack developer that loves python and javascript"
    }

    return response_body
```
and try to make the API request to the `/profile` endpoint using the URL above. You'll get a `401 UNAUTHORIZED error` because the token was absent when you made the request. 

![unauthorized error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/euujk8xxoproqdf7o8ef.png)

After the user logs in and gets the assigned token, the token needs to be sent with each call the user makes to the API endpoints in the backend as an `Authorization Header` in this format:
```javascript
Authorization: Bearer <access_token>
```
Before you head over to the frontend, you can also test this on `Postman` by adding the user's token to the Authorization header before you call the protected `\profile` API endpoint.

Make a `POST` request to the endpoint below to get your token and copy it out.
```javascript
http://127.0.0.1:5000/token
```
Next, add the `authorization` header key with your `token` as its value and then send the `GET` request, you should get a json response containing the dictionary with your name and about_me info.
![authorization header added](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cakm1vcyb4s7p4a1w7i.png)
 
Congratulations you have successfully added authentication to your API endpoint. After the changes and additions, this should be the final look of the `base.py` script.
```javascript
import json
from flask import Flask, request, jsonify
from datetime import datetime, timedelta, timezone
from flask_jwt_extended import create_access_token,get_jwt,get_jwt_identity, \
                               unset_jwt_cookies, jwt_required, JWTManager


api = Flask(__name__)

api.config["JWT_SECRET_KEY"] = "please-remember-to-change-me"
api.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
jwt = JWTManager(api)

@api.after_request
def refresh_expiring_jwts(response):
    try:
        exp_timestamp = get_jwt()["exp"]
        now = datetime.now(timezone.utc)
        target_timestamp = datetime.timestamp(now + timedelta(minutes=30))
        if target_timestamp > exp_timestamp:
            access_token = create_access_token(identity=get_jwt_identity())
            data = response.get_json()
            if type(data) is dict:
                data["access_token"] = access_token 
                response.data = json.dumps(data)
        return response
    except (RuntimeError, KeyError):
        # Case where there is not a valid JWT. Just return the original respone
        return response

@api.route('/token', methods=["POST"])
def create_token():
    email = request.json.get("email", None)
    password = request.json.get("password", None)
    if email != "test" or password != "test":
        return {"msg": "Wrong email or password"}, 401

    access_token = create_access_token(identity=email)
    response = {"access_token":access_token}
    return response

@api.route("/logout", methods=["POST"])
def logout():
    response = jsonify({"msg": "logout successful"})
    unset_jwt_cookies(response)
    return response

@api.route('/profile')
@jwt_required()
def my_profile():
    response_body = {
        "name": "Nagato",
        "about" :"Hello! I'm a full stack developer that loves python and javascript"
    }

    return response_body

```

Now you can head over to the react frontend where you'll be making the API endpoint calls.

##React Frontend
In the last article, you only had to make a few changes to the `App.js` file. But this time around major changes will be made and new components will also be created.

In the frontend, a `Login` component that will hold the login page will be created. This component will be rendered anytime it notices that an unauthenticated user is trying to access a page that contains a protected API endpoint. This will ensure that any request made to the backend has a token appended to it.

To start with, create a new directory `components` in the `src` directory and in it, four new components `Login.js`, `useToken.js`, `Header.js` and `Profile.js`. Then navigate back to the base directory and install `react-router-dom` before you go into the components:
```javascript
npm install react-router-dom
```

### Storage of token in the frontend
The token generated from the backend needs to be stored in your web browser after you log in. Presently, that is not the case. Whenever a user refreshes his browser page, the token gets deleted and the user would be prompted to log in once again. 

To fix this, you'll need to make use of web storage objects: `localStorage` or `sessionStorage`. You can read more on that [here](https://javascript.info/localstorage). 

i)sessionStorage: The user's token gets stored in the tab currently opened in the browser. If the user refreshes the page, the token is still retained. However, if the user opens a new tab to the same page in the web browser, the token won't reflect on that page as the new tab doesn't share the same storage with the previous one. Thus, the user would be prompted to log in again.

To see this in action, open any website of your choice and open up the `Developer tools` menu with the `Inspect Element` or `Inspect` option by right-clicking on any page in your browser. You can also see the web storage under the `Application` section.

Open up your console and store an object sample in the web storage using the sessionStorage function.
```javascript
sessionStorage.setItem('test', 53)
```
Then to get the value `53` assigned to the key `test` above run:
```javascript
sessionStorage.getItem('test')
```
![session and local storage test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzdztspwnzrk3afunu5x.png)
Refresh the page and run the `getItem` function again, you'll still get the value from the storage.

Now, open the link to the same page you just worked with, in a new tab, and try to access the stored object value via the console:
```javascript
sessionStorage.getItem('test')
```
You'll get a `null` value because the current tab doesn't have access to the storage of the previous tab. 

**note:** while carrying out all the tests above, keep an eye on the changes occurring in the `web storage` section above your `console`.

ii)localStorage: Here, the user's token get's stored in universal storage that can be accessed by all tabs and browser windows. The token is still retained even if the user refreshes or closes the page, creates a new tab or window, or restarts the browser entirely.

```javascript
localStorage.setItem('test', 333)
```
Then to get the assigned value `333`:
```javascript
localStorage.getItem('test')
```
Try to run the duplicate test done above, you'll notice that you can access the value from the duplicated page. You can also create a new browser window, open any page of the same website and try to access the value set above. You'll notice that you still have access to it. That is the beauty of using `localStorage`, it ensures that the user only needs to log in once and they can easily navigate to any page on the website. 

Whenever you are done, you can delete the object from the storage using:
```javascript
localStorage.removeItem("token")
```
### useToken.js
Now you need to replicate what was done above in your react code. Open the `useToken` component.
```javascript
import { useState } from 'react';

function useToken() {

  function getToken() {
	const userToken = localStorage.getItem('token');
	return userToken && userToken
  }

  const [token, setToken] = useState(getToken());

  function saveToken(userToken) {
    localStorage.setItem('token', userToken);
    setToken(userToken);
  };

  function removeToken() {
	localStorage.removeItem("token");
    setToken(null);
  }

  return {
    setToken: saveToken,
    token,
	removeToken
  }

}

export default useToken;
``` 
With the tests you carried out in the console, the functions created in the `useToken` component should be easy to understand.

The `getToken` function is used to retrieve the `token` stored in the `localStorage` and only returns a token if it exists hence the use of the `&&` conditional operator.

The useState hook is used to handle the state of the `token ` variable which will contain the value of the token. This ensures that the react application always reloads when any of the functions are called. Such that when a user logs in and the token is stored or when the user logs out, the application also becomes aware that a change has occurred in the web storage of your browser and hence reacts accordingly by either redirecting to the page the user wants to access or returning to the login page once the user logs out.

The `saveToken` function handles the storage of the token obtained when the user logs in and the `setToken` function in it updates the state of the `token` variable with the `token` passed as an argument to the `saveToken` function.

The `removeToken` function deletes the token from the local storage and returns the token back to the null state whenever it gets called.

Finally, the `saveToken` function assigned as a value to the setToken variable, the value of the `token` itself and the `removeToken` function are all returned as the result of calling the `useToken` function.

### App.js
I told you that you'll be making major changes right? 😜. Clean up `App.js`; all the code that was added the last time will be moved into the `Profile` component. 
```javascript 
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import Login from './components/Login'
import Profile from './components/Profile'
import Header from './components/Header'
import useToken from './components/useToken'
import './App.css'

function App() {
  const { token, removeToken, setToken } = useToken();

  return (
    <BrowserRouter>
      <div className="App">
        <Header token={removeToken}/>
        {!token && token!=="" &&token!== undefined?  
        <Login setToken={setToken} />
        :(
          <>
            <Routes>
              <Route exact path="/profile" element={<Profile token={token} setToken={setToken}/>}></Route>
            </Routes>
          </>
        )}
      </div>
    </BrowserRouter>
  );
}

export default App;

 ```
At the top of the file, the `BrowserRouter`, `Route`, `Routes` functions that will be used to handle URL routing for the profile component are imported from the installed `react-router-dom` package. The other created components are also imported from the `components` folder.

In the `App` function, the value object returned when the `useToken` function is called is destructured and the values are assigned to the `token`, `removeToken` and `setToken` variables respectively. 
```javascript
const { token, removeToken, setToken } = useToken();
```
Next, the `BrowserRouter` function is made the parent component and in it, the `Header` component is placed with the `removeToken` function passed as an argument which is called `prop` in react.
```javascript
<Header token={removeToken}/>
```
Then the javascript conditional ternary operator is used to ensure that the user must have a token before having access to the `profile` component. If the user doesn't have a token, the `Login` component is rendered with the `setToken` function passed as an argument. Else if the user already has a token, the `Profile` component with the URL path `/profile` is rendered and displayed to the user.

You can read more on how to use `React Router` [here](https://www.geeksforgeeks.org/reactjs-router/)

Now, you need to create the Login, Header, and Profile functions in your `Login`, Header, and `Profile` component files respectively.

### Login.js
```javascript
import { useState } from 'react';
import axios from "axios";

function Login(props) {

    const [loginForm, setloginForm] = useState({
      email: "",
      password: ""
    })

    function logMeIn(event) {
      axios({
        method: "POST",
        url:"/token",
        data:{
          email: loginForm.email,
          password: loginForm.password
         }
      })
      .then((response) => {
        props.setToken(response.data.access_token)
      }).catch((error) => {
        if (error.response) {
          console.log(error.response)
          console.log(error.response.status)
          console.log(error.response.headers)
          }
      })
  
      setloginForm(({
        email: "",
        password: ""}))
  
      event.preventDefault()
    }
    
    function handleChange(event) { 
      const {value, name} = event.target
      setloginForm(prevNote => ({
          ...prevNote, [name]: value})
      )}

    return (
      <div>
        <h1>Login</h1>
          <form className="login">
            <input onChange={handleChange} 
                  type="email"
                  text={loginForm.email} 
                  name="email" 
                  placeholder="Email" 
                  value={loginForm.email} />
            <input onChange={handleChange} 
                  type="password"
                  text={loginForm.password} 
                  name="password" 
                  placeholder="Password" 
                  value={loginForm.password} />
          
          <button onClick={logMeIn}>Submit</button>
        </form>
      </div>
    );
}

export default Login;

```
The code above should be easy to understand, the summary of what it does is to use the login details provided by the user to make a `POST` request to the `/token` API endpoint in the backend which then returns the user's token and the token is stored in the local web storage using the `setToken` function passed as a prop to the Login function.

### Header.js
```javascript
import logo from '../logo.svg'
import axios from "axios";

function Header(props) {

  function logMeOut() {
    axios({
      method: "POST",
      url:"/logout",
    })
    .then((response) => {
       props.token()
    }).catch((error) => {
      if (error.response) {
        console.log(error.response)
        console.log(error.response.status)
        console.log(error.response.headers)
        }
    })}

  	return(
		<header className="App-header">
			<img src={logo} className="App-logo" alt="logo" />
			<button onClick={logMeOut}> 
        		Logout
      		</button>
		</header>
	)
}

export default Header;

```
Once the user clicks on the `Logout` button, a `POST` request is made to the `/logout` API endpoint, and the cookies in which the user's JWToken is stored are cleared on the backend. The `Axios` response function is used to call the `removeToken` function which deletes the `token` stored in the local web storage. Now, if the user tries to access the `/profile` page, the user gets redirected to the login page.

### Profile.js
```javascript
import { useState } from 'react'
import axios from "axios";

function Profile(props) {

  const [profileData, setProfileData] = useState(null)
  function getData() {
    axios({
      method: "GET",
      url:"/profile",
      headers: {
        Authorization: 'Bearer ' + props.token
      }
    })
    .then((response) => {
      const res =response.data
      res.access_token && props.setToken(res.access_token)
      setProfileData(({
        profile_name: res.name,
        about_me: res.about}))
    }).catch((error) => {
      if (error.response) {
        console.log(error.response)
        console.log(error.response.status)
        console.log(error.response.headers)
        }
    })}

  return (
    <div className="Profile">

        <p>To get your profile details: </p><button onClick={getData}>Click me</button>
        {profileData && <div>
              <p>Profile name: {profileData.profile_name}</p>
              <p>About me: {profileData.about_me}</p>
            </div>
        }
        
    </div>
  );
}

export default Profile;

```
The piece of code previously in `App.js` was moved here. This contains the protected endpoint `\profile`. A `GET` request method is sent to the endpoint whenever the `Click me` button is clicked and it responds with the user's details. 

For the user to be able to access the data of the `\profile` API endpoint, an Authorization header that contains the token must be added to the axios `GET` request.
```javascript
headers: {
        Authorization: 'Bearer ' + props.token
      }
```
If the response contains an `access token`, this means that the current token is near expiration and the server has created a new token. So the token stored in the local storage is updated with the newly generated token.
```javascript
res.access_token && props.setToken(res.access_token)
```
### App.css
You also need to make a change to the CSS style for the header. On line 16 you'll see the style for the header component `.App-header`. Comment out or delete the `  /* min-height: 100vh; */` code so your application can end up looking like 👇:

![final application look](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ymn70ryf2rm1mg5gr1dn.png)

Now to test your application, start the backend server by running the script below
```javascript
npm run start-backend
``` 
followed by :
```javascript
npm start
``` 
Then navigate to the `http://localhost:3000/profile` URL in your web browser and you'll be prompted to login since the page is protected. I hope you still remember the login details: `email:test` and `password:test`. You can also open up `localStorage` under the `Application` section in `Developer tools` to monitor the token as it gets stored and deleted.

It's been a long ride, but we have finally come to the end of this tutorial. With what you have learned, I believe you can easily authenticate your flask plus react applications. Congratulations on the new knowledge you just acquired.

If you have any questions, feel free to drop them as a comment or send me a message on [Linkedin](https://www.linkedin.com/in/faruq-abdulsalam) or [Twitter](https://twitter.com/Ace_II) and I'll ensure I respond as quickly as I can. Ciao 👋