Skip to content

Conversation

@shaun-nx
Copy link
Contributor

@shaun-nx shaun-nx commented Nov 6, 2025

Proposed changes

Status set to Implementable after merging #4136

This document proposes a solution for enabling Authentication use cases through NGINX Gateway Fabric.

Closes #4052

Checklist

Before creating a PR, run through this checklist and mark each as complete.

  • I have read the CONTRIBUTING doc
  • I have added tests that prove my fix is effective or that my feature works
  • I have checked that all unit tests pass after adding my changes
  • I have updated necessary documentation
  • I have rebased my branch onto main
  • I will ensure my PR is targeting the main branch and pulling from my branch from my own fork

Release notes

If this PR introduces a change that affects users and needs to be mentioned in the release notes,
please add a brief note that summarizes the change.

NONE

@shaun-nx shaun-nx requested review from a team as code owners November 6, 2025 18:19
@github-actions github-actions bot added the documentation Improvements or additions to documentation label Nov 6, 2025
@codecov
Copy link

codecov bot commented Nov 6, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.06%. Comparing base (668d4fb) to head (a0c8c04).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4235      +/-   ##
==========================================
- Coverage   86.08%   86.06%   -0.03%     
==========================================
  Files         132      132              
  Lines       14342    14342              
  Branches       35       35              
==========================================
- Hits        12347    12344       -3     
- Misses       1791     1793       +2     
- Partials      204      205       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

type JWTTokenSource struct {
// Read token from Authorization header. Default: true.
//
// +optional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set defaults using kubebuilder: default

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is an optional field though, it shouldn't have a default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a blanket statement? In the case of JWTTokenType, that is defined as an optional field. (i.e. it's optional for the user to set it) but we default to assigning the value to signed (e.g. auth_jwt_type signed;)

In the NGINX documentation, this directive defaults to signed when the directive is not specified.
So in my mind, making is optional for the user to specify this field, and providing a default value for it makes sense to me.

What are your thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine

Copy link
Contributor Author

@shaun-nx shaun-nx Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sjberman
I did a review of the current optional fields with defaults. From what I can see, they should stay with those defaults. Curious what you think though.
These are:

// +optional
// +kubebuilder:default="Restricted"
Realm *string `json:"realm,omitempty"`

// +optional
// +kubebuilder:default=60s
Leeway *v1alpha1.Duration `json:"leeway,omitempty"`

// +optional
// +kubebuilder:default=signed
Type *JWTTokenType `json:"type,omitempty"`

// +optional
// +kubebuilder:default=access_token
TokenName string `json:"tokenName,omitempty"`

// +optional
// +kubebuilder:default=401
StatusCode *int32 `json:"statusCode,omitempty"`

// +optional
// +kubebuilder:default=Basic
Scheme *AuthScheme `json:"scheme,omitempty"`

// +optional
// +kubebuilder:default=Unauthorized
BodyPolicy *AuthFailureBodyPolicy `json:"bodyPolicy,omitempty"`

@github-project-automation github-project-automation bot moved this from 🆕 New to 🏗 In Progress in NGINX Gateway Fabric Nov 6, 2025
Copy link
Contributor

@ciarams87 ciarams87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are mostly focussed on typos I spotted while doing a first pass, I haven't fully absorbed the content yet

This allows users to reference an external authentication services, such as Keycloak, to handle the authentication requests.
While this API is available in the experimental channel, it is subject to change.

