Skip to content

Sec/refresh tokens - #2849

Open
nhoening wants to merge 5 commits into
mainfrom
sec/refresh-tokens
Open

nhoening wants to merge 5 commits into
mainfrom
sec/refresh-tokens

Conversation

@nhoening

@nhoening nhoening commented Nov 9, 2026

Copy link
Copy Markdown
Member

Description

Closes #2229

The

  • Clients can opt in to get refresh tokens with the eisting ndpoint for auth token access
  • Clients can rotate tokens
  • Clients can revoke a token family.
  • New fields use kebab-case; the existing auth_token, user_id, and access-token lifetime remain compatible.
  • Added changelog item in documentation/changelog.rst

Look & Feel

Check the new documentation and API endpoints.

How to test

Here is a bash script:

uv run --no-project --with requests python - <<'PY'
import getpass
import requests

base = "http://localhost:5000"
email = input("Email: ")
password = getpass.getpass("Password: ")

# Log in and opt in to refresh tokens.
response = requests.post(
    f"{base}/api/requestAuthToken",
    json={
        "email": email,
        "password": password,
        "include-refresh-token": True,
    },
    timeout=30,
)
response.raise_for_status()
first = response.json()
print("Login succeeded; access token lifetime:", first["expires-in"], "seconds")

# Exchange the refresh token.
response = requests.post(
    f"{base}/api/refreshAuthToken",
    json={"refresh-token": first["refresh-token"]},
    timeout=30,
)
response.raise_for_status()
second = response.json()
assert second["refresh-token"] != first["refresh-token"]
print("Refresh succeeded; a replacement refresh token was issued.")

# Use the new access token for an ordinary API request.
response = requests.get(
    f"{base}/api/v3_0/users/{second['user_id']}",
    headers={"Authorization": second["auth_token"]},
    timeout=30,
)
response.raise_for_status()
print("The new access token works.")

# Reuse of the old refresh token must fail and revoke the family.
response = requests.post(
    f"{base}/api/refreshAuthToken",
    json={"refresh-token": first["refresh-token"]},
    timeout=30,
)
assert response.status_code == 401, response.text
response = requests.post(
    f"{base}/api/refreshAuthToken",
    json={"refresh-token": second["refresh-token"]},
    timeout=30,
)
assert response.status_code == 401, response.text
print("Replay was rejected and the refresh-token family was revoked.")
PY

Future work

The client needs to support this, as well.

Context:
- Refresh-token rotation needs durable state and protection against concurrent use.

Change:
- Add the tracker model and migration, lock user and family rows consistently, and revoke families on credential changes or deactivation.
Context:
- Integrations need renewable credentials without changing existing access-token clients.

Change:
- Add JSON refresh and revocation endpoints with kebab-case fields, lifetime reporting, replay protection and immediate revocation.
- Preserve legacy response fields and token lifetime; reject inactive users and prevent caching token responses.
Context:
- Authentication lifecycle changes need regression coverage using real token validation.

Change:
- Cover rotation, concurrent reuse, expiry, revocation and credential changes.
- Correct naming checks for the existing auth_token and user_id response contract without allowing new snake-case fields.
Context:
- Issue #2229 adds renewable API credentials.

Change:
- Describe refresh-token rotation, expiry and revocation while retaining legacy access-token authentication.
Context:
- New clients need a complete flow and guidance for safe rotation and recovery.

Change:
- Document opt-in authentication, exchange, revocation, expiry and deployment requirements with curl and Python examples.
- Update generated OpenAPI definitions to match the actual authentication responses.
@nhoening nhoening self-assigned this Nov 9, 2026
@nhoening nhoening added this to the 1.2.0 milestone Nov 9, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refresh tokens

1 participant