fix: accept multi-issuer JWTs from any Authentik provider #1
+18
-7
@@ -19,20 +19,25 @@ func isValidIssuer(iss string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validator validates JWTs against Authentik JWKS.
|
// Validator validates JWTs against Authentik JWKS.
|
||||||
|
// Supports multi-issuer: any Authentik service account provider is accepted
|
||||||
|
// (portfolio-agent, memory-agent, api-gw, etc.) because all share the same
|
||||||
|
// JWKS signing key.
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
issuer string
|
issuer string // Not used for validation (kept for logging); issuer regex check is sufficient
|
||||||
audience string
|
audience string // Not used for validation; any audience from valid Authentik issuer is accepted
|
||||||
jwksURL string
|
jwksURL string
|
||||||
jwks *keyfunc.JWKS
|
jwks *keyfunc.JWKS
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewValidator creates a new JWT validator for a service.
|
// NewValidator creates a new JWT validator for a service.
|
||||||
|
// issuer and audience params are deprecated (ignored for validation) but kept
|
||||||
|
// for backward compatibility. Multi-issuer validation via isValidIssuer() is used instead.
|
||||||
// JWKS fetching is lazy (deferred until first validation).
|
// JWKS fetching is lazy (deferred until first validation).
|
||||||
func NewValidator(issuer, audience, jwksURL string) *Validator {
|
func NewValidator(issuer, audience, jwksURL string) *Validator {
|
||||||
return &Validator{
|
return &Validator{
|
||||||
issuer: issuer,
|
issuer: issuer, // deprecated param, kept for compat
|
||||||
audience: audience,
|
audience: audience, // deprecated param, kept for compat
|
||||||
jwksURL: jwksURL,
|
jwksURL: jwksURL,
|
||||||
jwks: nil, // Lazy-loaded on first use
|
jwks: nil, // Lazy-loaded on first use
|
||||||
}
|
}
|
||||||
@@ -124,10 +129,16 @@ func (v *Validator) ValidateBearerToken(authHeader string) (jwt.MapClaims, error
|
|||||||
return nil, fmt.Errorf("invalid issuer: %s", iss)
|
return nil, fmt.Errorf("invalid issuer: %s", iss)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check aud (audience)
|
// Check aud (audience) - accept any Authentik-provided audience
|
||||||
if aud, ok := claims["aud"].(string); !ok || aud != v.audience {
|
// since all Authentik service accounts use the same signing key.
|
||||||
return nil, fmt.Errorf("invalid audience: expected %s, got %s", v.audience, aud)
|
// The issuer check above is sufficient to ensure JWT came from Authentik.
|
||||||
|
if aud, ok := claims["aud"].(string); !ok {
|
||||||
|
return nil, fmt.Errorf("missing audience claim")
|
||||||
|
} else if aud == "" {
|
||||||
|
return nil, fmt.Errorf("empty audience claim")
|
||||||
}
|
}
|
||||||
|
// Note: Not hardcoding expected audience. Any audience from a valid Authentik
|
||||||
|
// issuer is accepted, since all service accounts are under the same trust boundary.
|
||||||
|
|
||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user