Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Flask›JWT Authentication with Flask

Auth and Security

JWT Authentication with Flask

JSON Web Tokens (JWTs) are a stateless method for verifying user identity by embedding claims within a digitally signed, URL-safe string. They are crucial for modern API architectures because they eliminate the need for server-side session storage, allowing services to remain highly scalable. You should reach for JWTs whenever you are building decoupled Flask APIs that need to authenticate users across multiple requests without querying a database for session validation every time.

Understanding the JWT Structure

A JWT is composed of three parts separated by dots: a header, a payload, and a signature. The header typically identifies the algorithm used, the payload contains the user claims (like their unique identifier and expiration time), and the signature ensures the token has not been tampered with. Because the server holds the secret key used to sign the token, it can verify the integrity of the data just by re-hashing the header and payload and comparing it against the provided signature. This stateless nature means your Flask application does not need to maintain a stateful session store, such as a database table or Redis cache, to identify which user is making a request. By embedding the user's identity directly into the token, you offload the burden of state management to the client, which is essential for performance and horizontal scaling in distributed systems.

import jwt
import datetime

# The secret key is used to sign the token; never hardcode this in production.
SECRET_KEY = 'super-secret-key-that-should-be-in-environment-variables'

def generate_token(user_id):
    # The payload contains claims about the user and token expiration.
    payload = {
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1),
        'iat': datetime.datetime.utcnow(),
        'sub': user_id
    }
    # Sign the token using the HMAC SHA256 algorithm.
    return jwt.encode(payload, SECRET_KEY, algorithm='HS256')

Implementing Protected Routes

To secure a route in Flask, you must intercept the incoming request and inspect the Authorization header for a valid token. The standard convention is to use the Bearer schema, where the client sends 'Authorization: Bearer <token>'. Once your application receives this request, it must extract the token string and attempt to decode it using the same secret key used for signing. If the token is expired, the signature is invalid, or the header is missing, the authentication check fails. Using a decorator is the most idiomatic way to implement this in Flask, as it keeps your route logic clean by separating the authentication mechanism from the business logic. By wrapping route functions with a custom decorator, you can ensure that only requests with a valid token have access to your sensitive endpoint data, effectively creating a perimeter of security around your API endpoints.

from functools import wraps
from flask import request, jsonify

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization', '').replace('Bearer ', '')
        if not token:
            return jsonify({'message': 'Token is missing'}), 401
        try:
            data = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        except jwt.ExpiredSignatureError:
            return jsonify({'message': 'Token has expired'}), 401
        except:
            return jsonify({'message': 'Invalid token'}), 401
        return f(*args, **kwargs)
    return decorated

User Login and Token Issuance

The token issuance process is where you establish identity. After a user provides valid credentials, such as a username and password, you verify them against your database. Upon successful validation, your Flask server generates a signed JWT that contains the user's unique identifier. This token acts as a temporary digital credential. The client is responsible for storing this token, usually in a local storage or secure cookie, and sending it back in the header of subsequent requests. Since the server does not 'remember' the user in a session object, it trusts the token implicitly once it verifies the cryptographic signature. This effectively hands the user a 'key' that proves who they are, provided they keep the key safe. By keeping the token lifespan short, you reduce the risk associated with stolen tokens, as they will naturally expire within a defined window, forcing the user to re-authenticate periodically.

from flask import Flask, request

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    auth = request.json
    # In a real app, verify credentials against a database hash.
    if auth.get('username') == 'admin' and auth.get('password') == 'secret':
        token = generate_token(user_id=1)
        return jsonify({'token': token})
    return jsonify({'message': 'Login failed'}), 401

Handling Expiration and Token Security

Security in a stateless environment relies heavily on the expiration claim within the JWT. If a token were never to expire, a compromised token would grant indefinite access to your application. By setting the 'exp' (expiration) field to a short duration, such as fifteen minutes or one hour, you limit the window of opportunity for an attacker to use a intercepted token. You must always handle the ExpiredSignatureError exception in your Flask application to explicitly inform the client that they need to re-authenticate. Never store sensitive information inside the JWT payload, as the token is merely base64 encoded and can be read by anyone who intercepts it. The security of the token comes from the signature, not from hiding the contents. By treating tokens as short-lived permissions rather than long-term credentials, you maintain a robust security posture while ensuring that users are periodically re-validated.

from jwt import decode

# Example of specific error handling during decoding
def verify_token(token):
    try:
        # Decode will raise ExpiredSignatureError if the exp claim is in the past
        return decode(token, SECRET_KEY, algorithms=['HS256'])
    except jwt.ExpiredSignatureError:
        return None # Prompt client to re-login
    except jwt.InvalidTokenError:
        return None # Token is malformed or signature mismatch

Applying Security Best Practices

To ensure your Flask API remains secure, always use the 'HS256' algorithm with a strong, complex secret key. If your infrastructure allows, consider asymmetric signing (using 'RS256') where you sign tokens with a private key and verify them with a public key; this is even more secure as the server verifying the token does not need the secret used to create it. Furthermore, you must always serve your API over HTTPS, as this prevents tokens from being intercepted via man-in-the-middle attacks. If a user logs out, you cannot technically revoke the token in a truly stateless system because the server does not track them. In high-security scenarios, you might implement a blacklist of revoked tokens in a fast memory store like Redis to handle logout scenarios effectively. Combining short-lived tokens with secure transmission protocols provides the strongest foundation for your Flask authentication system, protecting both the server's integrity and the user's data.

from flask import jsonify

@app.route('/dashboard')
@token_required
def dashboard():
    # Access the user data decoded from the token if needed
    return jsonify({'data': 'Welcome to the protected dashboard!'})

if __name__ == '__main__':
    # Always run behind a secure proxy with HTTPS in production
    app.run(ssl_context='adhoc')

Key points

  • JWTs allow for stateless authentication, removing the need for server-side sessions.
  • The token consists of a header, payload, and signature to ensure data integrity.
  • The secret key must be kept extremely secure and never hardcoded in your source code.
  • Flask decorators provide an elegant way to enforce authentication across protected routes.
  • Short expiration times mitigate the risks associated with stolen or compromised tokens.
  • Tokens should always be transmitted over HTTPS to prevent interception by third parties.
  • JWTs are encoded, not encrypted, so you must never store sensitive data like passwords inside them.
  • Logouts in stateless systems are typically managed by blacklisting tokens in a cache store.

Common mistakes

  • Mistake: Storing the secret key in plain text in the source code. Why it's wrong: Committing the secret key to version control allows anyone with repository access to forge valid tokens. Fix: Use environment variables or a secure secret management service to inject the key at runtime.
  • Mistake: Failing to set an expiration time (exp claim) on the JWT. Why it's wrong: Without an expiration, a leaked token provides permanent access to a user's account with no way to revoke it. Fix: Always set an 'exp' claim with a reasonable duration.
  • Mistake: Validating the JWT signature but ignoring the claims inside. Why it's wrong: A malicious actor could provide a validly signed but improperly scoped token if you don't verify the 'sub', 'aud', or 'iss' claims. Fix: Explicitly verify all required claims after the signature is checked.
  • Mistake: Using HTTP instead of HTTPS for token transmission. Why it's wrong: JWTs are passed in headers; without encryption, they are easily intercepted via man-in-the-middle attacks. Fix: Enforce TLS/SSL for all endpoints that handle authentication tokens.
  • Mistake: Including sensitive user data like passwords or PII in the token payload. Why it's wrong: JWTs are base64-encoded, not encrypted, meaning anyone who captures the token can easily decode its contents. Fix: Only store a unique user identifier (like an ID) in the payload.

Interview questions

What is a JSON Web Token (JWT) in the context of a Flask application, and why is it used?

A JSON Web Token is a compact, URL-safe means of representing claims to be transferred between a Flask client and server. In Flask, it is used for stateless authentication. Instead of storing session data in a server-side database or cache, the server signs a token containing user information. The client sends this token in the Authorization header for subsequent requests, allowing the Flask application to verify the user's identity without performing a database lookup on every request, which improves scalability.

How do you typically protect a route in Flask so that only authenticated users can access it?

To protect a route in Flask, you typically create a custom decorator function that intercepts the request before it reaches your view logic. Inside this decorator, you extract the 'Authorization' header and split it to get the token part. You then use a library like PyJWT to decode and verify the token signature using your application's secret key. If the token is valid, you proceed to the view; otherwise, you return a 401 Unauthorized response. Example: @token_required followed by your route function.

What is the role of the 'Secret Key' in Flask JWT authentication, and how should it be handled?

The secret key is the most critical piece of data in JWT authentication because it is used to sign the tokens. When Flask issues a token, it creates a digital signature based on the payload and this secret key. If an attacker gains access to your secret key, they can forge their own tokens and impersonate any user in your application. Therefore, you must store it in an environment variable, never hard-code it in your source files, and ensure it remains complex and private.

Compare the approach of using 'Stateless JWTs' versus 'Server-Side Sessions' in Flask.

Using stateless JWTs is advantageous in Flask when you are building a distributed system or a mobile API, as the server does not need to maintain any state about the user's login session; the client holds all necessary information. Conversely, server-side sessions keep the state on the server, allowing for easier invalidation or 'logout' functionality because you can simply delete the session record. JWTs are harder to revoke before expiration unless you implement a complex blocklist strategy, making sessions easier to manage but less performant at scale.

How can you implement a token revocation or 'logout' mechanism if JWTs are inherently stateless?

Since JWTs are stateless and the server doesn't keep track of issued tokens, revoking them is non-trivial. The standard approach in Flask is to implement a 'blocklist' using a fast key-value store like Redis. When a user logs out, you store the token's unique 'jti' (JWT ID) claim in Redis with an expiration time equal to the token's remaining lifespan. In your authentication decorator, you must query Redis to check if the incoming token's jti is in this blocklist before granting access to the route.

What are the security risks of storing JWTs in browser local storage compared to HttpOnly cookies?

Storing JWTs in local storage makes them vulnerable to Cross-Site Scripting (XSS) attacks, because any JavaScript running on your Flask app's domain can easily read the token. If an attacker injects a malicious script, they can steal the token and impersonate the user. A more secure approach is using HttpOnly cookies. Because these cookies are inaccessible to JavaScript, an XSS attack cannot read them, significantly reducing the surface area for token theft, although it requires careful configuration to prevent Cross-Site Request Forgery (CSRF) via SameSite cookie attributes.

All Flask interview questions →

Check yourself

1. When implementing JWT authentication in a Flask application, why is it recommended to use a decorator to protect routes?

  • A.To automatically encrypt the database password within the token
  • B.To centralize the token verification logic and keep route functions clean
  • C.To automatically convert the JWT into a standard Flask session cookie
  • D.To bypass the need for an authorization header in the client request
Show answer

B. To centralize the token verification logic and keep route functions clean
Decorators allow you to wrap functions with logic that checks for a valid token before the request handler runs. This prevents code duplication. Option 0 is wrong because passwords should never be in a JWT. Option 2 is wrong because JWTs are stateless and not sessions. Option 3 is wrong because the header is required.

2. What is the primary architectural benefit of using JWTs over Flask's default session management?

  • A.JWTs are automatically stored in the client's browser local storage by Flask
  • B.JWTs enable a stateless architecture by not requiring server-side session storage
  • C.JWTs are faster to generate than random session strings
  • D.JWTs provide built-in protection against cross-site request forgery (CSRF)
Show answer

B. JWTs enable a stateless architecture by not requiring server-side session storage
JWTs are self-contained, meaning the server doesn't need to look up a session ID in a database or cache, making it stateless. Option 0 is false as Flask doesn't manage local storage. Option 2 is irrelevant to architectural goals. Option 3 is false as CSRF is still a risk if tokens are stored in cookies.

3. If a user changes their password in your Flask application, why might a JWT remain valid until its original expiration date?

  • A.Because the JWT signature is tied to the previous password hash
  • B.Because the server has no built-in mechanism to invalidate individual stateless tokens
  • C.Because Flask automatically synchronizes local database changes with active JWTs
  • D.Because the token is signed with a public key that cannot be changed
Show answer

B. Because the server has no built-in mechanism to invalidate individual stateless tokens
Since JWTs are stateless, the server doesn't track active tokens, so it cannot proactively kill a specific token. Option 0 is wrong because the secret key, not the user password, signs the token. Option 2 is false as no such sync exists. Option 4 is false as rotation strategies exist but are not 'built-in' to Flask.

4. How should a Flask application handle a request that is missing the Authorization header when using JWTs?

  • A.It should return a 401 Unauthorized status code
  • B.It should automatically redirect the user to the login page
  • C.It should log the error and return a 404 Not Found error
  • D.It should allow the request but limit access to public routes only
Show answer

A. It should return a 401 Unauthorized status code
A 401 status indicates the request lacks valid authentication credentials. Option 1 is incorrect because APIs shouldn't perform browser redirects. Option 2 is a poor security practice as it masks the authentication error. Option 3 is incorrect as the route likely exists, only the credentials are missing.

5. When a Flask route validates a JWT, what does the 'secret key' represent in the signature verification process?

  • A.The public key that is shared with the client for verification
  • B.The server-side key used to create and verify the signature hash
  • C.The user's unique password hash used to verify identity
  • D.A randomly generated salt that is appended to the payload
Show answer

B. The server-side key used to create and verify the signature hash
The secret key is used to sign the token when created and verify it upon receipt to ensure it wasn't tampered with. Option 0 is wrong for HMAC (symmetric) signatures. Option 2 is wrong as user passwords shouldn't be involved. Option 3 is wrong as a salt isn't the primary mechanism for signature verification.

Take the full Flask quiz →

← PreviousFlask-Login — Session-based AuthenticationNext →Protecting Routes — login_required

Flask

23 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app