Acquire and securely store OAuth2/OIDC tokens in JupyterHub.
tessera adds a per-server button in JupyterLab. On click, the user runs an OAuth2/OIDC authorization-code flow (confidential client, PKCE). A JupyterHub Service receives the callback, exchanges the code, and stores the refresh token encrypted at rest. Notebooks then read a fresh access token from the Service on demand. The button is green when a valid refresh token exists, red otherwise. tessera is generic: any OAuth2/OIDC provider is a configuration entry.
tessera is not an Authenticator. It never signs anyone in to JupyterHub: your Hub keeps whatever Authenticator it already uses (PAM, LDAP, OAuth, or any other). tessera runs a separate step after that login, acquiring tokens from external OAuth2/OIDC providers so notebooks can call the APIs those providers protect.
Signed in on 6 August, still green on 10 August: the access token expired several times in between, and each one was renewed from the stored refresh token, on demand.
How it works
- One button per declared server. The panel shows a button for each OAuth server in your configuration, green when a valid refresh token is stored for that user, red otherwise.
- A click starts an authorization-code flow with PKCE. The Service
generates the
stateand the PKCE verifier, sends the browser to the provider with the S256 challenge, and keeps the verifier to itself. - The provider calls back to the Service, which validates the
stateagainst the flow it started and exchanges the code for tokens. The browser never handles a token. - The refresh token is encrypted at rest, with the encryption key held separately from the database file, so the database on its own reveals nothing.
- Notebooks read an access token on demand. The kernel client
(
get_token("my-idp"), orTESSERA_TOKEN["my-idp"]) fetches a fresh token from the Service on each access. Nothing is cached kernel-side and no token ever sits in an environment variable, so a token revoked at the provider is never served from a stale copy.
Architecture (three surfaces)
| Surface | Stack | Role |
|---|---|---|
| Frontend | TypeScript/React | Per-server status button (green/red), shipped as a prebuilt JupyterLab extension. |
| Service | Python 3.10+ | JupyterHub Service exposing /login, /callback, /token; encrypted token store. |
| Admin CLI | Python (Typer) | Deployment and ops helpers: config-snippet, init-config, doctor, install-kernel-config. |
Install
pip install jupyterhub-tessera
Installing the Python package also ships the prebuilt JupyterLab extension; no manual frontend copy is required.
Project layout
| Path | What lives there |
|---|---|
src/tessera/ |
Service, admin CLI, kernel client, and the prebuilt lab extension |
frontend/src/ |
TypeScript/React panel sources, with their tests alongside them |
tests/ |
Python test suite (config, flow, store, service, kernel, end to end) |
docs/ |
Sphinx documentation sources |
Development
Requirements: Python 3.10+, Node.js 20+.
# Python (use a virtual environment created outside any synced folder)
pip install -e ".[dev]"
# Frontend
npm install
npm run build
Security
A refresh token and a client secret are encrypted at rest, with the encryption key held separately from the database. The frontend never reads, stores, or logs a token; it only consumes a boolean status.
License
MIT, Copyright (c) 2026 Michel TRUONG.
Metadata
Release files for jupyterhub-tessera 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jupyterhub_tessera-1.1.0.tar.gz | 757.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jupyterhub_tessera-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 848.0 kB
Release files / jupyterhub_tessera-1.1.0.tar.gz
| Download URL | jupyterhub_tessera-1.1.0.tar.gz |
|---|---|
| Size | 757.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d7d57e20a278cbe2e57f9049d57014486a5d1044efd1b26f4b7dbc421d886275
|
|
BLAKE2b-256 checksum How to use checksums |
158761b218862dd9e78fd25e942773c46f3fbcc13789bc746eb6eb58d79de738
|
| 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 9, 2026.
Transparency logRelease files / jupyterhub_tessera-1.1.0-py3-none-any.whl
| Download URL | jupyterhub_tessera-1.1.0-py3-none-any.whl |
|---|---|
| Size | 90.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
586040f5a6d6391a63318f93042c132c29bf72fe2cbc94c2d4988fdbf9f6c121
|
|
BLAKE2b-256 checksum How to use checksums |
a8fea87c75e5299718961e433d5ead7f33b40ae0c16328fea46cd6cdb0b9d086
|
| 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 9, 2026.
Transparency log