in

How to Secure a Flask REST API with JSON Web Token: An In-Depth Guide for API Developers

As an API developer, properly securing your REST API should be one of your top priorities. You want to make sure your API endpoints are locked down and only callable by authorized clients.

One of the most secure and flexible methods for protecting a Flask REST API is using JSON Web Tokens (JWT). JWTs are a great way to handle authentication and authorization without sessions.

In this comprehensive, 3500+ word guide, we‘ll explore:

  • The benefits of using JWTs for API authentication
  • How JWTs work under the hood
  • Step-by-step guidance on implementing JWT-based auth in Flask
  • Additional methods and best practices for hardening your API security

By the end, you‘ll have an in-depth understanding of how to properly secure your Flask REST APIs with JWT to prevent unauthorized access. Let‘s get started!

Why JWT? The Benefits for API Authentication

JWT (pronounced "jot") is an open standard (RFC 7519) that defines a compact way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

Here are some key advantages of using JWTs for API authentication:

Stateless – JWTs contain all the necessary user data in the token itself rather than accessing a session. This makes horizontal scaling easier since no session data needs to be shared across servers.

Mobile friendly – JWTs are standardized so they work seamlessly across different domains, protocols, and clients unlike cookies. This is essential for APIs used by mobile apps.

Compact – JWTs are encoded into a single string which is easy to store and transmit, especially in URLs, POST parameters, or HTTP headers.

Verified – JWTs can be cryptographically signed by the server so their integrity and authenticity can be verified.

Flexibility – Custom claims can be easily added to the JWT payload without changing the underlying implementations.

For REST APIs, JWT is the perfect authentication mechanism since it keeps things stateless while still verifying identities through signatures. The client handles the session by simply passing the JWT with each request.

Okay, enough background. Let‘s dig into how they actually work.

Understanding JWT Structure and Workflow

JWTs consist of three main components – a header, payload, and signature.

Header – Contains metadata like the algorithm used to sign the JWT.

Payload – Contains the claims which include entity information like user id, name, expiry etc.

Signature – Generated by encoding the header and claims with a secret key. Used to verify the message wasn‘t changed.

Here is an example JWT (the signature is just placeholder text):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XBP9Y4fAsoTWIZBDtgKe5A

Let‘s break down the workflow:

  1. User logs in with credentials and gets a JWT

  2. JWT is stored locally, often in localStorage but can be stored in a cookie as well

  3. Subsequent API requests retrieve JWT and attach it either in the header or request body

  4. Server verifies JWT signature against the secret key

  5. If valid, user is authenticated and appropriate data is returned

  6. When JWT expires, user logs in again to get a new token

Now that we understand JWTs, let‘s look at implementing them in a Flask app.

Setting Up Our Flask App

I‘m assuming you already have Python 3 and Flask installed. If not, refer to my Flask REST API Tutorial.

We‘ll use Flask-SQLAlchemy for our database models and PyJWT for JWT handling. So install those packages:

pip install flask-sqlalchemy PyJWT

Next, I created an app.py file and imported the necessary packages:

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy 
from werkzeug.security import generate_password_hash, check_password_hash
import jwt
import datetime
from functools import wraps

Then initialized Flask, configured SQLAlchemy, and defined a User model with columns for id, name, hashed password, etc:

app = Flask(__name__)

app.config[‘SECRET_KEY‘] = ‘super-secret-key‘ 

app.config[‘SQLALCHEMY_DATABASE_URI‘] = ‘sqlite:///app.db‘

db = SQLAlchemy(app)

class User(db.Model):
    ...

With that setup, let‘s move on to implementing JWT authentication.

Implementing JWT Authentication in Flask

We want to generate JWTs when users log in successfully. But first we need to allow user registration so there are credentials to authenticate!

1. Create User Registration Endpoint

The registration endpoint will accept new user data, hash the password, and insert the new user into the database.

@app.route(‘/register‘, methods =[‘POST‘])
def register():
    username = request.json[‘username‘]
    password = request.json[‘password‘] // plain text 

    hashed_pw = generate_password_hash(password, ‘sha256‘)

    new_user = User(username=username, password=hashed_pw)

    db.session.add(new_user)
    db.session.commit()

    return jsonify({ ‘message‘: ‘Registered successfully!‘ }) 

Note we shouldn‘t store the plaintext password – always hash it first! I used Flask‘s generate_password_hash which handles salting and hashing securely.

For testing, I registered a new user "John" with Postman by sending a POST to /register.

2. Create /login Endpoint to Return JWT

With registration complete, we can create the /login endpoint:

@app.route(‘/login‘, methods =[‘POST‘])  
def login():
    username = request.json[‘username‘]
    password = request.json[‘password‘]

    user = User.query.filter_by(username=username).first() 

    if not user or not check_password_hash(user.password, password): 
        return jsonify({‘message‘: ‘Invalid username or password‘}), 401

    # Create JWT payload   
    payload = {
        ‘sub‘: user.id,
        ‘exp‘: datetime.datetime.utcnow() + datetime.timedelta(minutes=60)
    }

    # Create JWT
    jwt_token = jwt.encode(payload, app.config[‘SECRET_KEY‘], ‘HS256‘).decode(‘utf-8‘)

    return jsonify({ ‘token‘: jwt_token })

This queries the user, verifies the password, creates a payload with their user id and expiration time, encodes it into a JWT, and returns the JWT string.

I tested by sending a POST to /login with basic auth headers which returned my JWT.

3. Create JWT Required Decorator

To protect routes, we need a way to check if the request contains a valid JWT. Let‘s create a decorator for this:

def jwt_required(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        token = request.headers[‘Authorization‘] # Header format: "Bearer <jwt_token>"

        try:
            jwt.decode(token, app.config[‘SECRET_KEY‘], ‘HS256‘)

        except:
            return jsonify({‘message‘: ‘Invalid token‘}), 401

        return f(*args, **kwargs)

    return wrapper

This verifies the JWT against our secret key. If invalid, it will return a 401 unauthorized.

We can easily protect any route using @jwt_required.

4. Protect API Routes

Now let‘s create a protected route by adding the @jwt_required decorator:

@app.route(‘/users‘, methods=[‘GET‘])
@jwt_required
def get_users():

    users = User.query.all()

    return jsonify(users)

Only requests with a valid JWT in the header will be able to access this route. Let‘s test it out!

First I made a request without a JWT and got the 401 error as expected:

{
    "msg": "Missing Authorization Header"
}

Then I took my JWT from the /login endpoint and attached it on a new GET request to /users:

GET /users
Authorization: Bearer <jwt_token> 

And I received a 200 OK with the list of users! The API validated my JWT and authorized my request.

This shows how JWT authentication can easily be implemented in Flask and used to protect API routes.

Hardening API Security

JWTs are a great first line of defense but there are additional steps we should take to further harden API security.

Use HTTPS

Always use HTTPS for your API, otherwise the traffic can be intercepted and tokens stolen. HTTPS provides transport layer security.

For local development, a self-signed certificate works, but for production you‘ll need a valid SSL certificate. Many cloud hosts like Heroku provide this automatically.

Refresh Tokens

Access tokens like JWTs eventually expire. So we need a way to get a new access token without re-authenticating.

This is where refresh tokens come in – issue a long-lived refresh token on login that can be used to acquire new access tokens as needed.

OAuth 2.0

For enterprise-grade API security, look into OAuth 2.0. This is an authorization framework that provides several authorization flows for web and mobile apps.

It supports access and refresh tokens as well as revocation so stolen tokens can be invalidated. Super important for public-facing APIs!

Rate Limiting

Implement rate limiting to prevent brute force attacks. Restrict the number of requests a client can make within a timeframe. This reduces the effectiveness of DDoS and hacking attempts.

Input Validation

Sanitize and validate all input on your API endpoints. Tools like marshmallow can help ensure your app only processes expected data types and formats. Prevent issues like SQL injection or cross-site scripting.

Automatic Blocking

Track failed login attempts from a source and automatically block requests after a threshold, such as 100 failed attempts in 10 minutes. This thwarts credential stuffing and brute force login attacks.

By combining JWT authentication with other security best practices like HTTPS, OAuth, rate limiting, and input validation, you can achieve robust API protection.

Conclusion

I hope this guide gave you a comprehensive understanding of properly implementing JSON Web Token authentication in Flask APIs.

Here are some key takeaways:

  • JWTs provide stateless user authentication using a verified token
  • Hash and salt passwords before storing them
  • Generate JWTs containing user identity and expiration time after login
  • Apply jwt_required decorator to protect API routes
  • Refresh tokens allow acquiring new access tokens without re-auth
  • Use HTTPS, OAuth, rate limiting, input validation to further harden API security

JWT + REST provides a lightweight yet secure way to handle auth for web and mobile APIs. By following authentication and authorization best practices, you can keep your API locked down and ready for production use cases.

What questions do you still have about securing your Flask REST APIs? Let me know in the comments! I‘m always happy to chat more about API development best practices.

AlexisKestler

Written by Alexis Kestler

A female web designer and programmer - Now is a 36-year IT professional with over 15 years of experience living in NorCal. I enjoy keeping my feet wet in the world of technology through reading, working, and researching topics that pique my interest.