Skip to content

Commit

Permalink
Make OIDC and JWT authentication work together
Browse files Browse the repository at this point in the history
Currently, when both OIDC and JWT authentication mechanisms are configured in
the same SecurityPolicy, the OIDC is applied first. It ensures the presence of
the bearer and refresh tokens in cookies, and adds the Authorisation header to
the request. Then JWT is applied, validating the added header.

This setup works perfectly for browser requests. However, it blocks requests
from clients that provide a valid "Authorization: Bearer..." header (normally
non-browser clients). The OIDC mechanism kicks in first and redirects the
requests to the login pages because of the missing cookies.

Use Envoy Gateway's pass_through_matcher option to skip over the OIDC filter
when the request is going to be handled by the JWT filter later.
  • Loading branch information
Stephen Robin committed Jan 24, 2025
1 parent 25dfe10 commit a834e4d
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 5 deletions.
26 changes: 21 additions & 5 deletions internal/xds/translator/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (*oidc) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListe
continue
}

filter, err := buildHCMOAuth2Filter(route.Security.OIDC)
filter, err := buildHCMOAuth2Filter(route.Security)
if err != nil {
errs = errors.Join(errs, err)
continue
Expand All @@ -73,8 +73,8 @@ func (*oidc) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListe
}

// buildHCMOAuth2Filter returns an OAuth2 HTTP filter from the provided IR HTTPRoute.
func buildHCMOAuth2Filter(oidc *ir.OIDC) (*hcmv3.HttpFilter, error) {
oauth2Proto, err := oauth2Config(oidc)
func buildHCMOAuth2Filter(securityFeatures *ir.SecurityFeatures) (*hcmv3.HttpFilter, error) {
oauth2Proto, err := oauth2Config(securityFeatures)
if err != nil {
return nil, err
}
Expand All @@ -89,7 +89,7 @@ func buildHCMOAuth2Filter(oidc *ir.OIDC) (*hcmv3.HttpFilter, error) {
}

return &hcmv3.HttpFilter{
Name: oauth2FilterName(oidc),
Name: oauth2FilterName(securityFeatures.OIDC),
Disabled: true,
ConfigType: &hcmv3.HttpFilter_TypedConfig{
TypedConfig: OAuth2Any,
Expand All @@ -101,12 +101,14 @@ func oauth2FilterName(oidc *ir.OIDC) string {
return perRouteFilterName(egv1a1.EnvoyFilterOAuth2, oidc.Name)
}

func oauth2Config(oidc *ir.OIDC) (*oauth2v3.OAuth2, error) {
func oauth2Config(securityFeatures *ir.SecurityFeatures) (*oauth2v3.OAuth2, error) {
var (
tokenEndpointCluster string
err error
)

var oidc = securityFeatures.OIDC

if oidc.Provider.Destination != nil && len(oidc.Provider.Destination.Settings) > 0 {
tokenEndpointCluster = oidc.Provider.Destination.Name
} else {
Expand Down Expand Up @@ -228,6 +230,20 @@ func oauth2Config(oidc *ir.OIDC) (*oauth2v3.OAuth2, error) {
}
oauth2.Config.RetryPolicy = rp
}

// If both JWT and OIDC are enabled and an "Authorization: Bearer ..." header is set, OIDC should assume the request
// is from an API client (as opposed to a browser) and pass it through to the JWT filter.
if securityFeatures.JWT != nil {
var prefixMatcher = matcherv3.StringMatcher_Prefix{Prefix: "Bearer "}
var stringMatcher = matcherv3.StringMatcher{MatchPattern: &prefixMatcher}
var headerMatcher = routev3.HeaderMatcher{
Name: "Authorization",
HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{StringMatch: &stringMatcher},
}

oauth2.Config.PassThroughMatcher = []*routev3.HeaderMatcher{&headerMatcher}
}

return oauth2, nil
}

Expand Down
1 change: 1 addition & 0 deletions release-notes/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ security updates: |
new features: |
bug fixes: |
Fixed authentication when both JWT and OIDC are enabled.
# Enhancements that improve performance.
performance improvements: |
Expand Down

0 comments on commit a834e4d

Please sign in to comment.