Introduction
Security is one of the most important aspects of any organization. API authentication is one way in which APIs are secured, making sure that only authorized users access sensitive data from protected servers.
JSON Web Token (JWT) is a common method of user authentication and authorization standard used to exchange data securely. Made of three components, a header, a payload, and a signature, here are the steps involved in the authorization process:
- Authentication. The client sends the user credentials to the server, and the server authenticates the client and generates JWT containing the users' information
- Issuing the token. The JWT created by the server is sent to the client which is stored for future use.
- Sending the token. When the client wants to access a protected resource it sends the JWT in the authorization header of the http request.
- Verifying the token. The server receives the request and uses the secret key that was used to sign in to verify the JWT. If the JWT is valid, the server extracts the information contained in it and determines which action is the client authorized to perform.
- Authorizing the request. If the user is authorized, the server responds with the data requested else it returns an error message.
In this article, we are going to discuss how to authenticate a Flask API step by step.
Steps
Step 1: Installing Flask and JWT
First, let's create a folder in the VS Code code editor as follows:
mkdir flask-auth
Now, let's create a virtual environment and activate it inside this folder as follows:
python3 -m venv env
source env/bin/activate
Next, let's install Flask and JWT using the following commands:
pip install flask
pip install PyJWT
After installing Flask and JWT, let's make a file called app.py using the following command:
touch app.py
Now, let's create a folder that hold our login.html
and name it templates as follows:
mkdir templates
Inside the templates folder create a file called login.html
as follows:
touch login.html
After completing all these steps, your project folder should look like this:
Step 2: Importing necessary libraries
Inside the app.py
file we had created in the previous step write the following code:
import jwt
from flask import Flask, request, jsonify, make_response, render_template, session, flash
from datetime import datetime, timedelta
from functools import wraps
Let's discuss the imports above;
-
jwt
: It imports PYjwt which is used to encode and decode JSON Web Tokens. -
Flask
: This is the core of the Flask web framework. It is used to create the instance for the web application. -
request
: Used to create HTTPS requests. -
jsonify
: It is used to convert Python dictionaries into JSON responses. -
make_response
: It is used to create custom HTTP responses. -
render_template:
It is used to render HTML templates using jinja2, which is the templating engine that flask uses. -
session:
It allows you to store and access variables of the current user between requests. -
flash:
It is used to display an error or success message to the user. -
datetime:
It is a class that represents date and time. -
timedelta:
Represents the difference between two dates and times. It is mainly used to set the expiration time of Json Web Tokens. -
wraps:
This is used to decorate functions, preserving their metadata (like name, docstring, etc.) when they are wrapped by another function. It’s often used in scenarios like creating custom decorators.
Step 3: Creating Flask instance
Before creating the Flask instance follow these steps to create a secret key.
- In the terminal, run the following commands:
python3
then:
import uuid
then:
uuid.uuid4().hex
Now copy the secret key and paste it into your app instance. Replace secret
with your secret key:
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
Step 4: Define the public Route
Let's define a public route that can be accessed by anyone without authorization using the following code:
@app.route('/public')
def public():
return 'This is a public page'
Run the application and open the following URL in the browser:
http://127.0.0.1:5000/public
It should look like this in your browser;
Step 5: Define an auth route
Let's define an auth route that can only be accessed by users who are registered on the app. We will also use the token_required
decorator to make sure that users with token access the route:
@app.route('/auth')
@token_required
def auth():
return 'You are verified. Welcome to your dashboard!'
Now let's define the token_required
function which will be used for authorized routes.
def token_required(func):
@wraps(func)
def decorated(*args, **kwargs):
token = request.args.get('token')
if not token:
return jsonify({'Alert!': 'Token is missing'}), 401
try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
except jwt.ExpiredSignatureError:
return jsonify({'Alert!': 'Token has expired'}), 401
except jwt.InvalidTokenError:
return jsonify({'Alert!': 'Invalid Token'}), 401
return func(*args, **kwargs) # Correctly return the wrapped function
return decorated
Let's breakdown the code above;
The @token_required
decorator is applied to the function to make sure that only requests with a valid JWT token can access the route.
In line five we are retrieving the token from the request’s query parameters using request.args.get('token').
If the token is missing the condition becomes true hence, the 401 error is returned.
Inside line seven the code tries to decode the provided token using jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']).
If the token has expired, the code catches the ExpiredSignatureError and returns the 401 unauthorized error.
Line 11 checks if the token is invalid and if the condition is true, it returns the 401 unauthorized error.
Let's run the app on the terminal and enter the following URL in the terminal:
http://127.0.0.1:5000/auth
The output should look like the one below:
We are getting the expected output that Token is missing
because we are not yet logged in. Let's move to the next step and see how we can get the token.
Step 6: Define the login method
We need to define a login route that we will use to log in to the app and access the token. We will pass the username and the password and if they match the ones in the route the login session variable will be set to True
. Here is the code for the login route:
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
if username and password == '123456': # Fix incorrect comparison
session['logged_in'] = True
token = jwt.encode({
'user': username,
'exp': datetime.utcnow() + timedelta(seconds=150) # Use 'exp' for expiry
}, app.config['SECRET_KEY'], algorithm='HS256')
return jsonify({'token': token}) # No need for `.decode('utf-8')`
else:
return make_response('Unable to verify', 403, {
'WWW-Authenticate': 'Basic realm="Authentication Failed!"'
})
Let's go ahead and breakdown the code above;
The condition from line sis checks if the username and the password are correct. If the two are correct, the code creates a JWT token and allows the user to log in. If the username and the password are not correct, the code from line fourteen is executed where the system returns a 403 Forbidden error, saying "Unable to verify".
Now let's go ahead and define a login.html template that will display the login form. In the login.html folder that we had created earlier, add this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<form action="/login" method ='POST'>
<input type="username" name="username" placeholder="username"><br><br>
<input type="password" name="password" placeholder="password"><br><br>
<input type="submit" value="Login">
</form>
</body>
</html>
Run the app once again and input any username and password as 123456
:
http://127.0.0.1:5000
The output after inputting the correct password should look like this:
After logging in we get the token as shown above.
Let's use the token to access the data in the private route. Open JWT.io and paste the token as shown below:
Now we can access the data as shown above.
Final Thoughts
In this article, we have discussed the importance of API security in your application and how JWT authentication works. We also developed different routes of a Flask API and also learned how to come up with a secret key.
Finally, we used a JWT token to access data on the JWT.io
website. In conclusion, I recommend checking out my GitHub link here for the code.
Top comments (0)