Skip to main content

Authetication pipeline for Arcadia server apps

Project description

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

Project details


Release history Release notifications | RSS feed

This version

0.1

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

arcadia_auth-0.1.tar.gz (19.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

arcadia_auth-0.1-py3-none-any.whl (8.5 kB view details)

Uploaded Python 3

File details

Details for the file arcadia_auth-0.1.tar.gz.

File metadata

  • Download URL: arcadia_auth-0.1.tar.gz
  • Upload date:
  • Size: 19.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for arcadia_auth-0.1.tar.gz
Algorithm Hash digest
SHA256 96fca317c9150cf56085f70009dbf0711da076bf1978b335e9c40576fefc9864
MD5 9dd7df706d96527f34a46133dcb7ef54
BLAKE2b-256 6f8cf2efe3be7b4f1ca52a5260a98402e7bee4e9ca1ce4cac67c1b780b3df10a

See more details on using hashes here.

Provenance

The following attestation bundles were made for arcadia_auth-0.1.tar.gz:

Publisher: release-pypi.yml on antolu/arcadia-auth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file arcadia_auth-0.1-py3-none-any.whl.

File metadata

  • Download URL: arcadia_auth-0.1-py3-none-any.whl
  • Upload date:
  • Size: 8.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for arcadia_auth-0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3441763172ef832861243680dda27ad3164bdb6d02e197e5c4724b1101ce4a21
MD5 1ac43bead5bfbbf4a954e1d4c15f9be7
BLAKE2b-256 616a8b86bb739c2703e70b278175ae565d423a86b79e5f6556d763185a9b4cfd

See more details on using hashes here.

Provenance

The following attestation bundles were made for arcadia_auth-0.1-py3-none-any.whl:

Publisher: release-pypi.yml on antolu/arcadia-auth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page