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 bundled UI is built
with Node 18 or newer, Mithril, Tailwind, and Webpack.
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"]
UI["Token Service UI/API"]
DB["Postgres"]
JWKS["JWKS endpoint"]
Holepunch["Holepunch"]
Registry["Route Registry"]
Airlock["Airlock"]
App["Wormhole app"]
User --> UI
UI --> DB
UI --> JWKS
Holepunch --> UI
Registry --> UI
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 builds the UI, publishes OpenAPI, publishes the Python package and container image, copies images into OpenShift, and deploys with Helm.
Development
Requirements:
- Python 3.11 or newer
- uv
- Node 18 or newer for the UI
- Postgres for migration work
Install locally:
uv venv
uv pip install -e .
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 token_service generate-jwks --write-settings --overwrite
Run the API:
uv run token_service run --host localhost --port 5000
Run migrations:
uv run alembic upgrade head
Generate OpenAPI:
uv run token_service openapi
Build the UI:
cd token_service/ui
npm ci
npm run build
The UI build is bundled into token_service/static.
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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wormhole_token_service-0.1.0.tar.gz.
File metadata
- Download URL: wormhole_token_service-0.1.0.tar.gz
- Upload date:
- Size: 219.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a23b36bfb43889c2a1ae651e4168aded5d1bb18c4fb005b59c4bfa21b7c0d7f
|
|
| MD5 |
dfc896162d3b2a62bb2f4311bb691e5e
|
|
| BLAKE2b-256 |
2c57256ce6dbc94df62c818fe33d2c2f2e308378145332fc685531da5ed2562d
|
Provenance
The following attestation bundles were made for wormhole_token_service-0.1.0.tar.gz:
Publisher:
release.yml on llnl/wormhole-token-service
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wormhole_token_service-0.1.0.tar.gz -
Subject digest:
5a23b36bfb43889c2a1ae651e4168aded5d1bb18c4fb005b59c4bfa21b7c0d7f - Sigstore transparency entry: 2274360609
- Sigstore integration time:
-
Permalink:
llnl/wormhole-token-service@a29603ff9e6d3d579cf329595aaeb2301aeb543c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/llnl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a29603ff9e6d3d579cf329595aaeb2301aeb543c -
Trigger Event:
push
-
Statement type:
File details
Details for the file wormhole_token_service-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wormhole_token_service-0.1.0-py3-none-any.whl
- Upload date:
- Size: 76.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9e19a8ee2384cbab32da74e42d457ce2446bcb3f28bbc09597ebbd6d7fbe769
|
|
| MD5 |
c0de3105b3b2da7304f01a8b65acab28
|
|
| BLAKE2b-256 |
9f8036bfd430ffebebf756afff4671c3e2f29e0e79a4517358cf9cb88893b4ec
|
Provenance
The following attestation bundles were made for wormhole_token_service-0.1.0-py3-none-any.whl:
Publisher:
release.yml on llnl/wormhole-token-service
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wormhole_token_service-0.1.0-py3-none-any.whl -
Subject digest:
a9e19a8ee2384cbab32da74e42d457ce2446bcb3f28bbc09597ebbd6d7fbe769 - Sigstore transparency entry: 2274360744
- Sigstore integration time:
-
Permalink:
llnl/wormhole-token-service@a29603ff9e6d3d579cf329595aaeb2301aeb543c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/llnl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a29603ff9e6d3d579cf329595aaeb2301aeb543c -
Trigger Event:
push
-
Statement type: