Skip to content

Route auth schemes

DHIS2's Route API (/api/routes) proxies requests to upstream services. Its own auth is one of five discriminated variants — basic, API token, header, query param, OAuth2 client-credentials. The union is typed end-to-end via the AuthScheme Annotated union + AuthSchemeAdapter (a pydantic TypeAdapter) so callers can switch on the variant exhaustively with match.

When to reach for it

  • Authoring or editing a Route from Python (the route plugin already does this — the types let your own code stay on the same surface).
  • Reading a route's auth back from DHIS2 + branching on the variant (e.g. "rotate every Bearer token expiring this week").
  • Validating an inbound JSON blob (config file, CI fixture) as a typed AuthScheme before saving.

Worked example — parse + dispatch

from dhis2w_client import (
    AuthScheme,
    AuthSchemeAdapter,
    HttpBasicAuthScheme,
    ApiTokenAuthScheme,
    ApiHeadersAuthScheme,
    ApiQueryParamsAuthScheme,
    OAuth2ClientCredentialsAuthScheme,
)

# DHIS2 returns the auth block as a dict on the Route object. Adapter picks
# the right subclass by the `type` discriminator field.
raw = {"type": "http-basic", "username": "alice", "password": "..."}
scheme: AuthScheme = AuthSchemeAdapter.validate_python(raw)

match scheme:
    case HttpBasicAuthScheme(username=u):
        print(f"basic auth as {u}")
    case ApiTokenAuthScheme(token=t):
        print(f"api token {t[:6]}…")
    case ApiHeadersAuthScheme(headers=hs):
        print(f"headers: {sorted(hs)}")
    case ApiQueryParamsAuthScheme(queryParams=qs):
        print(f"query-params: {sorted(qs)}")
    case OAuth2ClientCredentialsAuthScheme(tokenUri=uri):
        print(f"oauth2 cc against {uri}")

Worked example — round-trip a Route's auth block

from dhis2w_client import AuthSchemeAdapter
from dhis2w_core.client_context import open_client
from dhis2w_core.profile import profile_from_env

async with open_client(profile_from_env()) as client:
    raw = await client.get_raw("/api/routes/abcdefghij")
    scheme = AuthSchemeAdapter.validate_python(raw["auth"])
    print(f"{raw['name']} -> auth.type={scheme.type}")

auth_schemes

Typed DHIS2 Route auth schemes — re-exports from generated v42 OAS models.

DHIS2's /api/routes and /api/webhooks objects carry an auth block describing how DHIS2 talks to upstream targets. OpenAPI defines the five leaf schemas — HttpBasicAuthScheme, ApiTokenAuthScheme, ... — but historically dropped the Jackson type discriminator (BUGS.md #14).

The codegen emitter patches the spec at build time (see dhis2w_codegen.spec_patches) to synthesise the discriminator block + add a type: Literal["<tag>"] field to each variant. This module then re-exports the generated leaves + the discriminated Union + a TypeAdapter helper so callers have one import path.

Subtypes (every one DHIS2 v42 accepts):

  • http-basic -> HttpBasicAuthScheme - RFC 7617 Basic auth (user + password).
  • api-token -> ApiTokenAuthScheme - DHIS2-flavour static token.
  • api-headers -> ApiHeadersAuthScheme - arbitrary custom headers.
  • api-query-params -> ApiQueryParamsAuthScheme - auth via query-string params.
  • oauth2-client-credentials -> OAuth2ClientCredentialsAuthScheme - upstream OAuth2 client-credentials flow.

Attributes

AuthScheme = Annotated[HttpBasicAuthScheme | ApiTokenAuthScheme | ApiHeadersAuthScheme | ApiQueryParamsAuthScheme | OAuth2ClientCredentialsAuthScheme, Field(discriminator='type')] module-attribute

Discriminated union for the 5 DHIS2 Route auth variants. Validate via AuthSchemeAdapter.

AuthSchemeAdapter = TypeAdapter(AuthScheme) module-attribute

Classes

ApiHeadersAuthScheme

Bases: BaseModel

