Welcome back! In the previous lessons, you've learned how to set up a basic /login
endpoint, generate JSON Web Tokens (JWT
) upon successful login, and secure an endpoint using JWT
in your Flask application. These are essential steps in building a secure API.
In this lesson, we'll go a step further by discussing the importance of setting token expiration times and introducing the concepts of access and refresh tokens. Using these tokens effectively is crucial for maintaining the security and usability of your application.
Before diving into the new content, let's quickly recap our existing Flask and JWT
setup to ensure we're all on the same page. Here's the fundamental configuration for our Flask app, including the mock database, JWT
setup, and schema validation:
Python1from flask import Flask, request, jsonify 2from flask_jwt_extended import JWTManager 3from marshmallow import Schema, fields, ValidationError 4from marshmallow.validate import Length 5 6# Initialize a Flask app instance 7app = Flask(__name__) 8 9# Mock database of users 10database = [ 11 {"id": 1, "username": "cosmo", "password": "space-corgi"} 12] 13 14# Define a schema for validating login data 15class LoginSchema(Schema): 16 username = fields.Str(required=True, validate=Length(min=1)) 17 password = fields.Str(required=True, validate=Length(min=1)) 18 19# Set the secret key for signing JWTs 20app.config['JWT_SECRET_KEY'] = 'super-secret' 21 22# Initialize the JWTManager with the Flask app 23jwt = JWTManager(app)
This setup initializes a Flask application, configures the JWT
secret key, initializes the JWTManager
, and sets up a mock database and a login schema for validation.
In previous lessons, we used access tokens to secure our endpoints when users logged in. Now, we'll introduce another type of token: the refresh token.
/profile
, /dashboard
, or any other user-specific routes./refresh
, to obtain a new access token when the old one expires. Refresh tokens are not used directly to access resources but rather to acquire new access tokens.By using refresh tokens, we can make our application more secure and user-friendly. Instead of forcing users to log in frequently, we can allow them to stay logged in by obtaining new access tokens automatically.
Setting token expiration times is critical for security. Tokens that never expire can be a significant security risk. By configuring these times, you reduce the window for potential misuse.
The timedelta
class from Python's datetime
module allows us to specify these durations. It accepts parameters like seconds, minutes, hours, days and weeks.
Here are some examples of how to create different durations:
Python1from datetime import timedelta 2 3# Examples of durations 4seconds = timedelta(seconds=30) # 30 seconds 5minutes = timedelta(minutes=5) # 5 minutes 6hours = timedelta(hours=1) # 1 hour 7days = timedelta(days=1) # 1 day 8weeks = timedelta(weeks=1) # 1 week 9custom = timedelta(days=2, hours=3, minutes=15) # 2 days, 3 hours, and 15 minutes
Now that we understand how to specify durations with timedelta
, let's apply this to our token expiration configuration and ensure that tokens have a limited lifespan, enhancing security.
Here's how to configure the expiration times for access and refresh tokens:
Python1from datetime import timedelta 2 3# Set the expiry time for access tokens (15 minutes) 4app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=15) 5# Set the expiry time for refresh tokens (1 hour) 6app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(hours=1)
In this configuration:
By setting these expiration times, you enforce periodic re-authentication and token renewal, which helps maintain the security of your application.
With our expiration times now set, let's extend our existing /login
route to generate both tokens upon a successful login:
Python1from flask_jwt_extended import create_access_token, create_refresh_token 2 3# Existing login route to include token generation 4@app.route('/login', methods=['POST']) 5def login(): 6 # -- previous input validation and user verification code goes here -- 7 8 # Check if the user exists and if the password matches 9 if user and user["password"] == password: 10 # Create a JWT access token 11 access_token = create_access_token(identity=username) 12 # Create a JWT refresh token 13 refresh_token = create_refresh_token(identity=username) 14 15 # Return the tokens as a JSON response 16 return jsonify(access_token=access_token, refresh_token=refresh_token), 200 17 else: 18 # Return an error if the user does not exist or the password is incorrect 19 return jsonify(error="Bad username or password"), 401
In this route, upon successful login:
create_access_token(identity=username)
.create_refresh_token(identity=username)
.This setup ensures that the user receives both tokens needed for future access and token refresh actions.
Once the access token expires, users will need a way to get a new one without having to re-authenticate. This is where the refresh token comes into play.
Below is the implementation of a /refresh
route that uses the refresh token to provide a new access token:
Python1from flask_jwt_extended import jwt_required, get_jwt_identity 2 3# Define a refresh route that requires a valid refresh token to access 4@app.route('/refresh', methods=['POST']) 5@jwt_required(refresh=True) 6def refresh(): 7 # Get the identity of the current user from the JWT refresh token 8 current_user = get_jwt_identity() 9 # Create a new access token 10 new_access_token = create_access_token(identity=current_user) 11 # Return the new access token as a JSON response 12 return jsonify(access_token=new_access_token), 200
In this route:
@jwt_required(refresh=True)
decorator ensures that the request includes a valid refresh token.get_jwt_identity()
retrieves the identity (user) from the current refresh token.create_access_token(identity=current_user)
.This process allows users to stay authenticated without having to provide their credentials every time their access token expires.
When an access token expires, the server responds with a 401 Unauthorized
status and a message like:
JSON1{ 2 "msg": "Token has expired" 3}
This indicates that the token is invalid due to expiration. The client should use the refresh token to get a new access token or prompt the user to re-authenticate, ensuring ongoing security.
In this lesson, we covered:
Congratulations on reaching the final stage of our course! Your dedication has brought you to a point where you can confidently secure Flask applications using JWT authentication. Up next, you have a few final tasks to complete that will reinforce these concepts.
You're almost there, so keep going strong!