Skip to main content

Token Service

Token Service is the Wormhole authority for creating, validating, rotating, and exchanging Wormhole access tokens.

Table of Contents

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 by token_service generate-jwks.
  • DYNACONF_AUTH__jwt__public_pem: JWT public PEM generated by token_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.
/api/v1/config Returns selected service configuration values.

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

CI runs the lint checks and the full test suite on pull requests and on pushes to main, with the dependencies pinned in uv.lock.

Run lint checks:

uv run ruff check token_service tests
uv run ruff format --check token_service tests

Run the full test suite, as CI does:

uv run pytest

The Kerberos integration tests boot an ephemeral MIT krb5 realm with k5test and are skipped when MIT krb5 is not installed. On Debian or Ubuntu, install krb5-kdc, krb5-admin-server, and krb5-user; on macOS, run brew install krb5.

Run only the non-integration tests:

uv run pytest -m "not integration"

Tox also runs Ruff (uvx tox -e lint) and the non-integration tests (uvx tox -e py311), but resolves its own dependencies instead of using uv.lock.

Developer Notes

  • token_service/server.py mounts versioned aliases under /api/v1, /api/latest, and /api/stable.
  • token_service/dependencies.py owns auth dependency wiring for OIDC sessions, admin tokens, user tokens, and impersonation flows.
  • token_service/services.py owns token creation, validation, JWT issuance, attestation, and rotation logic.
  • token_service/store/orm.py contains 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.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wormhole-token-service 0.2.5
File Size Uploaded
wormhole_token_service-0.2.5.tar.gz 229.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wormhole-token-service 0.2.5
File Interpreter ABI Platform
wormhole_token_service-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 278.9 kB

Release files / wormhole_token_service-0.2.5.tar.gz

Download URL wormhole_token_service-0.2.5.tar.gz
Size 229.8 kB
Tags Source
SHA-256 checksum
How to use checksums
4e09197ef1a79a8e6bd0b6b4350bdaed1fba41f83861b9fff2614613696a4009
BLAKE2b-256 checksum
How to use checksums
e8a9528cf2731dc05fc175f983ae94a614e0675131ccf0b81fecd99a213610c5
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 Oct 16, 2026.

Transparency log

Release files / wormhole_token_service-0.2.5-py3-none-any.whl

Download URL wormhole_token_service-0.2.5-py3-none-any.whl
Size 49.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bee936c064a7e8930e13d369ba54dc65d33997020b1231773069ab9c85348224
BLAKE2b-256 checksum
How to use checksums
41538c5de0947db3cb541134d1ad27490b316bbad89558dc1f36aa2569c320cd
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 Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page