App-Client darf eine eigene Anwendung beim Anmeldedienst sein
In Authentik hat jede Anwendung standardmaessig ihren eigenen Issuer, abgeleitet aus dem Application-Slug. Web- und App-Client brauchen dort aber zwei getrennte Anwendungen, weil ein Provider entweder vertraulich ODER oeffentlich ist. Bisher wurde das App-Token gegen den Issuer und den Schluesselsatz der Web-Anwendung geprueft - die App-Anmeldung waere damit an der iss-Pruefung gescheitert. Discovery und JWKS werden jetzt je Adresse gepuffert statt je Anbieter, und OIDC_<KUERZEL>_NATIVE_DISCOVERY_URL benennt die Anwendung des App-Clients. Ohne die Variable bleibt es bei einer gemeinsamen Adresse. Ausserdem im README: der Signing Key muss gesetzt sein, sonst signiert Authentik mit HS256 und das Token wird abgelehnt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3d0ff54bf5
commit
31b3624cc7
@@ -76,30 +76,35 @@ def _holen(url: str) -> dict[str, Any]:
|
||||
) from fehler
|
||||
|
||||
|
||||
def metadaten(provider: OidcProvider, erzwingen: bool = False) -> dict[str, Any]:
|
||||
"""Discovery-Dokument des Anbieters (gepuffert)."""
|
||||
eintrag = _metadaten_cache.get(provider.slug)
|
||||
def metadaten(discovery_url: str, erzwingen: bool = False) -> dict[str, Any]:
|
||||
"""Discovery-Dokument (gepuffert).
|
||||
|
||||
Gepuffert wird nach der Adresse, nicht nach dem Anbieter: Browser- und
|
||||
App-Client koennen bei Authentik zwei getrennte Anwendungen mit je eigenem
|
||||
Issuer und eigenem Schluesselsatz sein.
|
||||
"""
|
||||
eintrag = _metadaten_cache.get(discovery_url)
|
||||
if not erzwingen and eintrag and eintrag[0] > time.monotonic():
|
||||
return eintrag[1]
|
||||
|
||||
daten = _holen(provider.discovery_url)
|
||||
daten = _holen(discovery_url)
|
||||
for pflicht in ("issuer", "authorization_endpoint", "token_endpoint", "jwks_uri"):
|
||||
if not daten.get(pflicht):
|
||||
raise OidcFehler(
|
||||
"discovery_unvollstaendig",
|
||||
f"Discovery-Dokument von '{provider.slug}' ohne '{pflicht}'.",
|
||||
f"Discovery-Dokument unter {discovery_url} ohne '{pflicht}'.",
|
||||
)
|
||||
_metadaten_cache[provider.slug] = (time.monotonic() + CACHE_SEKUNDEN, daten)
|
||||
_metadaten_cache[discovery_url] = (time.monotonic() + CACHE_SEKUNDEN, daten)
|
||||
return daten
|
||||
|
||||
|
||||
def _jwks(provider: OidcProvider, erzwingen: bool = False) -> Any:
|
||||
eintrag = _jwks_cache.get(provider.slug)
|
||||
def _jwks(discovery_url: str, erzwingen: bool = False) -> Any:
|
||||
eintrag = _jwks_cache.get(discovery_url)
|
||||
if not erzwingen and eintrag and eintrag[0] > time.monotonic():
|
||||
return eintrag[1]
|
||||
|
||||
schluessel = JsonWebKey.import_key_set(_holen(metadaten(provider)["jwks_uri"]))
|
||||
_jwks_cache[provider.slug] = (time.monotonic() + CACHE_SEKUNDEN, schluessel)
|
||||
schluessel = JsonWebKey.import_key_set(_holen(metadaten(discovery_url)["jwks_uri"]))
|
||||
_jwks_cache[discovery_url] = (time.monotonic() + CACHE_SEKUNDEN, schluessel)
|
||||
return schluessel
|
||||
|
||||
|
||||
@@ -118,7 +123,7 @@ def _client(provider: OidcProvider, redirect_uri: str) -> OAuth2Client:
|
||||
|
||||
def anmelde_url(provider: OidcProvider, state: str, nonce: str, code_verifier: str) -> str:
|
||||
"""Adresse, zu der der Browser fuer die Anmeldung geschickt wird."""
|
||||
meta = metadaten(provider)
|
||||
meta = metadaten(provider.discovery_url)
|
||||
with _client(provider, provider.redirect_uri) as client:
|
||||
url, _ = client.create_authorization_url(
|
||||
meta["authorization_endpoint"],
|
||||
@@ -131,7 +136,7 @@ def anmelde_url(provider: OidcProvider, state: str, nonce: str, code_verifier: s
|
||||
|
||||
def code_einloesen(provider: OidcProvider, code: str, code_verifier: str) -> dict[str, Any]:
|
||||
"""Tauscht den Autorisierungs-Code gegen Tokens (vertraulicher Web-Client)."""
|
||||
meta = metadaten(provider)
|
||||
meta = metadaten(provider.discovery_url)
|
||||
try:
|
||||
with _client(provider, provider.redirect_uri) as client:
|
||||
return client.fetch_token(
|
||||
@@ -154,14 +159,17 @@ def pruefe_id_token(
|
||||
id_token: str,
|
||||
nonce: str | None = None,
|
||||
erlaubte_audiences: tuple[str, ...] | None = None,
|
||||
discovery_url: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Prueft Signatur, ``iss``, ``aud``, ``exp`` (und ``nonce``, falls bekannt).
|
||||
|
||||
``erlaubte_audiences`` schraenkt die zulaessigen Empfaenger ein: der
|
||||
Web-Rueckweg akzeptiert nur die vertrauliche Client-ID, der App-Weg nur die
|
||||
der registrierten oeffentlichen Clients.
|
||||
der registrierten oeffentlichen Clients. ``discovery_url`` bestimmt, gegen
|
||||
welchen Issuer und welchen Schluesselsatz geprueft wird.
|
||||
"""
|
||||
meta = metadaten(provider)
|
||||
quelle = discovery_url or provider.discovery_url
|
||||
meta = metadaten(quelle)
|
||||
audiences = erlaubte_audiences or provider.erlaubte_audiences
|
||||
if not audiences:
|
||||
raise OidcFehler(
|
||||
@@ -187,10 +195,10 @@ def pruefe_id_token(
|
||||
|
||||
try:
|
||||
try:
|
||||
claims = _entschluesseln(_jwks(provider))
|
||||
claims = _entschluesseln(_jwks(quelle))
|
||||
except JoseError:
|
||||
# Kann ein frisch gedrehter Signaturschluessel sein – einmal neu laden.
|
||||
claims = _entschluesseln(_jwks(provider, erzwingen=True))
|
||||
claims = _entschluesseln(_jwks(quelle, erzwingen=True))
|
||||
claims.validate(leeway=UHREN_SPIELRAUM)
|
||||
except JoseError as fehler:
|
||||
raise OidcFehler("id_token_ungueltig", f"ID-Token abgelehnt: {fehler}") from fehler
|
||||
|
||||
Reference in New Issue
Block a user