Using JWT for user authentication in Flask

Pre-requisite: Basic knowledge about JSON Web Token (JWT)

I will be asuming you have the basic knowledge of JWT and how JWT works. If not, then I suggest reading the linked Geeksforgeeks article.

Let’s jump right into the setup. Ofcource, you need python3 installed on your system. Now, follow along with me. I will be using a virtual environment where I will install the libraries which is undoubtedly the best way of doing any kind of development.

  1. First create a folder named flask project and change directory to it. If you are on linux then type the following in your terminal.

    mkdir "flask project" && cd "flask project"
  2. Now, create a virtual environment. If you are on linux then type the following in your terminal.



    python3 -m venv env

    Note: If you get any error then that means venv isn’t installed in your system. To install it, type sudo apt install python3-venv in your terminal and then you are good to go. If you are on windows then use something like virtualenv to make a virtual environment.
    This will create a folder named venv in the flask project which will contain the project specific libraries.

  3. Now create a file named requirements.txt and add the following lines in it.

    Flask-RESTful==0.3.8
    PyJWT==1.7.1
    Flask-SQLAlchemy==2.4.1
  4. Now, lets install these libraries for this project. To do so, first we need to activate the virtual enviornment. To do so, type the following in your terminal.

    source env/bin/activate

    Note: If you are on windows then it would be Scripts instead of bin
    Now, its time to install the libraries. To do so, again type the following in your terminal.

    pip install -r requirements.txt

Now, we are done with the setup part. Lets now start writing the actual code. Before beginning with the code, I would like to make something clear. I would be writing the entire code in a single file, i.e. the database models and the routes all together, which is not a good practice and definitely not manageable for larger projects. Try keeping creating seperate python files or modules for routes and database models.
With that cleared out, lets directly jump into the writing the actual code. I will be adding inline comments explaining every part of the code.

Create a python file called app.py and type the following code in it.

filter_none

edit
close

play_arrow

link
brightness_4
code

# flask imports
from flask import Flask, request, jsonify, make_response
from flask_sqlalchemy import SQLAlchemy
import uuid # for public id
from  werkzeug.security import generate_password_hash, check_password_hash
# imports for PyJWT authentication
import jwt
from datetime import datetime, timedelta
from functools import wraps
   
# creates Flask object
app = Flask(__name__)
# configuration
# NEVER HARDCODE YOUR CONFIGURATION IN YOUR CODE
# INSTEAD CREATE A .env FILE AND STORE IN IT
app.config['SECRET_KEY'] = 'your secret key'
# database name
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///Database.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# creates SQLALCHEMY object
db = SQLAlchemy(app)
   
