flask-session.manager.sk
Flask companion package for react-session.manager.sk.
Cookie-driven JWT session management for Flask backends, designed to pair with the React session manager's HttpOnly-cookie transport.
Install
# uv (recommended)
uv add flask-session.manager.sk
# pip
pip install flask-session.manager.sk
Quick Start
from flask import Flask, jsonify
from flask_jwt_extended import create_access_token
from flask_session_manager_sk import (
SessionManager,
SessionManagerCallbacks,
verify_session_token_record,
)
app = Flask(__name__)
app.config.update(
{
"SECRET_KEY": "your-secret-key",
"JWT_TOKEN_LOCATION": ["cookies"],
"JWT_COOKIE_SECURE": True, # require HTTPS in production
"JWT_COOKIE_CSRF_PROTECT": True,
"JWT_ACCESS_COOKIE_NAME": "access_token_cookie",
"FRONTEND_URL": "https://myapp.example.com",
"CORS_ORIGINS": ["https://myapp.example.com"],
}
)
manager = SessionManager()
def verify_user_token(user, agent, device_uid, token):
record = user.find_session_record(agent=agent, device_uid=device_uid)
if verify_session_token_record(token, record):
return record
return None
callbacks = SessionManagerCallbacks(
user_lookup=lambda identity: get_user_by_id(identity),
refresh_user_token=lambda user, agent, device_uid: create_and_store_token(
user, agent, device_uid
),
verify_user_token=verify_user_token,
is_user_active=lambda user: user.is_active,
is_session_persistent=lambda user, agent, device_uid, record=None: bool(
record and record.persistent
),
)
manager.init_app(app, callbacks=callbacks)
@app.route("/auth/who")
@jwt_required(optional=True)
def whoami():
from flask_jwt_extended import current_user
if current_user:
return jsonify(logged_in=True, user_id=current_user.id)
return jsonify(logged_in=False)
SessionManager.init_app() automatically registers the package CSRF/origin guard
for cookie authentication. Browser unsafe requests using cookies must come from a
configured FRONTEND_URL or CORS_ORIGINS origin unless you deliberately opt out
with FSM_CSRF_ORIGIN_CHECK=False.
Public API
Only these names are part of the stable surface:
| Name | Description |
|---|---|
SessionManager |
Flask extension that wires JWT callbacks |
SessionManagerCallbacks |
Frozen dataclass of application hooks |
token_response |
Create a JSON response with an HttpOnly JWT cookie |
clear_token_response |
Create a response that clears JWT cookies |
verify_session_token_record |
Verify a presented JWT against a server-side token record |
Everything else in the package is internal and may change without notice.
import flask_session_manager_sk
print(flask_session_manager_sk.__version__) # e.g. "1.0.0"
SessionManagerCallbacks
@dataclass(frozen=True)
class SessionManagerCallbacks:
# Required
user_lookup: Callable[[str], Any | None]
refresh_user_token: Callable[[Any, str, str | None], str | None]
# Optional
verify_user_token: (
Callable[[Any, str | None, str | None, str | None], Any | None] | None
) = None
is_user_active: Callable[[Any], bool] | None = None
is_session_persistent: (
Callable[[Any, str | None, str | None, Any | None], bool] | None
) = None
verify_user_token receives (user, agent, device_uid, token). Agent and device
UID are identifiers/signals only. They are not secrets and must never be enough
to authenticate a session by themselves. Resolve the relevant server-side session
record with metadata, then compare the presented JWT against the record's stored
hash with verify_session_token_record() or equivalent constant-time logic.
is_session_persistent receives (user, agent, device_uid, token_record) during
expired-token refresh. Return True only when the remembered-session policy is
still valid for that server-side record. This lets the package reissue persistent
cookies without owning your database schema or lifetime policy.
Flask Configuration Reference
These values are read at runtime via current_app.config. No configuration is
stored in the package.
| Key | Required | Default | Description |
|---|---|---|---|
SECRET_KEY |
Yes | - | Flask secret, must be >=32 bytes for HMAC-SHA256 |
JWT_TOKEN_LOCATION |
Yes | - | Include "cookies" for browser cookie auth; bearer-only clients can use "headers" |
JWT_ACCESS_COOKIE_NAME |
Yes | - | Cookie name; react-session.manager.sk expects "access_token_cookie" |
JWT_COOKIE_CSRF_PROTECT |
Yes | - | Must be True for recommended browser cookie auth. May be set to False when FSM_CSRF_ORIGIN_CHECK=True as an explicit reduced-defense fallback. |
JWT_COOKIE_SECURE |
Yes | - | True in production and always required when JWT_COOKIE_SAMESITE="None" |
JWT_COOKIE_SAMESITE |
No | - | "Lax" or "Strict"; use "None" only with Secure=True for cross-site cookies |
FSM_CSRF_ORIGIN_CHECK |
No | True |
Automatically reject unsafe cookie-auth requests from unconfigured or missing origins. Set False only for deliberate, security-reviewed opt-out. |
FSM_PERSISTENT_MAX_AGE |
For persistent=True |
- | timedelta or int seconds for remembered-session cookie Max-Age |
FSM_COOKIE_PARTITIONED |
No | False |
Append ; Partitioned (CHIPS) to all auth cookies. Requires JWT_COOKIE_SAMESITE="None" and JWT_COOKIE_SECURE=True. Helps cross-site cookie auth on supported iOS WebKit/Safari ITP and other third-party-cookie blockers. |
FRONTEND_URL |
For cookie origin checks | - | Canonical URL of the SPA |
CORS_ORIGINS |
For cookie origin checks | - | Allowed browser origins as a string or list |
When cookie auth is enabled, initialization validates the security-sensitive
combinations above and raises RuntimeError with an actionable message if the
configuration is unsafe or missing required browser origins. Cookie auth
requires at least one of JWT_COOKIE_CSRF_PROTECT=True or
FSM_CSRF_ORIGIN_CHECK=True; disabling both is rejected. Bearer-only
configurations are not blocked by these cookie checks.
CSRF and Origin Policy
Flask-JWT-Extended's double-submit CSRF token and this package's origin check are complementary defenses:
- safe methods (
GET,HEAD,OPTIONS) are not blocked by the package origin guard - unsafe methods (
POST,PUT,PATCH,DELETE) using cookie auth require an allowedOriginorReferer - if both
OriginandRefererare missing on an unsafe cookie-auth request, the guard fails closed with403 - bearer-authenticated requests skip the browser cookie origin guard
- if both a cookie and a bearer authorization header are present, bearer mode wins for the package origin guard
Same-site readable-cookie CSRF
When the SPA and API share a registrable domain (e.g. app.example.com and
api.example.com), the browser can read the csrf_access_token cookie that
token_response() sets. Configure the frontend Axios instance with:
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
axios.defaults.xsrfCookieName = "csrf_access_token";
axios.defaults.xsrfHeaderName = "X-CSRF-TOKEN";
Axios then reads the cookie and sends it as X-CSRF-TOKEN automatically.
Genuinely cross-site SPA/API CSRF
When the SPA and API live on different registrable domains (e.g.
https://portal.example.com and https://api.herokuapp.com), the browser cannot
read the API-scoped csrf_access_token cookie from the SPA's origin. In this
case token_response() and the expired-token refresh path also return the
current CSRF value in the response header:
X-CSRF-TOKEN: <current csrf claim>
Access-Control-Expose-Headers: X-CSRF-TOKEN
The access JWT itself remains HttpOnly and is never exposed to JavaScript.
Required CORS configuration for cross-site deployments:
CORS(
app,
supports_credentials=True,
origins=app.config["CORS_ORIGINS"], # must not be "*" with credentials
allow_headers=["Content-Type", "appVersion", "deviceUID", "X-CSRF-TOKEN"],
expose_headers=["X-CSRF-TOKEN"],
)
The frontend companion react-session.manager.sk (v4.2+) captures this header
in memory and attaches it to unsafe requests automatically. Do not store the
CSRF value in localStorage or sessionStorage.
Required request header
Unsafe cookie-auth requests must include:
X-CSRF-TOKEN: <current csrf claim>
token_response() emits both the readable csrf_access_token cookie (for
same-site deployments) and the X-CSRF-TOKEN response header (for cross-site
deployments), so both topologies work without backend changes.
Origin-only fallback (reduced defense)
The recommended configuration enables both protections:
JWT_COOKIE_CSRF_PROTECT = True
FSM_CSRF_ORIGIN_CHECK = True
The header transport above is the preferred cross-site solution and is now
implemented by this package (v1.3+) paired with
react-session.manager.sk v4.2+. For consumers that cannot yet adopt it, an
explicit origin-only fallback remains supported:
JWT_COOKIE_CSRF_PROTECT = False
FSM_CSRF_ORIGIN_CHECK = True
This disables Flask-JWT-Extended's double-submit CSRF check while keeping the
strict Origin/Referer guard active for unsafe cookie-authenticated requests.
It is a reduced-defense fallback, not a permanent configuration.
The package fails closed when both protections are disabled together
(JWT_COOKIE_CSRF_PROTECT=False + FSM_CSRF_ORIGIN_CHECK=False) for cookie
authentication.
Session Lifetime and Remember Me
JWT expiry, browser cookie lifetime, and remembered-session lifetime are separate concepts:
JWT_ACCESS_TOKEN_EXPIREScontrols how long an individual JWT is valid.- Session cookies (
persistent=False) are browser-session cookies and do not receiveMax-Age. - Remembered cookies (
persistent=True) receiveMax-AgefromFSM_PERSISTENT_MAX_AGEand can survive browser restarts. - Expired cookie JWTs are refreshed through replacement HttpOnly cookies. The
refresh response is signal-only (
{"refreshed": true}) and does not expose a new raw access token to JavaScript. - The refresh response also emits a new
X-CSRF-TOKENheader and exposes it, so cross-site SPAs can update their in-memory CSRF value. is_session_persistent(...)decides whether refresh should preserve persistent cookie attributes for a remembered session.- Absolute versus sliding remembered-session lifetime is application policy. Store
fields such as
created_at,expires_at,last_seen, orrevoked_aton your own session record and returnFalsefrom callbacks when the remembered session is no longer valid. - Logout, revocation, inactive users, and failed token-hash verification override persistence and must reject refresh.
Token Verification and Revocation
Use token hashes as the proof of possession. Device UID, User-Agent, IP address, and similar metadata are useful for finding a candidate session record, but they are not authentication secrets.
Recommended consumer pattern:
def verify_user_token(user, agent, device_uid, token):
record = user.find_session_record(agent=agent, device_uid=device_uid)
if verify_session_token_record(token, record):
return record
return None
verify_token_hash() uses a constant-time digest comparison and fails closed for
missing values. clear_session_token_value() preserves the registered-device row
while clearing active token state (token_hash = None, empty hint, no plaintext
token). Legacy rows containing the old SHA-256 hash of an empty string are treated
as revoked and never verify.
Cross-Site Cookies and iOS WebKit (CHIPS)
Browser auth uses an HttpOnly cookie. When the SPA and the API live on different
registrable domains (e.g. app.example.com to api.herokuapp.com), the cookie
is a third-party cookie. Desktop Chromium accepts SameSite=None; Secure
third-party cookies, but iOS WebKit (Safari and every iOS browser, including
Brave, which is WebKit under the hood) blocks them by default via Intelligent
Tracking Prevention. Result: login succeeds, the browser never stores the cookie,
and the next request 401s.
Set FSM_COOKIE_PARTITIONED=True to emit ; Partitioned (CHIPS) on all auth
cookies. Partitioned cookies are stored in a per-top-level-site partition, so
WebKit keeps them even with third-party cookies blocked. Requirements:
JWT_COOKIE_SAMESITE="None"andJWT_COOKIE_SECURE=True(enforced; a misconfigured combination raisesRuntimeErrorat cookie-set time).- Browser support: iOS 16.4+ / Safari 16.4+, Chrome 114+. Older iOS remains affected; the durable fix is serving the API from the same site as the SPA.
React Companion Contract
The frontend companion react-session.manager.sk (v4.2+) uses this transport contract:
- Axios configured with
withCredentials: true,withXSRFToken: true - Cookies expected at default Flask-JWT-Extended names:
access_token_cookie(JWT)csrf_access_token(CSRF double-submit)
- CSRF header sent as
X-CSRF-TOKEN deviceUIDheader sent on every requestappVersionheader sent on every request- No
Authorizationbearer header for browser requests - For cross-site deployments, the CSRF value is captured from the
X-CSRF-TOKENresponse header (in memory only) and attached to unsafe requests automatically - Legacy
localStorage/sessionStoragebearer tokens are automatically cleared
Internal Modules
Not part of the stable API. Import at your own risk.
| Module | Content |
|---|---|
flask_session_manager_sk.cookies |
CSRF origin checks, cookie auth detection, clear_session_token_value |
flask_session_manager_sk.request |
get_agent, get_ip, get_token, get_dets_from_request |
flask_session_manager_sk.tokens |
create_token_hash, token_hint, verify_token_hash, verify_session_token_record, update_token_record |
flask_session_manager_sk.extension |
SessionManager implementation details |
Migrating from Bearer Tokens
If your backend currently returns access tokens as JSON payloads (e.g.
{"access_token": "..."}) and stores them in localStorage:
- Package adoption: Wire
SessionManagerwith your user-lookup, token-refresh, token-verification, and optional persistence callbacks. - Login endpoint: Call
token_response(payload, status, access_token)instead ofjsonify(payload). This sets the HttpOnly cookie automatically and also emits theX-CSRF-TOKENheader for cross-site SPAs. - CSRF/origin checks: Remove custom boilerplate that manually registers
reject_cookie_csrf()as abefore_requesthook.SessionManager.init_app()registers it automatically for cookie auth by default. - Token verification: Make sure your
verify_user_tokencallback compares the presented JWT against the stored token hash. Matching device metadata alone is insecure. - Frontend: Upgrade
react-session.manager.skto v4.2+. It removes bearer-token browser storage, keeps same-site XSRF support, and adds cross-site CSRF header capture for SPAs on a different registrable domain. - Backwards compatibility: Non-browser clients (API scripts, scheduled
tasks) can still send valid bearer authorization when
JWT_TOKEN_LOCATIONincludes"headers". The package origin guard skips bearer-authenticated requests. Expired bearer tokens are not auto-refreshed by this package because refreshed JWTs are only issued through HttpOnly cookies; bearer clients should use the application's explicit token-renewal flow.
Development
git clone https://github.com/Skulldorom/flask-session.manager.sk.git
cd flask-session.manager.sk
# Install deps + editable package
uv sync --dev
# Run checks
uv run ruff check .
uv run ruff format --check .
uv run pytest -v
# Build
uv build
License
MIT - see LICENSE.
Release files for flask-session.manager.sk 1.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flask_session_manager_sk-1.3.1.tar.gz | 38.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flask_session_manager_sk-1.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.9 kB
Release files / flask_session_manager_sk-1.3.1.tar.gz
| Download URL | flask_session_manager_sk-1.3.1.tar.gz |
|---|---|
| Size | 38.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
acaac104280d93e4867be7cdde349c4875bf20a77b5bbe24645775bdbb9d831d
|
|
BLAKE2b-256 checksum How to use checksums |
2d502f8d9f5bfe57327ea357c08852b59ae53cca3987dbd950bf29cb43e574ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.
Transparency logRelease files / flask_session_manager_sk-1.3.1-py3-none-any.whl
| Download URL | flask_session_manager_sk-1.3.1-py3-none-any.whl |
|---|---|
| Size | 15.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9039a9f73750f8ce739b25de1dfb39fbc9fc4dcc5c19ede7b9322f05dd8fd89c
|
|
BLAKE2b-256 checksum How to use checksums |
b53e007902b5ff4366b7312c54ef23e709726b3dce85346559e86bab71a79da5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.
Transparency log