Our decision to go forward with our own `AuthenticationFilter` was to ensure we could quckly provide authenticaiton to our users while allowing us to closley monitor progress of the ExternalAuthFilter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Our decision to go forward with our own `AuthenticationFilter` was to ensure we could quckly provide authenticaiton to our users while allowing us to closley monitor progress of the ExternalAuthFilter.
Our decision to go forward with our own `AuthenticationFilter` was to ensure we could quickly provide authentication to our users while allowing us to closely monitor progress of the ExternalAuthFilter.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, this proposed AuthenticationFilter is to supply different auth methods than provided in the ExternalAuth filter, and is technically unrelated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sjberman that's not entirely true. If we went forward with ExternalAuth filter, we could build out an external auth service that uses NGINX. It wouldn't prevent us from supplying those auth methods.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that is unrelated to this AuthenticationFilter. This filter is to configure our NGINX data plane to perform native auth, without the need for an external entity. The ExternalAuthFilter is for just using an external entity for auth, whatever that entity may be (it wouldn't be our direct NGINX data plane, but it could be an external NGINX instance).

These are two different solutions for implementing auth for the user, and are not technically related to each other. I only mention this because this sentence in the doc implies that this Filter is being implemented as a workaround to the ExternalAuth filter, but it's really not, it's a different auth solution. Just being pedantic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sjberman do you think this statement needs to be changed in any way?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine

Copy link
Collaborator

@sjberman sjberman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do a more thorough review once I'm back online full time.

This allows users to reference an external authentication services, such as Keycloak, to handle the authentication requests.
While this API is available in the experimental channel, it is subject to change.

Our decision to go forward with our own `AuthenticationFilter` was to ensure we could quckly provide authenticaiton to our users while allowing us to closley monitor progress of the ExternalAuthFilter.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, this proposed AuthenticationFilter is to supply different auth methods than provided in the ExternalAuth filter, and is technically unrelated.

// Configures "realm="<realm_value>" in WWW-Authenticate header in error page location.
//
// +optional
// +kubebuilder:default="Restricted"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realm has a default here but not in Basic Auth. Is that ok? And does it need to be capitalized?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. It doesn't need to be capitalized. This is mostly just for error logging from auth requests.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we lowercase it then?

Copy link
Contributor Author

@shaun-nx shaun-nx Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We certainly can. The example in the nginx basic auth and jwt auth docs show the realm set as "closed site" so there really isn't any standard default for this.

This mainly is used for things like credential caching. It uses scheme + origin + realm as the cache key.

As I think about it more, it might be safer to leave this as an empty string by default so users don't get confused by behaviour.

What do you think?

// Example: "auth_jwt_leeway 60s".
//
// +optional
// +kubebuilder:default=60s
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this the nginx default? If so, I don't think it's needed in here. Same for any other fields that may already be nginx defaults. Up until now we haven't set nginx defaults directly in the policy, because if they ever change, then we have to update our policy too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Thanks for catching that. For auth_jwt_leeway it defaults to 0s, so we can leave the default out of this one.
I'll make that same adjustment for the other fields. From what I ready up, 60s is a recommended setting in most scenarios when adjusting for clock skew. We can just document that though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been changed now. The comment shows +kubebuilder:default=0s to reflect the NGINX docs

Copy link
Collaborator

@sjberman sjberman Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't want a default, then we don't need that comment at all. just let nginx define it, we don't need to set it in the policy

Realm *string `json:"realm,omitempty"`

// Mode selects how JWT keys are provided: local file or remote JWKS.
Mode JWTKeyMode `json:"mode,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should probably be named KeyMode to be more specific.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, when I commented this I meant the field name, not the type. The type can stay as JWTKeyMode

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with the other fields that I left similar comments on.

// Required when Mode == File.
//
// +optional
File *JWTFileKeySource `json:"file,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd name this FileKeySource, since File by itself isn't really clear

// Required when Mode == Remote.
//
// +optional
Remote *JWTRemoteKeySource `json:"remote,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoteKeySource for same reasons

//
// +optional
// +kubebuilder:default=signed
Type *JWTTokenType `json:"type,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TokenType is more specific

// Defaults to reading from Authorization header.
//
// +optional
TokenSource *JWTTokenSource `json:"tokenSource,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any field types that have the name JWTToken in them should just be JWT, since the T in JWT stands for Token.

@shaun-nx shaun-nx requested a review from sjberman November 20, 2025 14:02
// KeyMode selects where JWT keys come from.
// +kubebuilder:validation:Enum=File;Remote
type JWTKeyMode string
type KeyMode string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not opposed to having JWT in the names of these fields. It makes it clear that they are for JWT. My only gripe was having JWTToken, which is redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I looked over it, while KeyMode was only related to JWT right now, the NGINX OIDC Module lets you specify a local secret, as well as a remote URL. So this could eventually be re-used when we implement OIDC auth

That being said, I'll look back over it and see if other fields where JWT was removed could still keep it.

// Configures "realm="<realm_value>" in WWW-Authenticate header in error page location.
//
// +optional
// +kubebuilder:default="Restricted"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we lowercase it then?

@sjberman
Copy link
Collaborator

Being that JWT is an NGINX Plus feature, we should also outline that fact, and what the behavior would be if an OSS user attemps to specify it (probably reject it, make the route as invalid).

@shaun-nx
Copy link
Contributor Author

shaun-nx commented Nov 20, 2025

Being that JWT is an NGINX Plus feature, we should also outline that fact, and what the behavior would be if an OSS user attemps to specify it (probably reject it, make the route as invalid).

Good point. I'll add that to the Status section.
Since we only process Filters that are attached to a HTTPRoute/GRPCRoute, should we reject the route itself, or can we "reject" the reference itself without needing to reject the route resource?

@sjberman
Copy link
Collaborator

sjberman commented Nov 20, 2025

Good point. I'll add that to the Status section.
Since we only process Filters that are attached to a HTTPRoute/GRPCRoute, should we reject the route itself, or can we "reject" the reference itself without needing to reject the route resource?

We should mark the rule invalid, but not the entire route. Other rules that don't use the filter can still work as intended, but this rule should not.

@shaun-nx
Copy link
Contributor Author

Good point. I'll add that to the Status section.
Since we only process Filters that are attached to a HTTPRoute/GRPCRoute, should we reject the route itself, or can we "reject" the reference itself without needing to reject the route resource?

We should mark the rule invalid, but not the entire route. Other rules that don't use the filter can still work as intended, but this rule should not.

Sounds good. I'll make a note of that. Thanks @sjberman 👍


#### Attaching a JWT AuthenticationFilter to a route when using NGINX OSS

If a user attempts to attach a JWT tpye AuthenticationFilter while using NGINX OSS, the rule referncing the filter will be `Rejected`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If a user attempts to attach a JWT tpye AuthenticationFilter while using NGINX OSS, the rule referncing the filter will be `Rejected`.
If a user attempts to attach a JWT type AuthenticationFilter while using NGINX OSS, the rule referencing the filter will be `Rejected`.


If a user attempts to attach a JWT tpye AuthenticationFilter while using NGINX OSS, the rule referncing the filter will be `Rejected`.

This can appear as `UnresolvedRef` to inform the user that the rule has been `Rejected`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Invalid an option? UnresolvedRef generally means "we couldn't find this resource".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to use the ResolvedRefs condition on the Route to show an error message but condition should still be true to not mark the route invalid. You said you will just remove the route rule to handle the error. Similar to this.


Only a single `AuthenticationFilter` may be referened in a single rule.

The `Status` the HTTPRoute/GRPCRoute in this scenario should be set to `Invalid`, and the resource should be `Rejected`.
Copy link
Contributor

@salonichf5 salonichf5 Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the route rule referencing the extensionRef should be rejected. Resource makes it sound like the route would be rejected.

Copy link
Contributor

@salonichf5 salonichf5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just one small edit. Lgtm

type FileKeySource struct {
// SecretRef references a Secret containing the JWKS.
SecretRef SecretObjectReference `json:"secretRef,omitempty"`
SecretRef LocalObjectReferenceWithKey `json:"secretRef,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not use SecretRef as the name either, for flexibility.

Copy link
Collaborator

@sjberman sjberman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some nits

### Additional Fields for JWT
`require`, `tokenSource` and `propagation` are some additional fields that may be incldued in future updates to the API.
These fields allow for more customization of how the JWT auth behavtes, but aren't required for the minial delivery of JWT Auth.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
These fields allow for more customization of how the JWT auth behavtes, but aren't required for the minial delivery of JWT Auth.
These fields allow for more customization of how the JWT auth behaves, but aren't required for the minimal delivery of JWT Auth.

Comment on lines +1050 to +1051
Example GoLang API changes:
```go
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Example GoLang API changes:
```go
Example GoLang API changes:
```go

Comment on lines +966 to +967
Example: Grant BasicAuth in app-ns to read a Secret in security-ns
```yaml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Example: Grant BasicAuth in app-ns to read a Secret in security-ns
```yaml
Example: Grant BasicAuth in app-ns to read a Secret in security-ns
```yaml

Comment on lines +984 to +985
AuthenticationFilter referencing the cross-namespace Secret
```yaml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AuthenticationFilter referencing the cross-namespace Secret
```yaml
AuthenticationFilter referencing the cross-namespace Secret
```yaml

Comment on lines +1005 to +1006
Example of what implementation of these fields might look like:
```yaml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Example of what implementation of these fields might look like:
```yaml
Example of what implementation of these fields might look like:
```yaml


This example shows a single HTTPRoute, with a single `filter` defined in a `rule`

![reference-1](/docs/images/authentication-filter/reference-1.png)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This image is probably outdated a bit for the field names.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may not even be that necessary to have, up to you.

// with required `key` field to extract data.
type LocalObjectReferenceWithKey struct {
v1.LocalObjectReference
Key string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we set a default key that we can use in our docs so users don't have to specify this unless they explicitly change it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

Status: 🏗 In Progress

Development

Successfully merging this pull request may close these issues.

Design for Authentication Filter

5 participants