Token Service
Token Service is the Wormhole authority for creating, validating, rotating, and exchanging Wormhole access tokens.
Table of Contents
- Description
- How Token Service Fits Into Wormhole
- Configuration
- Deployment
- Development
- API and Token Notes
- User Syncing Bootstrap
- Testing
- Developer Notes
- Governance
Description
Token Service is a Python FastAPI service that manages Wormhole access tokens.
It creates and stores user/admin tokens, validates X-Token credentials,
exchanges valid tokens or MFA/OIDC sessions for short-lived JWTs, publishes
JWKS for downstream validation, and exposes internal admin APIs for identity
sync and impersonated token creation. Major dependencies include FastAPI,
fastapi-offline, SQLAlchemy, Alembic, Dynaconf, Authlib, bcrypt, Pendulum,
psycopg2, attrs, tenacity, requests, and multimethod.
The service uses a layered architecture. token_service/command.py provides
the CLI entry point for run, openapi, and generate-jwks;
token_service/server.py assembles FastAPI routes and versioned API aliases;
token_service/routers contains public token, MFA, and well-known JWKS
endpoints; token_service/internal contains admin routes for users, groups, and
tokens; token_service/models.py contains attrs domain models;
token_service/pydantic_models.py defines API schemas; token_service/store
and token_service/service/uow.py implement SQLAlchemy repositories and unit
of work; and token_service/services.py implements token lifecycle logic.
Within Wormhole, Token Service is the identity and token authority. Holepunch uses it to exchange Wormhole access tokens and OAuth sessions for short-lived JWTs; Route Registry uses it to authenticate callers and validate JWTs; Airlock uses its JWKS endpoint to validate tokens before forwarding requests to local apps; and users or automation acquire tokens to access Wormhole-protected apps without managing a separate credential per service.
How Token Service Fits Into Wormhole
flowchart LR
User["User or automation"]
API["Token Service API"]
DB["Postgres"]
JWKS["JWKS endpoint"]
Holepunch["Holepunch"]
Registry["Route Registry"]
Airlock["Airlock"]
App["Wormhole app"]
User --> API
API --> DB
API --> JWKS
Holepunch --> API
Registry --> API
Airlock -. validates JWTs .-> JWKS
Holepunch --> App
Registry --> App
The service connects enterprise authentication to Wormhole tokens and JWTs so interactive browser access and automated API access use the same access-control foundation.
Configuration
Token Service uses Dynaconf. Defaults live in
token_service/config/settings.toml; local overrides can be supplied with
settings.toml, settings.local.toml, .secrets.toml, or DYNACONF_*
environment variables.
Important configuration groups include:
| Group | Purpose |
|---|---|
SERVER |
FastAPI host and port. |
DB |
SQLAlchemy database URL and credentials. |
AUTH.authlib_oidc |
OIDC provider, session cookie, redirect, and discovery settings. |
AUTH.jwt |
JWT algorithm, signing keys, and key ID. |
AUTH.admin |
Admin API secret material. |
TOKEN |
Session name, session lifetime, and maximum token lifetime. |
Generate JWKS material per environment:
uv run token_service generate-jwks --write-settings --overwrite
This creates jwks/private.pem, jwks/public.pem, jwks/kid.txt, and a local
settings override when requested. Treat generated private keys as secrets.
Deployment
OpenShift and Helm
The Helm chart in helm/token-service deploys the app, services,
ingress, service account, and config map. The app deployment
loads non-secret Dynaconf settings from token-service-config, loads secret
settings from token-service-secrets, and runs /app/scripts/migrate-db in an
init container before the API starts.
Create the required secrets before deploying.
1. Initialize PostgreSQL
Connect to PostgreSQL with psql and create the database, user, and schema:
CREATE DATABASE "tokenServiceDB";
CREATE USER "tokenServiceUser" WITH PASSWORD 'yourStrongPassword';
\c tokenServiceDB
CREATE SCHEMA "tokenServiceUser" AUTHORIZATION "tokenServiceUser";
Validate the connection:
psql -U tokenServiceUser -d tokenServiceDB -h localhost -p 5432
2. Create token-service-secrets
Required keys:
DYNACONF_AUTH__admin__secret_key: randomly generated admin secret.DYNACONF_AUTH__authlib_oidc__client_id: client ID registered with the OAuth provider.DYNACONF_AUTH__authlib_oidc__client_secret: client secret from the OAuth provider.DYNACONF_AUTH__authlib_oidc__session_config__secret_key: randomly generated session secret.DYNACONF_AUTH__jwt__private_pem: JWT private PEM generated bytoken_service generate-jwks.DYNACONF_AUTH__jwt__public_pem: JWT public PEM generated bytoken_service generate-jwks.DYNACONF_DB__username: postgres username.DYNACONF_DB__password: postgres password.DYNACONF_DB__url: postgres connection string.
3. Deploy Token Service
Deploy with the Helm values for the target environment:
helm upgrade --install token-service ./helm/token-service -f ./helm/token-service/values.yaml
Use the appropriate overlay and namespace for the target deployment.
CI publishes OpenAPI, publishes the Python package and container
image, copies images into OpenShift, and deploys with Helm. The container
installs wormhole-token-service[all] so the image gets the source build of
psycopg2; installing the package without the all extra leaves it with no
Postgres driver.
Development
Requirements:
- Python 3.11 or newer
- uv
- Postgres for migration work
Install locally:
uv sync --dev
The Postgres driver differs by environment. Local development and the dev
container use psycopg2-binary, which installs as a wheel and needs no
compiler; it comes from the dev dependency group. Production installs the
postgres extra, which pulls the source build of psycopg2 and needs libpq
and a compiler:
pip install "wormhole-token-service[all]"
The two are the same import package and must never share an environment, so
they are declared as conflicting in [tool.uv]. Use uv sync --dev for
development and uv sync --no-dev --extra all for a production-like
environment; --no-dev is required because uv enables the dev group by
default, and the extra conflicts with it.
Pre-Commit Hook:
uvx pre-commit install
This runs basic lint checks on the repository. We highly encourage using this
hook to avoid unnecessary lint CI failures.
Generate local JWKS/settings:
uv run wormhole_token_service generate-jwks --write-settings --overwrite
Run the API:
uv run wormhole_token_service run --host localhost --port 5000
Run migrations:
uv run alembic upgrade head
Generate OpenAPI:
uv run wormhole_token_service openapi
API and Token Notes
Important public endpoints include:
| Endpoint | Purpose |
|---|---|
/.well-known/jwks.json |
Publishes public keys used to validate JWTs. |
/api/v1/token/jwt |
Exchanges a valid Wormhole access token for a short-lived JWT. |
/api/v1/mfa/jwt |
Exchanges a valid OIDC/MFA session for a short-lived JWT. |
/api/v1/token/rotate |
Rotates eligible tokens after attestation. |
Important internal/admin endpoints include user, group, and admin token
management routes under /api/v1/admin. Admin token creation is implemented at:
/api/v1/admin/token
Token strings are represented as id.secret. Normal token secrets are stored as
bcrypt hashes. Subtokens are delegated child credentials that inherit parent
token lifetime constraints. JWT claims include user, group, scope, DUID,
parent-token, external ID, and token ID context used by downstream Wormhole
components.
User Syncing Bootstrap
If you have an identity syncing service, it needs an admin token so it can populate Token Service identity tables.
Use this bootstrap flow for a new environment.
1. Add a temporary admin user
INSERT INTO "tokenServiceUser"."user" (uid, is_admin, duid)
VALUES ('<your user>', TRUE, 1);
2. Authenticate to Token Service
Open this URL in a browser and complete authentication:
https://<token-service>/api/v1/mfa/jwt
Use browser developer tools to find the token-service-session cookie.
3. Create the Sync admin token
curl -H "Cookie: token-service-session={cookie}" \
-X POST \
-H "Content-Type: application/json" \
-d '{"name": "sync", "role": "IDENTITY"}' \
https://<token-service>/api/v1/admin/token
4. Store Secret
Store the secret where your user syncing service expects it.
5. Remove the temporary admin user
DELETE FROM "tokenServiceUser"."user" WHERE uid = '<your user>';
6. Verify cron behavior
When a new sync job starts, it should populate the user table in the Token Service database.
Testing
Run lint checks:
uvx tox -e lint
Run the Python test suite used by CI:
uvx tox -e py311
Run non-integration tests directly:
uv run pytest -m "not integration"
Tox runs Ruff checks and format checks for token_service and tests.
Developer Notes
token_service/server.pymounts versioned aliases under/api/v1,/api/latest, and/api/stable.token_service/dependencies.pyowns auth dependency wiring for OIDC sessions, admin tokens, user tokens, and impersonation flows.token_service/services.pyowns token creation, validation, JWT issuance, attestation, and rotation logic.token_service/store/orm.pycontains SQLAlchemy table mappings; keep Alembic migrations in sync with model changes.- E2E tests start a real Uvicorn server with fake OIDC dependencies.
Governance
Contributions are welcome. Contributors should look in CONTRIBUTING.md for
project guidelines on how to create and structure pull requests.
This project is licensed under the Apache 2.0 license with LLVM exception. The
full license text is available in LICENSE.
LLNL-CODE-2020712
Metadata
Release files for wormhole-token-service 0.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wormhole_token_service-0.2.3.tar.gz | 212.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wormhole_token_service-0.2.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 255.3 kB
Release files / wormhole_token_service-0.2.3.tar.gz
| Download URL | wormhole_token_service-0.2.3.tar.gz |
|---|---|
| Size | 212.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0adbd442d02c5a9ee57fb88567464635ba7f2c8aa973037eaa5c2a60c7072f79
|
|
BLAKE2b-256 checksum How to use checksums |
7a3044c409726421cdaca20c11d6c21f64fcd848d09a82cf2d4d1e8beec53e61
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.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 30, 2026.
Transparency logRelease files / wormhole_token_service-0.2.3-py3-none-any.whl
| Download URL | wormhole_token_service-0.2.3-py3-none-any.whl |
|---|---|
| Size | 42.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
01bbcc28d49abe4a8647327e959e0592f7e62ae5da2b2907b35395d7cc0fddca
|
|
BLAKE2b-256 checksum How to use checksums |
a1f2c40d3c202306632c3b44115b9b9e70990662b11ad2f54a0c705247953550
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.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 30, 2026.
Transparency log