Full-stack tutorial — 3: Flask + jwt

Part III : Authentication using JSON Web Tokens

Riken Mehta
8 min readJun 10, 2018

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. You can read more about JWT here.

Basically, rather than adding user information in every request we make to our server, we can send an encrypted token which is compact & self contained.

Let’s explain some concepts further.

  • Compact: Because of their smaller size, JWTs can be sent through a URL, POST parameter, or inside an HTTP header. Additionally, the smaller size means transmission is fast.
  • Self-contained: The payload contains all the required information about the user, avoiding the need to query the database more than once. (Source: https://jwt.io/introduction/)

Throughout this tutorial series, you will be reading about building a full-stack web application using flask + react + docker:

In this tutorial series we will eventually build a to-do application with user login & live update of the data. We will be using react as our front-end JavaScript library & MongoDB as our database.

We will be using Flask-JWT-Extended module which internally uses PyJWT. In this — Part III — tutorial, we will update the user APIs, we have setup in Part II, to use authentication. Throughout this post, we will cover :

  • Integrating Flask-JWT-Extended module with our flask server.
  • Using jsonschema to validate the API request object.
  • Creating user registration & authentication route.

Note: This is Part III of the tutorial series for full-stack web app development. If you haven’t go through the — Part I & Part II — yet, please consider going through them first, because we will be using many references to the code implemented in previous posts.

Directory structure

Integrating Flask-JWT-Extended module with our flask server:

We will update our modules/app/__init__.py to initialize the flask-jwt-extended object.

Import JWTManager from flask_jwt_extended module. Also, we will need to encrypt the password set by the user before saving it to database. It is considered as bad practice & a major security threat to save the password as it is. So we will be using flask_bcrypt module for that.

from flask_jwt_extended import JWTManager
from flask_bcrypt import Bcrypt

There are couple of config variables we have to add in app object and then initialize the instance of JWTManager. Same way we also initialize the Bcrypt object.

# create the flask object
app = Flask(__name__)
app.config['MONGO_URI'] = os.environ.get('DB')
app.config['JWT_SECRET_KEY'] = os.environ.get('SECRET')
app.config['JWT_ACCESS_TOKEN_EXPIRES'] =
datetime.timedelta(days=1)
mongo = PyMongo(app)
flask_bcrypt = Bcrypt(app)
jwt = JWTManager(app)
app.json_encoder = JSONEncoder

This way, we have just integrated both the module with our flask server.

Note: Remember, we need to declare the environment variable named ‘SECRET’ in our docker-compose.yml file. This can be any string, which will remain same, and will be used internally by PyJWT while generating the web token.

Using jsonschema to validate the API request object:

Remember we added many if-else statements to check if the data passed along with the API request object was valid & in required format or not? Now, as the number of parameters in request object increase, it becomes too messy to check for each and every entity. Also, we will have to verify if the format and type of the data passed is valid.

This is exactly where we can use jsonschema module and its validations to do the task for us. Let’s create a directory inside our app folder named schemas. We will be defining the schema and also it’s validation functions for every controller in different files. Let’s start by creating a file named user.py inside schemas directory.

The code above basically declare the schema & write a function to validate it. If there are some error, we return the error message along with error flag. To read more about what are the different configurations available for json schema, you can access the detailed documentation here.

In the schema, we basically defined the different properties we will have in a request object for user creation, and type of the data. We can also add required properties and some constrains like minLength, maxLength etc.

By adding :

"additionalProperties" : False

we make sure that the request object does not contain any extra fields. In the validation method, we use the inbuilt validation function which will throw different errors on various types of schema violations. We catch those errors and return them. We will be using these method in our usual flask routes to make sure that we get the required data in the request object.

Creating user registration & authentication route:

Now, lets see how we can add a register route in the “modules/controllers/user.py” file. At the top, import the validation method we defined in user schema file.

from app.schemas import validate_user

Then define the route like this. Basically we just moved the POST method of previously written user route in a separate route.

@app.route('/register', methods=['POST'])
def register():
''' register user endpoint '''
data = validate_user(request.get_json())
if data['ok']:
data = data['data']
data['password'] = flask_bcrypt.generate_password_hash(
data['password'])
mongo.db.users.insert_one(data)
return jsonify({'ok': True, 'message': 'User created
successfully!'}), 200
else:
return jsonify({'ok': False, 'message': 'Bad request
parameters: {}'.format(data['message'])}), 400

At first, we check if the received data is valid & in required format. If yes, we go on encrypting the password using flask_bcrypt object we created in app.py file. And then we store the data in our database. If there are any error with the request object, we respond the request with bad request parameters message.

Now, let’s create a auth route, which will be called the very first time to generate the JSON token. So, the basic pipeline will be something like this.

(Source: https://jwt.io/introduction/)

In our case, ‘/auth’ will be the login route. We will create a JWT and return that to the browser. Now, on all the subsequent requests, we will check if the same JWT exists in the Authorization header of the request. The ‘/auth’ route will be something like this.

@app.route('/auth', methods=['POST'])
def auth_user():
''' auth endpoint '''
data = validate_user(request.get_json())
if data['ok']:
data = data['data']
user = mongo.db.users.find_one({'email': data['email']},
"_id": 0})
if user and
flask_bcrypt.check_password_hash(user['password'],
data['password']):
del user['password']
access_token = create_access_token(identity=data)
refresh_token = create_refresh_token(identity=data)
user['token'] = access_token
user['refresh'] = refresh_token
return jsonify({'ok': True, 'data': user}), 200
else:
return jsonify({'ok': False, 'message': 'invalid
username or password'}), 401
else:
return jsonify({'ok': False, 'message': 'Bad request
parameters: {}'.format(data['message'])}), 400

This route will first validate the data. Then see if the user with given username exists in the database, and also check if the password matches. If both conditions passed, it will create access_token & refresh_token. Basically, access_token is the JWT created using the user object. These tokens gets expired in short time. We have configured this time while we were integrating flask_jwt_extended module in our app.py file.

app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 
datetime.timedelta(days=1)

refresh_tokens are used to basically get the new access token without calling the auth method. We can define a ‘/refresh’ route, which will take care of this.

@app.route('/refresh', methods=['POST'])
@jwt_refresh_token_required
def refresh():
''' refresh token endpoint '''
current_user = get_jwt_identity()
ret = {
'token': create_access_token(identity=current_user)
}
return jsonify({'ok': True, 'data': ret}), 200

This route uses a decorator from flask_jwt_extended module named, ‘jwt_refresh_token_required’. This means, the request object must contain refresh_token in the Authorization header. By calling get_jwt_identity() method, we can get the user data. And using this data, we will create a new token.

Now, we will see how to make sure that every method will have the validation for Authorization header. For that, we have a similar decorator function named ‘jwt_required’

Here is the modified ‘/user’ route.

@app.route('/user', methods=['GET', 'DELETE', 'PATCH'])
@jwt_required
def user():
''' route read user '''
if request.method == 'GET':
query = request.args
data = mongo.db.users.find_one(query, {"_id": 0})
return jsonify({'ok': True, 'data': data}), 200
data = request.json()
if request.method == 'DELETE':
if data.get('email', None) is not None:
db_response = mongo.db.users.delete_one({'email':
data['email']})
if db_response.deleted_count == 1:
response = {'ok': True, 'message': 'record
deleted'}
else:
response = {'ok': True, 'message': 'no record
found'}
return jsonify(response), 200
else:
return jsonify({'ok': False, 'message': 'Bad request
parameters!'}), 400
if request.method == 'PATCH':
if data.get('query', {}) != {}:
mongo.db.users.update_one(data['query'], {'$set':
data.get('payload', {})})
return jsonify({'ok': True, 'message': 'record
updated'}), 200
else:
return jsonify({'ok': False, 'message': 'Bad request
parameters!'}), 400

We have now added the ‘@jwt_required’ decorator to the already existing route. This will make sure that all the requests have Authorization header in the request object.

Finally, lets just add a callback function which will be called when there are no Authorization header found.

@jwt.unauthorized_loader
def unauthorized_response(callback):
return jsonify({
'ok': False,
'message': 'Missing Authorization Header'
}), 401

The whole ‘modules/app/controllers/user.py’ file will look something like this.

With all these modifications, we have added a few more dependencies to our backend server. Now the requirements.txt will will be something like this,

requests==2.18.1
Flask==0.12.2
pymongo==3.4.0
flask_pymongo==0.5.1
Flask_JWT_Extended==3.7.2
Flask_Bcrypt==0.7.1
jsonschema==2.5.1

To start the web server, run the docker compose command with build argument.

docker-compose up --build

Now, if everything is done properly, we should have our server up & running. We will now see how we can test our server using postman app.

Registering user:

Authenticating user:

Request with Authorization header:

I hope, you get the results as expected. Now, we are set to create a user, and put the authentication on every subsequent request. In next part, we will create routes for CRUD operation on tasks. We will also add jsonschema validation for the task object. Then, we will be ready to go ahead with the react front-end UI.

So, stay tuned for the next part. Till then, keep adding comments for any query or suggestions.

--

--

Riken Mehta

CS Graduate student, NYU. Image processing & Machine learning practitioner. Former Co-founder and algorithms lead at poshaQ — a fashion retail startup.