Add Keycloak OIDC auth middleware with tenant-scoped access control #4

Closed
opened 2026-03-22 07:27:31 +00:00 by forgejo_admin · 0 comments

Type

Feature

Repo

forgejo_admin/minio-api

Lineage

plan-minio-mobile → Phase 2 → Phase 2b (Keycloak Auth + Tenant Scoping)

User Story

As a platform admin
I want all API requests authenticated via Keycloak tokens
So that only authorized users can access MinIO assets, scoped to their project

As a project stakeholder
I want to see only my project's files when I hit the API
So that I can't accidentally (or intentionally) access other projects' assets

Context

Phase 2a shipped the FastAPI service with 15 REST endpoints and no auth. This phase adds Keycloak OIDC token validation and tenant-scoped access control. After this, every request requires a valid Bearer token and stakeholders are restricted to their project prefix.

The Keycloak instance is at keycloak.tail5b443a.ts.net. Check what realms, clients, and groups already exist before creating new ones — Keycloak has built-in admin and account consoles.

Key decisions:

  • JWT validation against Keycloak JWKS — no session cookies, pure Bearer token
  • Group claims = tenant scope — Keycloak group westside maps to MinIO prefix assets/westside/
  • Admin role bypasses scoping — sees all buckets and prefixes
  • Stakeholder role — list/get/upload within their prefix only, no delete, no other buckets
  • 401 on missing/invalid token, 403 on out-of-scope access

File Targets

Files to create:

  • src/minio_api/auth.py — Keycloak OIDC middleware: JWKS fetching, JWT validation, role/group extraction, tenant context
  • src/minio_api/permissions.py — access control logic: is_admin(), get_allowed_prefix(), check_access()
  • tests/test_auth.py — unit tests with mock JWTs (no Keycloak needed)
  • tests/test_permissions.py — unit tests for access control logic

Files to modify:

  • src/minio_api/main.py — add auth middleware, configure Keycloak settings
  • src/minio_api/routes/buckets.py — inject auth dependency, scope bucket list for stakeholders
  • src/minio_api/routes/objects.py — inject auth dependency, scope prefix, block delete for stakeholders
  • src/minio_api/routes/presign.py — inject auth dependency, scope presigned URLs to allowed prefix
  • src/minio_api/routes/multipart.py — inject auth dependency, scope multipart to allowed prefix
  • src/minio_api/dependencies.py — add auth dependency (current user from token)

Files NOT to touch:

  • src/minio_sdk/ — SDK is a separate repo/package

Acceptance Criteria

  • All 15 endpoints require valid Bearer token (401 without)
  • Admin role sees all buckets and prefixes (full CRUD)
  • Stakeholder role sees only assets bucket, scoped to their group prefix
  • Stakeholder cannot delete objects (403)
  • Stakeholder cannot list/access objects outside their prefix (403)
  • Presigned URLs scoped — stakeholder can only generate URLs for their prefix
  • JWKS fetched from Keycloak and cached (not fetched per-request)
  • Auth disabled via env var for local development (AUTH_DISABLED=true skips validation)
  • Existing tests still pass (with auth disabled or mock tokens)
  • New auth unit tests with mock JWTs cover: valid admin, valid stakeholder, expired token, bad signature, missing group claim

Test Expectations

  • Unit tests: mock JWT tokens (no real Keycloak needed)
  • Unit tests: access control logic (admin vs stakeholder vs no-group)
  • Integration tests: existing tests should work with AUTH_DISABLED=true
  • Run command: pytest tests/ -v

Constraints

  • Use PyJWT or python-jose for JWT decoding (not a full OIDC client library)
  • JWKS endpoint: https://keycloak.tail5b443a.ts.net/realms/{realm}/protocol/openid-connect/certs
  • Keycloak realm: check existing realms first, create minio-mobile client if needed
  • Group claims in JWT: configured via Keycloak client mappers (group membership → token claim)
  • AUTH_DISABLED=true env var must bypass all auth for local dev and existing tests
  • FastAPI dependency injection pattern: current_user = Depends(get_current_user)

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • phase-minio-mobile-2b-keycloak-auth — phase note
  • phase-minio-mobile-2a-fastapi-routes — prerequisite (completed)
  • project-minio-mobile — project this advances
### Type Feature ### Repo `forgejo_admin/minio-api` ### Lineage `plan-minio-mobile` → Phase 2 → Phase 2b (Keycloak Auth + Tenant Scoping) ### User Story As a platform admin I want all API requests authenticated via Keycloak tokens So that only authorized users can access MinIO assets, scoped to their project As a project stakeholder I want to see only my project's files when I hit the API So that I can't accidentally (or intentionally) access other projects' assets ### Context Phase 2a shipped the FastAPI service with 15 REST endpoints and no auth. This phase adds Keycloak OIDC token validation and tenant-scoped access control. After this, every request requires a valid Bearer token and stakeholders are restricted to their project prefix. The Keycloak instance is at `keycloak.tail5b443a.ts.net`. Check what realms, clients, and groups already exist before creating new ones — Keycloak has built-in admin and account consoles. Key decisions: - **JWT validation against Keycloak JWKS** — no session cookies, pure Bearer token - **Group claims = tenant scope** — Keycloak group `westside` maps to MinIO prefix `assets/westside/` - **Admin role bypasses scoping** — sees all buckets and prefixes - **Stakeholder role** — list/get/upload within their prefix only, no delete, no other buckets - **401 on missing/invalid token, 403 on out-of-scope access** ### File Targets Files to create: - `src/minio_api/auth.py` — Keycloak OIDC middleware: JWKS fetching, JWT validation, role/group extraction, tenant context - `src/minio_api/permissions.py` — access control logic: `is_admin()`, `get_allowed_prefix()`, `check_access()` - `tests/test_auth.py` — unit tests with mock JWTs (no Keycloak needed) - `tests/test_permissions.py` — unit tests for access control logic Files to modify: - `src/minio_api/main.py` — add auth middleware, configure Keycloak settings - `src/minio_api/routes/buckets.py` — inject auth dependency, scope bucket list for stakeholders - `src/minio_api/routes/objects.py` — inject auth dependency, scope prefix, block delete for stakeholders - `src/minio_api/routes/presign.py` — inject auth dependency, scope presigned URLs to allowed prefix - `src/minio_api/routes/multipart.py` — inject auth dependency, scope multipart to allowed prefix - `src/minio_api/dependencies.py` — add auth dependency (current user from token) Files NOT to touch: - `src/minio_sdk/` — SDK is a separate repo/package ### Acceptance Criteria - [ ] All 15 endpoints require valid Bearer token (401 without) - [ ] Admin role sees all buckets and prefixes (full CRUD) - [ ] Stakeholder role sees only `assets` bucket, scoped to their group prefix - [ ] Stakeholder cannot delete objects (403) - [ ] Stakeholder cannot list/access objects outside their prefix (403) - [ ] Presigned URLs scoped — stakeholder can only generate URLs for their prefix - [ ] JWKS fetched from Keycloak and cached (not fetched per-request) - [ ] Auth disabled via env var for local development (`AUTH_DISABLED=true` skips validation) - [ ] Existing tests still pass (with auth disabled or mock tokens) - [ ] New auth unit tests with mock JWTs cover: valid admin, valid stakeholder, expired token, bad signature, missing group claim ### Test Expectations - [ ] Unit tests: mock JWT tokens (no real Keycloak needed) - [ ] Unit tests: access control logic (admin vs stakeholder vs no-group) - [ ] Integration tests: existing tests should work with `AUTH_DISABLED=true` - Run command: `pytest tests/ -v` ### Constraints - Use PyJWT or python-jose for JWT decoding (not a full OIDC client library) - JWKS endpoint: `https://keycloak.tail5b443a.ts.net/realms/{realm}/protocol/openid-connect/certs` - Keycloak realm: check existing realms first, create `minio-mobile` client if needed - Group claims in JWT: configured via Keycloak client mappers (group membership → token claim) - `AUTH_DISABLED=true` env var must bypass all auth for local dev and existing tests - FastAPI dependency injection pattern: `current_user = Depends(get_current_user)` ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `phase-minio-mobile-2b-keycloak-auth` — phase note - `phase-minio-mobile-2a-fastapi-routes` — prerequisite (completed) - `project-minio-mobile` — project this advances
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/minio-api#4
No description provided.