Building a JWT login and registration backend with flask-praetorian for ReactJS frontend
Tags: flask, postgres, jwt, react
Published: 2021-09-10 05:54:55

This is a tutorial to help you build a JWT based login application and registration using the micro web framework **Flask**.

> **Note: This application has been updated and now has more features that are not described in this article (as confirmation email, reset password and etc), but it will be described in future articles. The repository of this application has the updated version.**

Before running the Flask API its necessary to install a bunch of packages as you can check listed here [requirements.txt](https://github.com/patriciadourado/jwtlogin-flask/blob/master/requirements.txt).

## Python Virtual Environment

To install the required modules I've used `Python virtualenv` to create a isolated Virtual Environment in Python so the project can have it's own dependencies independently of other project's dependencies. In resume: for not installing globally this modules. 

![Python virtualenv](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/woynqrjairnkuphfz8hi.jpg)

### Installation

To install `virtualenv` just run the following command on your project folder (here we use pip on windows):

`py -3 -m pip install --user virtualenv`

#### Creating a Virtual Environment

To create a Virtual Environment name **myproject**:

`py -3 -m venv myproject`

You will see a new folder created called **myproject**

#### Activation

To activate and use your new virtual environment, just run:

`myproject\Scripts\activate`

Now you can start to install the modules and packages you want and run your project on the new environment. 

To install requeriments.txt just this command:

`pip install -r requirements.txt`

To deactivate **myproject** just run: `deactivate`.

## PostegreSQL

Its also necessary to create a database and **users** table before anything. I've used [PostegreSQL](https://www.postgresql.org/) as database and pgAdmin 4 interface to create the DB and table.

![pgadmin4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jl0up3ukkrl6d8zhvf9q.png)

### Create users table

The SQL for the created users table is the following:

```SQL
CREATE TABLE public.users
(
    id integer NOT NULL DEFAULT nextval('users_id_seq'::regclass),
    username text COLLATE pg_catalog."default" NOT NULL,
    password text COLLATE pg_catalog."default" NOT NULL,
    roles text COLLATE pg_catalog."default",
    is_active boolean,
    CONSTRAINT users_pkey PRIMARY KEY (id)
)

TABLESPACE pg_default;

ALTER TABLE public.users
    OWNER to (insert here your user_database)
```

## DB Model

A model that might be used using **flask-praetorian**:

```python
class User(db.Model):
    
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.Text, unique=True, nullable=False)
    password = db.Column(db.Text, nullable=False)
    roles = db.Column(db.Text)
    is_active = db.Column(db.Boolean, default=True, server_default='true')

    @property
    def rolenames(self):
        try:
            return self.roles.split(',')
        except Exception:
            return []

    @classmethod
    def lookup(cls, username):
        return cls.query.filter_by(username=username).one_or_none()

    @classmethod
    def identify(cls, id):
        return cls.query.get(id)

    @property
    def identity(self):
        return self.id

    def is_valid(self):
        return self.is_active
```

## Initialize Flask App

```python
app = flask.Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'my secret key'
app.config['JWT_ACCESS_LIFESPAN'] = {'hours': 24}
app.config['JWT_REFRESH_LIFESPAN'] = {'days': 30}

# Initialize the flask-praetorian instance for the app
guard.init_app(app, User)
```

## SQLAlchemy

The [SQLAlchemy](https://www.sqlalchemy.org/) was used as the Python ORM for accessing data from the database and facilitate the communication between app and db converting function calls to SQL statements.

Do not forget to change ***'SQLALCHEMY_DATABASE_URI'*** to your own here:

```python
# Initialize a local database
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user_database:password@hostname:5432/database_name'
db.init_app(app)

# Initializes CORS so that the api_tool can talk to app
cors.init_app(app)
```

## Endpoints

Some endpoints were defined to be consumed by the frontend application, they are:

**1. /api/**

The first endpoint is the confirmation our API is up running!

```python
@app.route('/api/')
def home():
    return {"Hello": "World"}, 200
```

**2. /api/login**

The second endpoint receives the user credentials (by POST request) and authenticates/logs it with flask-praetorian 'authenticate' method issuing a user JWT access token and returning a 200 code with the token;

```python
@app.route('/api/login', methods=['POST'])
def login():
    """
    Logs a user in by parsing a POST request containing user credentials and
    issuing a JWT token.
    """
    req = flask.request.get_json(force=True)
    username = req.get('username', None)
    password = req.get('password', None)
    user = guard.authenticate(username, password)
    ret = {'access_token': guard.encode_jwt_token(user)}
    return ret, 200
```

**3. /api/refresh**

The third endpoint refreshes (by POST request) an existing JWT creating a new one with a new access expiration, returning a 200 code with the new token;

```python
@app.route('/api/refresh', methods=['POST'])
def refresh():
    """
    Refreshes an existing JWT by creating a new one that is a copy of the old
    except that it has a refreshed access expiration.
    .. example::
       $ curl http://localhost:5000/api/refresh -X GET \
         -H "Authorization: Bearer <your_token>"
    """
    print("refresh request")
    old_token = Request.get_data()
    new_token = guard.refresh_jwt_token(old_token)
    ret = {'access_token': new_token}
    return ret, 200
```

**4. /api/protected**

The fourth endpoint is a protected endpoint which requires a header with a valid JWT using the `@flask_praetorian.auth_required` decorator. The endpoint returns a message with the current user username as a secret message;

```python
@app.route('/api/protected')
@flask_praetorian.auth_required
def protected():
    """
    A protected endpoint. The auth_required decorator will require a header
    containing a valid JWT
    .. example::
       $ curl http://localhost:5000/api/protected -X GET \
         -H "Authorization: Bearer <your_token>"
    """
    return {'message': 'protected endpoint (allowed usr {})'.format(flask_praetorian.current_user().username)}
```

**5. /api/registration**

The fifth endpoint is a simple user registration without requiring user email (for now), with the password hash method being invoked only to demonstrate insertion into database if its a new user;

```python
@app.route('/api/registration', methods=['POST'])
def registration():
    
    """Register user without validation email, only for test"""

    req = flask.request.get_json(force=True)
    username = req.get('username', None)
    password = req.get('password', None)
    
    with app.app_context():
        db.create_all()
        if db.session.query(User).filter_by(username=username).count() < 1:
            db.session.add(User(
                username=username,
                password=guard.hash_password(password),
                roles='user'
            ))
        db.session.commit()
    
    user = guard.authenticate(username, password)
    ret = {'access_token': guard.encode_jwt_token(user)}

    return ret,200
```

## Run Flask App

```python
# Run
if __name__ == '__main__':
    app.run()
```

## Running Locally

To run your application locally you can use the following command:

`flask run`

## Deploying the Application

This application was deployed on **Heroku.**

![heroku](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/33pwbw64n25tzytnz7bo.png)

**If you want to deploy to Heroku, follow the steps:**

* Create a Heroku account [here](https://signup.heroku.com/login);
* Download and Install Heroku CLI: [link](https://devcenter.heroku.com/articles/heroku-cli#download-and-install);
* Login into Heroku (on cli);
* Its necessary to add a Heroku *Procfile* on flask directory to map the remote app:
    * Create a file called **Procfile without** extension with the following line: **`web: gunicorn app:app`**
* Create a **requirements.txt** file with all the installed requirements for flask app runs; (see it in pip freeze)
or just use the following command: **`pip freeze > requirements.txt`**;
* On Heroku website (platform) create a new app called **myapp**;
* After installed heroku on CLI run: **`heroku login`** (it will make the login on web page pop-up);
* On Heroku website:
    * Create a database:  **`heroku addons:create heroku-postgresql:hobby-dev --app myapp`**
    * To see the URL of database: **`heroku config --app myapp`**
* You will need to create the PostegreSQL database table that we described on PostegreSQL section but on Heroku now, I did it using the pgAdmin interface linked to the address host of the Heroku database we created on the step above.
    * The database address host, user and password you can find on **Database Credentials** on your Heroku app settings. You can follow [this article](https://medium.com/@vapurrmaid/getting-started-with-heroku-postgres-and-pgadmin-run-on-part-2-90d9499ed8fb) if you need more help;
* Initiate a local git repository: **`git init`** Add on git the following files: **`app.py requirements.txt Procfile`** *(ignore venv, __pycashe__ with .gitignore)*;
* Don't forget to **commit** your changes;
* Link your local repository to heroku **`heroku git:remote -a myapp`**;
* Push to Heroku your commits **`git push heroku master`**;

## Flask-praetorian

To let the things easier Flask-praetorian was used to handle the hard logic by itself. 

![flask-praetorian](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5cwiijslaellw7gglm23.png)

Among the advantages of using Flask-praetorian in this API (where the most important is undoubtedly allowing to use JWT token for authentication) are:

* Hash passwords for storing in database;
* Verify plaintext passwords against the hashed, stored versions;
* Generate authorization tokens upon verification of passwords;
* Check requests to secured endpoints for authorized tokens;
* Supply expiration of tokens and mechanisms for refreshing them;
* Ensure that the users associated with tokens have necessary roles for access;

You can check Flask-praetorian documentation here: [Flask-praetorian](https://flask-praetorian.readthedocs.io/en/latest/index.html#table-of-contents)

## Frontend Application

For now the ReactJS application (check the repository [here](https://github.com/patriciadourado/jwtlogin-reactjs)) that consumes this Flask API provides three different pages:

1. The `Home page` with the login button (if the user isn't logged) and with the secret button and the logout button (assuming the user is logged);
2. The `Login Page` where the user can log-in;
3. The `Protected page` with a content message that only the logged user can view;

**Note: As I said at the beginning of the article, the application has been updated both the backend and the frontend, check some new pages:**

### Login Page
![Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkduqrrkr1efbb8tr4jt.png)
### Registration Page
![Registration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxacmn2o00lxnfgtroyi.png)
### Reset Password Page
![Reset Password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kujw9ef85h5c3bksr5fl.png)

**Note 2:** You can check the whole jwtlogin flask application code in this **[github repository](https://github.com/patriciadourado/jwtlogin-flask)** and the deployed with ReactJS part on its description link;

### Inspiration and reference links:

* [Setting up & Deploying JWT based auth using Flask & React](http://yasoob.me/posts/how-to-setup-and-deploy-jwt-auth-using-react-and-flask/)
* [Flask by Example – Setting up Postgres, SQLAlchemy, and Alembic](https://realpython.com/flask-by-example-part-2-postgres-sqlalchemy-and-alembic/)
* [Building a secure admin interface with Flask-Admin and Flask-Security](https://ckraczkowsky.medium.com/building-a-secure-admin-interface-with-flask-admin-and-flask-security-13ae81faa05)
* [Heroku: Deploy your Flask App with a Database Online](https://medium.com/analytics-vidhya/heroku-deploy-your-flask-app-with-a-database-online-d19274a7a749)
* [Getting Started with Heroku, Postgres and PgAdmin — RUN__ON Part 2](https://medium.com/@vapurrmaid/getting-started-with-heroku-postgres-and-pgadmin-run-on-part-2-90d9499ed8fb)