# Database ORMs
class User(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    public_id = db.Column(db.String(50), unique = True)
    name = db.Column(db.String(100))
    email = db.Column(db.String(70), unique = True)
    password = db.Column(db.String(80))
   
# decorator for verifying the JWT
def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = None
        # jwt is passed in the request header
        if 'x-access-token' in request.headers:
            token = request.headers['x-access-token']
        # return 401 if token is not passed
        if not token:
            return jsonify({'message' : 'Token is missing !!'}), 401
   
        try:
            # decoding the payload to fetch the stored details
            data = jwt.decode(token, app.config['SECRET_KEY'])
            current_user = User.query\
                .filter_by(public_id = data['public_id'])\
                .first()
        except:
            return jsonify({
                'message' : 'Token is invalid !!'
            }), 401
        # returns the current logged in users contex to the routes
        return  f(current_user, *args, **kwargs)
   
    return decorated
   
# User Database Route
# this route sends back list of users users
@app.route('/user', methods =['GET'])
@token_required
def get_all_users(current_user):
    # querying the database
    # for all the entries in it
    users = User.query.all()
    # converting the query objects
    # to list of jsons
    output = []
    for user in users:
        # appending the user data json 
        # to the response list
        output.append({
            'public_id': user.public_id,
            'name' : user.name,
            'email' : user.email
        })
   
    return jsonify({'users': output})
   
# route for loging user in
@app.route('/login', methods =['POST'])
def login():
    # creates dictionary of form data
    auth = request.form
   
    if not auth or not auth.get('email') or not auth.get('password'):
        # returns 401 if any email or / and password is missing
        return make_response(
            'Could not verify',
            401,
            {'WWW-Authenticate' : 'Basic realm ="Login required !!"'}
        )
   
    user = User.query\
        .filter_by(email = auth.get('email'))\
        .first()
   
    if not user:
        # returns 401 if user does not exist
        return make_response(
            'Could not verify',
            401,
            {'WWW-Authenticate' : 'Basic realm ="User does not exist !!"'}
        )
   
    if check_password_hash(user.password, auth.get('password')):
        # generates the JWT Token
        token = jwt.encode({
            'public_id': user.public_id,
            'exp' : datetime.utcnow() + timedelta(minutes = 30)
        }, app.config['SECRET_KEY'])
   
        return make_response(jsonify({'token' : token.decode('UTF-8')}), 201)
    # returns 403 if password is wrong
    return make_response(
        'Could not verify',
        403,
        {'WWW-Authenticate' : 'Basic realm ="Wrong Password !!"'}
    )
   
# signup route
@app.route('/signup', methods =['POST'])
def signup():
    # creates a dictionary of the form data
    data = request.form
   
    # gets name, email and password
    name, email = data.get('name'), data.get('email')
    password = data.get('password')
   
    # checking for existing user
    user = User.query\
        .filter_by(email = email)\
        .first()
    if not user:
        # database ORM object
        user = User(
            public_id = str(uuid.uuid4()),
            name = name,
            email = email,
            password = generate_password_hash(password)
        )
        # insert user
        db.session.add(user)
        db.session.commit()
   
        return make_response('Successfully registered.', 201)
    else:
        # returns 202 if user already exists
        return make_response('User already exists. Please Log in.', 202)
   
if __name__ == "__main__":
    # setting debug to True enables hot reload
    # and also provides a debuger shell
    # if you hit an error while running the server
    app.run(debug = True)

chevron_right


Now, our code is ready. We now need to create the database first and then the table User from the ORM (Object Relational Mapping). To do so, first start the python3 interpreter in your terminal. You can do that by typing python3 in your terminal and that should do the trick for you.

Next you need to type the following in your python3 interpreter:

from app import db
db.create_all()

So, what this does is first it imports the database object and then calls the create_all() function to create all the tables from the ORM. It should look something like this.



python interpreter

Now that our actual code is ready, let’s test it out. I recommend using postman for testing out the APIs. You can use something like CURL but I will be using postman for this tutorial.

To start testing our api, first we need to run our API. To do so, open up a terminal window and type the following in it.

python app.py

You should see an output like this

Python app.py

If you get any error then make sure all your synta and indentation are correct. You can see that our api is running on http://localhost:5000/. Copy this url. We will use this urlalong with the routes to test the api.

Now, open up Postman. You should be greated with the following screen.

Postman Greeting

Now, click on the + sign and enter the url localhost:5000/signup change request type to POST, then select Body and then form-data and enter the data as key-value pair and then click on Send and you should get a response. It should look something like this.

Signup



So, we are registered. Now lets login. To do that just change the endpoint to /login and untick the Name field and click on Send. You should get a JWT as a response. Note down that JWT. That will be our token and we will need to send that token along with every subsequent requests. This token will identify us as loged in.

Login

The JSON contains the token. Note it down. Next try to fetch the list of users. To do that, change the endpoint to /user and then in the headers section, add a field as x-access-token and add the JWT token in the value and click on Send. You will get the list of users as JSON.

User

So, this is how you can perform authetication with JWT in Flask. I recommend you to practice more with JWTs and user authentication to get your concepts more clear.

Attention geek! Strengthen your foundations with the Python Programming Foundation Course and learn the basics.

To begin with, your interview preparations Enhance your Data Structures concepts with the Python DS Course.




My Personal Notes arrow_drop_up

I code for fun

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.


Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.