Building a JWT login and registration backend with flask-praetorian for ReactJS frontend

patriciadourado

Patricia Dourado

Posted on September 10, 2021

Building a JWT login and registration backend with flask-praetorian for ReactJS frontend

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.

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

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 as database and pgAdmin 4 interface to create the DB and table.

pgadmin4

Create users table

The SQL for the created users table is the following:

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)
Enter fullscreen mode Exit fullscreen mode

DB Model

A model that might be used using flask-praetorian:

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
Enter fullscreen mode Exit fullscreen mode

Initialize Flask App

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)
Enter fullscreen mode Exit fullscreen mode

SQLAlchemy

The SQLAlchemy 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:

# 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)
Enter fullscreen mode Exit fullscreen mode

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!

@app.route('/api/')
def home():
    return {"Hello": "World"}, 200
Enter fullscreen mode Exit fullscreen mode

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;

@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
Enter fullscreen mode Exit fullscreen mode

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;

@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
Enter fullscreen mode Exit fullscreen mode

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;

@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)}
Enter fullscreen mode Exit fullscreen mode

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;

@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
Enter fullscreen mode Exit fullscreen mode

Run Flask App

# Run
if __name__ == '__main__':
    app.run()
Enter fullscreen mode Exit fullscreen mode

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

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

  • Create a Heroku account here;
  • Download and Install Heroku CLI: link;
  • 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 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

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

Frontend Application

For now the ReactJS application (check the repository here) 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

Registration Page

Registration

Reset Password Page

Reset Password

Note 2: You can check the whole jwtlogin flask application code in this github repository and the deployed with ReactJS part on its description link;

Inspiration and reference links:

💖 💪 🙅 🚩
patriciadourado
Patricia Dourado

Posted on September 10, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related