fix: accept multiple OIDC audiences for cross-client auth
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 1m15s

- Changed oidc_audience (string) to oidc_audiences (list)
- Now accepts tokens with audience: core-api, tatlock-ui, tatlock
- Fixes environment endpoint returning "default" user when using
  tatlock-ui token (audience mismatch was causing JWT claims error)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-01-07 19:01:39 +01:00
co-authored by Claude Opus 4.5
parent c1f16d44e5
commit 6ce34cc016
7 changed files with 28 additions and 17 deletions
+9
View File
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.10.5] - 2026-01-07
### Fixed
- **OIDC audience mismatch** - Accept tokens from multiple clients
- Changed `oidc_audience` (string) to `oidc_audiences` (list)
- Now accepts tokens with audience: `core-api`, `tatlock-ui`, or `tatlock`
- Fixes environment endpoint returning "default" user instead of authenticated username
## [1.10.4] - 2026-01-07
### Removed
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "core-api"
version = "1.10.4"
version = "1.10.5"
description = "Core Code API - Infrastructure management and tools API"
readme = "README.md"
requires-python = ">=3.12"
+6 -6
View File
@@ -23,16 +23,16 @@ class OIDCConfig:
# These will be set from environment variables in config.py
self.enabled = False
self.issuer = ""
self.audience = ""
self.audiences: list[str] = []
self.jwks_uri = ""
def configure(self, enabled: bool, issuer: str, audience: str):
def configure(self, enabled: bool, issuer: str, audiences: list[str]):
"""Configure OIDC settings"""
self.enabled = enabled
self.issuer = issuer
self.audience = audience
self.audiences = audiences
self.jwks_uri = f"{issuer.rstrip('/')}/jwks/"
logger.info(f"OIDC configured: enabled={enabled}, issuer={issuer}")
logger.info(f"OIDC configured: enabled={enabled}, issuer={issuer}, audiences={audiences}")
# Global OIDC config instance
@@ -129,12 +129,12 @@ async def get_current_user(
logger.warning(f"No matching key found for kid: {kid}")
raise HTTPException(status_code=401, detail="Invalid token key")
# Verify and decode token
# Verify and decode token (accepts any of the configured audiences)
payload = jwt.decode(
token,
rsa_key,
algorithms=["RS256"],
audience=oidc_config.audience,
audience=oidc_config.audiences,
issuer=oidc_config.issuer,
)
+2 -1
View File
@@ -84,7 +84,8 @@ class Settings(BaseSettings):
# OIDC Authentication (Authentik)
oidc_enabled: bool = False # Set to True to require authentication
oidc_issuer: str = "https://auth.schweitz.net/application/o/core-api/"
oidc_audience: str = "core-api"
# Accept tokens from multiple clients (core-api, tatlock-ui, tatlock)
oidc_audiences: list[str] = ["core-api", "tatlock-ui", "tatlock"]
# Authentik API (for token validation and user management)
# Must use domain name (not IP) when AUTHENTIK_COOKIE_DOMAIN is set
+6 -6
View File
@@ -64,16 +64,16 @@ class OIDCConfig:
# These will be set from environment variables in config.py
self.enabled = False
self.issuer = ""
self.audience = ""
self.audiences: list[str] = []
self.jwks_uri = ""
def configure(self, enabled: bool, issuer: str, audience: str):
def configure(self, enabled: bool, issuer: str, audiences: list[str]):
"""Configure OIDC settings"""
self.enabled = enabled
self.issuer = issuer
self.audience = audience
self.audiences = audiences
self.jwks_uri = f"{issuer.rstrip('/')}/jwks/"
logger.info(f"OIDC configured: enabled={enabled}, issuer={issuer}")
logger.info(f"OIDC configured: enabled={enabled}, issuer={issuer}, audiences={audiences}")
# Global OIDC config instance
@@ -177,12 +177,12 @@ async def get_current_user(
logger.warning(f"No matching key found for kid: {kid}")
raise HTTPException(status_code=401, detail="Invalid token key")
# Verify and decode token
# Verify and decode token (accepts any of the configured audiences)
payload = jwt.decode(
token,
rsa_key,
algorithms=["RS256"],
audience=oidc_config.audience,
audience=oidc_config.audiences,
issuer=oidc_config.issuer,
)
+2 -1
View File
@@ -91,7 +91,8 @@ class Settings(BaseSettings):
# OIDC Authentication (Authentik)
oidc_enabled: bool = False
oidc_issuer: str = "https://auth.schweitz.net/application/o/core-api/"
oidc_audience: str = "core-api"
# Accept tokens from multiple clients (core-api, tatlock-ui, tatlock)
oidc_audiences: list[str] = ["core-api", "tatlock-ui", "tatlock"]
# Authentik API (for token validation and user management)
authentik_url: str = "https://auth.schweitz.net"
+2 -2
View File
@@ -24,13 +24,13 @@ def initialize_oidc(settings: Settings) -> None:
auth_oidc_config.configure(
enabled=settings.oidc_enabled,
issuer=settings.oidc_issuer,
audience=settings.oidc_audience
audiences=settings.oidc_audiences
)
domains_oidc_config.configure(
enabled=settings.oidc_enabled,
issuer=settings.oidc_issuer,
audience=settings.oidc_audience
audiences=settings.oidc_audiences
)
if settings.oidc_enabled: