ProvenPin Install

Flask 3 + Flask-JWT-Extended access and refresh tokens: working versions (tested 2026-10-15)

Login, protected routes, refresh tokens and custom claims in Flask 3 using Flask-JWT-Extended, with an app factory.

passed3 passed · 0 failed2026-10-15 13:15 UTC
Status
passed
Last passed
2026-10-15 13:15 UTC
Tests
3 / 3
Language
Python
Runtime
Python 3.13.16
pinned versions3
flask@3.1.3flask-jwt-extended@4.7.4pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38740169402

The code

Exactly the files that passed. Your agent can pull them with get_recipe("flask-jwt-extended-auth") (install), or see them on GitHub.

requirements.txt
flask==3.1.3
flask-jwt-extended==4.7.4
pytest==9.1.1
app.py
from flask import Flask, jsonify, request
from flask_jwt_extended import (JWTManager, create_access_token, create_refresh_token,
                                get_jwt, get_jwt_identity, jwt_required)

USERS = {"ada": {"password": "lovelace", "role": "admin"}}  # swap for a real store

def create_app(secret="change-me"):
    app = Flask(__name__)
    app.config["JWT_SECRET_KEY"] = secret
    JWTManager(app)

    @app.post("/login")
    def login():
        body = request.get_json(silent=True) or {}
        user = USERS.get(body.get("username"))
        if not user or user["password"] != body.get("password"):
            return jsonify(error="invalid_credentials"), 401
        claims = {"role": user["role"]}
        return jsonify(access=create_access_token(identity=body["username"], additional_claims=claims),
                       refresh=create_refresh_token(identity=body["username"], additional_claims=claims))

    @app.post("/refresh")
    @jwt_required(refresh=True)
    def refresh():
        return jsonify(access=create_access_token(identity=get_jwt_identity(), additional_claims={"role": get_jwt()["role"]}))

    @app.get("/admin")
    @jwt_required()
    def admin():
        if get_jwt().get("role") != "admin":
            return jsonify(error="forbidden"), 403
        return jsonify(user=get_jwt_identity())

    return app
tests/test_auth.py
from app import create_app

def client():
    return create_app("a-long-enough-test-secret-key-123").test_client()

def test_login_and_access():
    c = client()
    tokens = c.post("/login", json={"username": "ada", "password": "lovelace"}).get_json()
    r = c.get("/admin", headers={"Authorization": f"Bearer {tokens['access']}"})
    assert r.status_code == 200 and r.get_json() == {"user": "ada"}

def test_refresh_flow():
    c = client()
    tokens = c.post("/login", json={"username": "ada", "password": "lovelace"}).get_json()
    new = c.post("/refresh", headers={"Authorization": f"Bearer {tokens['refresh']}"}).get_json()["access"]
    assert c.get("/admin", headers={"Authorization": f"Bearer {new}"}).status_code == 200
    # an access token cannot be used to refresh
    assert c.post("/refresh", headers={"Authorization": f"Bearer {new}"}).status_code == 422

def test_rejections():
    c = client()
    assert c.post("/login", json={"username": "ada", "password": "x"}).status_code == 401
    assert c.get("/admin").status_code == 401
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.