OpenAPI schema ApiHeadersAuthScheme.

Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/oas/api_headers_auth_scheme.py
class ApiHeadersAuthScheme(_BaseModel):
    """OpenAPI schema `ApiHeadersAuthScheme`."""

    model_config = _ConfigDict(extra="allow", populate_by_name=True, defer_build=True)

    headers: dict[str, str] | None = None
    type: Literal["api-headers"] = "api-headers"

ApiQueryParamsAuthScheme

Bases: BaseModel

OpenAPI schema ApiQueryParamsAuthScheme.

Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/oas/api_query_params_auth_scheme.py
class ApiQueryParamsAuthScheme(_BaseModel):
    """OpenAPI schema `ApiQueryParamsAuthScheme`."""

    model_config = _ConfigDict(extra="allow", populate_by_name=True, defer_build=True)

    queryParams: dict[str, str] | None = None
    type: Literal["api-query-params"] = "api-query-params"

ApiTokenAuthScheme

Bases: BaseModel

OpenAPI schema ApiTokenAuthScheme.

Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/oas/api_token_auth_scheme.py
class ApiTokenAuthScheme(_BaseModel):
    """OpenAPI schema `ApiTokenAuthScheme`."""

    model_config = _ConfigDict(extra="allow", populate_by_name=True, defer_build=True)

    token: str | None = None
    type: Literal["api-token"] = "api-token"

HttpBasicAuthScheme

Bases: BaseModel

OpenAPI schema HttpBasicAuthScheme.

Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/oas/http_basic_auth_scheme.py
class HttpBasicAuthScheme(_BaseModel):
    """OpenAPI schema `HttpBasicAuthScheme`."""

    model_config = _ConfigDict(extra="allow", populate_by_name=True, defer_build=True)

    password: str | None = None
    type: Literal["http-basic"] = "http-basic"
    username: str | None = None

OAuth2ClientCredentialsAuthScheme

Bases: BaseModel

OpenAPI schema OAuth2ClientCredentialsAuthScheme.

Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/oas/o_auth2_client_credentials_auth_scheme.py
class OAuth2ClientCredentialsAuthScheme(_BaseModel):
    """OpenAPI schema `OAuth2ClientCredentialsAuthScheme`."""

    model_config = _ConfigDict(extra="allow", populate_by_name=True, defer_build=True)

    clientId: str | None = None
    clientSecret: str | None = None
    scopes: str | None = None
    tokenUri: str | None = None
    type: Literal["oauth2-client-credentials"] = "oauth2-client-credentials"

Functions

auth_scheme_from_route(route)

Parse a Route's auth field into the typed discriminated union.

Since the codegen spec-patches sweep Route.auth is already the discriminated Union; this helper exists for legacy callers that pass in a raw dict (e.g. loaded from JSON or from a pre-patched spec).

Example

route = await client.resources.routes.get("abc123") scheme = auth_scheme_from_route(route) match scheme: ... case HttpBasicAuthScheme(username=u): ... print(f"basic auth as {u}") ... case OAuth2ClientCredentialsAuthScheme(tokenUri=uri): ... print(f"oauth2 against {uri}")

Source code in packages/dhis2w-client/src/dhis2w_client/v42/auth_schemes.py
def auth_scheme_from_route(route: Any) -> AuthScheme | None:
    """Parse a `Route`'s `auth` field into the typed discriminated union.

    Since the codegen spec-patches sweep `Route.auth` is already the
    discriminated Union; this helper exists for legacy callers that pass
    in a raw dict (e.g. loaded from JSON or from a pre-patched spec).

    Example:
        >>> route = await client.resources.routes.get("abc123")
        >>> scheme = auth_scheme_from_route(route)
        >>> match scheme:
        ...     case HttpBasicAuthScheme(username=u):
        ...         print(f"basic auth as {u}")
        ...     case OAuth2ClientCredentialsAuthScheme(tokenUri=uri):
        ...         print(f"oauth2 against {uri}")
    """
    raw = getattr(route, "auth", None)
    if raw is None:
        return None
    if isinstance(raw, BaseModel):
        raw = raw.model_dump()
    if not isinstance(raw, dict) or "type" not in raw:
        return None
    return AuthSchemeAdapter.validate_python(raw)