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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.