Skip to main content

arcadia-auth

Shared Keycloak/OIDC authentication library for Arcadia server applications. Provides an async OAuth 2.0 client for authorization code flows and a JWT validator for token verification.

Installation

This package is not published to PyPI. Install directly from the repository:

pip install git+https://github.com/antolu/arcadia-auth.git

To pin a specific version or commit:

pip install git+https://github.com/antolu/arcadia-auth.git@v0.1.0
pip install git+https://github.com/antolu/arcadia-auth.git@abc1234

In a requirements.txt or pyproject.toml:

arcadia-auth @ git+https://github.com/antolu/arcadia-auth.git@v0.1.0

Requirements

  • Python 3.11+
  • A running Keycloak instance (or any OIDC-compatible provider)

Configuration

All settings are loaded via OidcSettings, a Pydantic BaseSettings class that reads from environment variables or a .env file.

from arcadia_auth import OidcSettings

settings = OidcSettings(
    oidc_endpoint="http://keycloak:8080",        # internal endpoint (service-to-service)
    oidc_public_endpoint="http://localhost:9091", # public endpoint (browser-facing)
    oidc_realm="my-realm",
    oidc_client_id="my-client",
    oidc_client_secret="secret",
    oidc_redirect_uri="http://localhost:8000/auth/callback",
)

Or via environment variables:

OIDC_ENDPOINT=http://keycloak:8080
OIDC_PUBLIC_ENDPOINT=http://localhost:9091
OIDC_REALM=my-realm
OIDC_CLIENT_ID=my-client
OIDC_CLIENT_SECRET=secret
OIDC_REDIRECT_URI=http://localhost:8000/auth/callback

Settings reference

Field Type Default Description
oidc_endpoint str Internal OIDC server URL (used for service-to-service calls)
oidc_public_endpoint str Public OIDC server URL (used in browser redirect URLs)
oidc_realm str Keycloak realm name
oidc_client_id str OAuth client ID
oidc_client_secret str OAuth client secret
oidc_redirect_uri str Default OAuth redirect URI
oidc_jwks_cache_ttl int 3600 JWKS cache TTL in seconds
oidc_init_retries int 5 Retries for initialization
oidc_init_backoff float 2.0 Exponential backoff multiplier for retries

oidc_base_url, oidc_public_base_url, and oidc_issuer_url are derived properties and do not need to be set.

Usage

OAuth 2.0 authorization code flow

OidcClient handles the browser-facing OAuth flow: building authorization URLs, exchanging codes for tokens, refreshing tokens, and fetching user info.

import asyncio
from arcadia_auth import OidcClient, OidcSettings

settings = OidcSettings()
client = OidcClient(settings)

async def main() -> None:
    # Must be called once before using the client
    await client.initialize()

    # Build the login redirect URL
    url = client.authorization_url(
        redirect_uri="http://localhost:8000/auth/callback",
        state="random-csrf-token",
        scope="openid profile email",
    )
    # Redirect the user's browser to `url`

    # After the user logs in, Keycloak redirects to your callback with ?code=...&state=...
    tokens = await client.fetch_tokens(
        code="authorization-code-from-callback",
        redirect_uri="http://localhost:8000/auth/callback",
    )
    # tokens: {"access_token": "...", "refresh_token": "...", "id_token": "...", ...}

    # Refresh an access token
    new_tokens = await client.refresh_token(tokens["refresh_token"])

    # Fetch user info
    userinfo = await client.fetch_userinfo(tokens["access_token"])
    # userinfo: {"sub": "...", "email": "...", "name": "...", ...}

    # Revoke a token on logout (best-effort, errors are logged but not raised)
    await client.revoke_token(tokens["refresh_token"])

asyncio.run(main())

FastAPI example

from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from arcadia_auth import OidcClient, OidcSettings

settings = OidcSettings()
client = OidcClient(settings)

@asynccontextmanager
async def lifespan(app: FastAPI):
    await client.initialize()
    yield

app = FastAPI(lifespan=lifespan)

@app.get("/login")
async def login():
    url = client.authorization_url(
        redirect_uri=settings.oidc_redirect_uri,
        state="some-state",
        scope="openid profile email",
    )
    from fastapi.responses import RedirectResponse
    return RedirectResponse(url)

@app.get("/auth/callback")
async def callback(code: str, state: str):
    tokens = await client.fetch_tokens(code, settings.oidc_redirect_uri)
    return tokens

JWT token validation

OidcValidator validates bearer tokens on protected API endpoints. It fetches the JWKS from Keycloak and caches it for oidc_jwks_cache_ttl seconds.

import asyncio
from arcadia_auth import OidcValidator, OidcSettings

settings = OidcSettings()
validator = OidcValidator(settings)

async def main() -> None:
    # Must be called once before validating tokens
    await validator.initialize()

    token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
    claims = await validator.validate_token(token)
    # claims: {"sub": "user-id", "iss": "...", "exp": 1234567890, ...}

asyncio.run(main())

FastAPI dependency example

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from arcadia_auth import OidcValidator, OidcSettings, TokenExpiredError, TokenInvalidError

settings = OidcSettings()
validator = OidcValidator(settings)
bearer = HTTPBearer()

async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(bearer),
) -> dict:
    try:
        return await validator.validate_token(credentials.credentials)
    except TokenExpiredError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token expired")
    except TokenInvalidError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")

app = FastAPI()

@app.get("/protected")
async def protected(claims: dict = Depends(get_current_user)):
    return {"user": claims["sub"]}

Exceptions

All exceptions inherit from OidcError.

Exception When raised
OidcError Base class for all errors in this library
DiscoveryError OIDC discovery endpoint is unreachable or returns an unexpected response
JwksError JWKS endpoint is unreachable or returns an unexpected response
TokenExpiredError JWT exp claim is in the past
TokenInvalidError JWT signature is invalid, issuer does not match, or required claims are missing

Development

git clone https://github.com/antolu/arcadia-auth.git
cd arcadia-auth
pip install -e ".[test,dev]"
pre-commit install
pytest

Metadata

Release files for arcadia-auth 0.1

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

Source distribution (sdist)

Source distribution for arcadia-auth 0.1
File Size Uploaded Provenance Actions
arcadia_auth-0.1.tar.gz 19.5 kB With provenance Details for arcadia_auth-0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for arcadia-auth 0.1
File Interpreter ABI Platform Actions
arcadia_auth-0.1-py3-none-any.whl Python 3 none any Details for arcadia_auth-0.1-py3-none-any.whl

Total release size: 28.1 kB

Release files / arcadia_auth-0.1.tar.gz

Download URL arcadia_auth-0.1.tar.gz
Size 19.5 kB
Tags Source
SHA-256 checksum
How to use checksums
96fca317c9150cf56085f70009dbf0711da076bf1978b335e9c40576fefc9864
BLAKE2b-256 checksum
How to use checksums
6f8cf2efe3be7b4f1ca52a5260a98402e7bee4e9ca1ce4cac67c1b780b3df10a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 May 1, 2026.

Transparency log

Release files / arcadia_auth-0.1-py3-none-any.whl

Download URL arcadia_auth-0.1-py3-none-any.whl
Size 8.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3441763172ef832861243680dda27ad3164bdb6d02e197e5c4724b1101ce4a21
BLAKE2b-256 checksum
How to use checksums
616a8b86bb739c2703e70b278175ae565d423a86b79e5f6556d763185a9b4cfd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 May 1, 2026.

Transparency log

Security for arcadia-auth 0.1

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.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for arcadia_auth-0.1.tar.gz

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 May 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1 This release

2 release files Full provenance

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