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.
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.
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)
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
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)
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)
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
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
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
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)}
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
Run Flask App
# 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.
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 file called Procfile without extension with the following line:
- 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
- Create a database:
- 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.
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:
- 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); - The
Login Page
where the user can log-in; - 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
Registration Page
Reset Password Page
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:
- Setting up & Deploying JWT based auth using Flask & React
- Flask by Example – Setting up Postgres, SQLAlchemy, and Alembic
- Building a secure admin interface with Flask-Admin and Flask-Security
- Heroku: Deploy your Flask App with a Database Online
- Getting Started with Heroku, Postgres and PgAdmin — RUN__ON Part 2
Top comments (1)
not as you think, give it a try!