auth: add RS256 validation via JWKS (Phase 3)
CI / Python tests (push) Waiting to run
CI / Frontend build (push) Waiting to run

When --oidc-issuer is set, validate tokens as RS256 id_tokens fetched
against bincio-auth's JWKS endpoint (cached for 1h). Falls back to
HS256 if the RS256 check fails, so existing sessions keep working
during the transition. DB session lookup is the final fallback.

New --oidc-issuer flag reads BINCIO_OIDC_ISSUER env var.
This commit is contained in:
Davide Scaini
2026-06-03 15:43:44 +02:00
parent e24d290127
commit 3394be4ee9
2 changed files with 83 additions and 9 deletions
+9 -3
View File
@@ -24,11 +24,13 @@ console = Console()
@click.option("--sync-secret", default=None, envvar="BINCIO_SYNC_SECRET", help="Shared secret for POST /api/internal/rebuild (used by the sync-strava systemd timer).")
@click.option("--jwt-secret", default=None, envvar="BINCIO_AUTH_JWT_SECRET", help="Shared JWT secret from bincio-auth. When set, validates JWTs locally instead of DB session lookup.")
@click.option("--auth-api", default=None, envvar="BINCIO_AUTH_API", help="Internal URL of the bincio-auth API (e.g. http://127.0.0.1:4040). When set, admin user-state operations are proxied to bincio-auth.")
@click.option("--oidc-issuer", default=None, envvar="BINCIO_OIDC_ISSUER", help="OIDC issuer URL (e.g. https://bincio.org). When set, validates RS256 id_tokens via JWKS (preferred over HS256).")
def serve(data_dir: str, site_dir: str | None, host: str, port: int,
strava_client_id: str | None, strava_client_secret: str | None,
max_users: int | None, public_url: str | None,
webroot: str | None, dem_url: str | None,
sync_secret: str | None, jwt_secret: str | None, auth_api: str | None) -> None:
sync_secret: str | None, jwt_secret: str | None, auth_api: str | None,
oidc_issuer: str | None) -> None:
"""Start the bincio multi-user application server.
Handles auth, user management, and write operations.
@@ -72,6 +74,8 @@ def serve(data_dir: str, site_dir: str | None, host: str, port: int,
deps.jwt_secret = jwt_secret
if auth_api:
deps.auth_api = auth_api.rstrip("/")
if oidc_issuer:
deps.oidc_issuer = oidc_issuer
db = open_db(dd)
current_limit = get_setting(db, "max_users")
@@ -89,8 +93,10 @@ def serve(data_dir: str, site_dir: str | None, host: str, port: int,
else:
console.print(" Users: [dim]unlimited[/dim]")
console.print(f" DEM: [cyan]{deps.dem_url}[/cyan]")
if deps.jwt_secret:
console.print(" Auth: [green]JWT (bincio-auth)[/green]")
if deps.oidc_issuer:
console.print(f" Auth: [green]RS256 via {deps.oidc_issuer}[/green]" + (" + HS256 fallback" if deps.jwt_secret else ""))
elif deps.jwt_secret:
console.print(" Auth: [green]JWT HS256 (bincio-auth)[/green]")
else:
console.print(" Auth: [dim]local DB sessions[/dim]")
console.print()