A HTTP Filter checking for OIDC Authentication, made for Envoy Plugins, written in Rust
37
stars
450
commits
Rust
primary language
Sep 9, 2026
updated

A Wasm-plugin for the Envoy-Proxy written in Rust acting as an HTTP Filter, that implements the OpenID Authorization Code Flow. Requests sent to the filter are checked for the presence of a valid session cookie. If the cookie is not present, the user is redirected to the authorization_endpoint to authenticate. After successful authentication, the user is redirected back to the original path with the authorization code in the URL query. The plugin then exchanges the code for a token using the token_endpoint and stores the token in the session. If the cookie is present and decryptable, the plugin validates the token and passes the request to the backend, if the token is valid (optional).
Go to demo-page to see the plugin in action. Auth0 is used as the OpenID provider. Simply create an account or login with Google. The plugin has been configured to show httpbin.org as the upstream. Then open the developer tools and check the cookies or use the httpbin cookie inspector. You will see a cookie called oidcSession-0. This is the session, that holds the authorization state. If you delete the cookie and refresh the page, you will be redirected to the authorization_endpoint to authenticate again.
This repo is the result of a bachelor thesis in Information Systems. It is inspired by two other projects: oidc-filter & wasm-oauth-filter. This project has several advantages and improvements:
authorization_endpoint to authenticate again.For developing the Rust Toolchain has to be installed and the WASM target has to be enabled. E.g. for Ubuntu this can be achieved by:
# Install Build essentials
apt install build-essential
# Install Rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Enable WASM compilation target
cargo build --target wasm32-wasip1 --release
Shortcut (make sure to have make installed):
make run
cargo build --target wasm32-wasip1 --release
# or
make build
docker compose up
curl localhost:10000
To deploy the plugin to production, the following steps are needed (either manually or via a CI/CD pipeline):
Build the plugin
1.1 with cargo build --target wasm32-wasip1 --release - this can be done in a initContainer (see k8s folder) and then copy the binary to the path /etc/envoy/proxy-wasm-plugins/ in the envoy container.
1.2 by using the pre-built Docker image antonengelhardt/wasm-oidc-plugin.
Run envoy as a container with the envoy.yaml file mounted through the ConfigMap as a volume.
Set up Service, Certificate, Ingress to expose the Envoy to the internet.
For reference, see the k8s folder.
To generate a detailed documentation, run (also hosted on GitHub Pages):
cargo doc --document-private-items --open
The plugin is configured via the envoy.yaml-file. The following configuration options are required:
| Name | Type | Description | Example | Required |
|---|---|---|---|---|
exclude_hosts | Vec<Regex> | A comma separated list Hosts (in Regex expressions), that are excluded from the filter. | ["localhost:10000"] | ❌ |
exclude_paths | Vec<Regex> | A comma separated list of paths (in Regex expressions), that are excluded from the filter. | ["/health"] | ❌ |
exclude_urls | Vec<Regex> | A comma separated list of URLs (in Regex expressions), that are excluded from the filter. | ["http://localhost:10000/health"] | ❌ |
access_token_header_name | string | If set, this name will be used to forward the access token to the backend. | X-Access-Token | ❌ |
access_token_header_prefix | string | The prefix of the header value, that is used to forward the access token. If empty, "" is used. | Bearer | ❌ |
id_token_header_name | string | If set, this name will be used to forward the id token to the backend. | X-Id-Token | ❌ |
id_token_header_prefix | string | The prefix of the header value, that is used to forward the id token. If empty, "" is used. | Bearer | ❌ |
cookie_name | string | The name of the cookie, that is used to store the session. Will be suffixed with a dash and a number for multiple cookies if the state is too long. | oidcSession | ✅ |
logout_path | string | The path, that is used to logout the user and clear all cookies. The user will be redirected to end_session_endpoint of the OIDC provider, if the server supports this; alternatively the user is sent to "/" | /_wasm-oidc-plugin/logout | ✅ |
filter_plugin_cookies | bool | Whether to filter the cookies that are managed and controlled by the plugin (namely cookie_name and nonce). | true | ✅ |
cookie_duration_in_s | u64 | The duration in seconds, after which the session cookie expires. | 86400 | ✅ |
token_validation | bool | Whether to validate the token or not. | true | ✅ |
aes_key | string | A base64 encoded AES-256 Key: openssl rand -base64 32 | <generated-aes-key> | ✅ |
reload_interval_in_h | u64 | The interval in hours, after which the OpenID configuration is reloaded. | 24 | ✅ |
ticking_interval_in_ms | u64 | The interval in milliseconds, after which the plugin will wait for the discovery endpoint to respond or send a new request. | 500 | ✅ |
open_id_configs | Vec<OpenIdConfig> | A list of OpenID Configuration objects. | See below | ✅ |
OpenIdConfig| Name | Type | Description | Example | Required |
|---|---|---|---|---|
name | string | The name of the OpenID provider (this will be shown on the Auth Page). | Google | ✅ |
image | string | The URL to the image of the OpenID provider (this will be shown on the Auth Page). | https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/2560px-Google_2015_logo.svg.png | ✅ |
config_endpoint | string | The open id configuration endpoint. | https://accounts.google.com/.well-known/openid-configuration | ✅ |
upstream_cluster | string | The name of the upstream cluster in your Envoy configuration. | httpbin | ✅ |
authority | string | The authority/domain of the authorization_endpoint. | accounts.google.com | ✅ |
redirect_uri | string | The redirect URI, that the authorization_endpoint will redirect to. | http://localhost:10000/oidc/callback | ✅ |
client_id | string | The client ID, for getting and exchanging the code. | wasm-oidc-plugin | ✅ |
scope | string | The scope, to validate | openid email | ✅ |
claims | map | The claims to request as defined here | See below | ✅ |
client_secret | string | The client secret, that is used to authenticate with the authorization_endpoint. | secret | ✅ |
audience | string | The audience, that is used to validate the token. | wasm-oidc-plugin | ✅ |
claimsThe claims are defined as a map of strings.
claims:
id_token:
groups: null
username: null
Legacy configs (provider fields at the root) still work and are converted at runtime with a deprecation warning. Prefer migrating to open_id_configs.
Set upstream_cluster to an Envoy cluster name that actually exists — auto-derivation from authority is often wrong.
# Requires PyYAML. Accepts a bare plugin config or a full Envoy/ConfigMap YAML.
python3 scripts/migrate-config.py envoy-legacy.yaml -o envoy-migrated.yaml
A state determines what to load next. The following states are possible and depending on the outcome, the state is changed or not:
| State | Description |
|---|---|
LoadingConfig | The plugin is loading the configuration from all config_endpoints. |
LoadingJwks | The plugin is loading the public keys from all jwks_uri. |
Ready | The plugin is ready to handle requests and will reload the configuration after the reload_interval_in_h has passed. |
Below is a state diagram for one single OpenID Provider
stateDiagram-v2
[*] --> LoadingConfig: on_configure() creates resolvers
note right of LoadingConfig
Plugin config loaded + getting OpenIDConfig
end note
LoadingConfig --> LoadingJwks: OpenID config response received
note right of LoadingJwks
Getting Keys from Endpoint
end note
LoadingJwks --> Ready: JWKS response received
note right of Ready
Keys stored, providers created, queued requests resumed
end note
Ready --> LoadingConfig: [every reload_interval_in_h]
Ready --> Ready: Process HTTP requests
When a new request arrives, the root context creates a new http context with the information that has been loaded previously.
Then, one of the following cases is handled:
authorization_endpoint after successful authentication. The plugin exchanges the code for a token using the token_endpoint and stores the token in the session. Then, the user is redirected back to the original request.authorization_endpoint to authenticate. Once, the user returns, the second case is handled. If multiple OpenID providers are configured, the user is redirected to a page where they can select the provider to authenticate with.sequenceDiagram
participant User
participant Plugin
participant Auth as Auth-Endpoint
participant Token as Token-Endpoint
participant App as App/Backend
Note over User, App: [No Cookies Present]
User->>Plugin: Tries to Access App/Backend
Plugin-->>User: Redirect to Auth-Endpoint
User->>Auth: User authenticates
Auth-->>Plugin: Authorization Code
Plugin->>Token: Exchange Code for Tokens
Token-->>Plugin: Tokens
Plugin-->>User: Reply with Cookies and redirect to Original Path
Note over User, App: [Cookies Present]
User->>Plugin: Tries to Access App/Backend
critical [Validation]
Plugin->>Plugin: Validate Cookie with keys
end
alt [Valid]
Plugin->>App: Allow Request
else [Invalid]
Plugin-->>User: Redirect to Auth-Endpoint
end
Git-Cliff automates the generation of changelogs, when following conventional commit messages
We are using Gitleaks to protect from unwanted secret leaking and prevent security incidents by detecting passwords, secrets, API keys, tokens and more in git repos.
To run gitleaks, install it first and then run:
gitleaks protect
# To get the list of leaks
gitleaks protect --verbose
If you want to install a pre-commit hook - you should - install pre-commit and run (from the root of the project):
pre-commit install
The pre-commit hook will also check for a clean code style, formatting best practices and enforce clippy lints.
Cargo-deny checks all dependencies for security vulnerabilities and license issues.
Install cargo-deny:
cargo install --locked cargo-deny
And then run:
cargo-deny check licenses
cargo-deny check advisories
These commands are also run in the CI pipeline.
My OpenID provider uses different hosts for discovery, JWKS, and token exchange (e.g. Google). How can I configure this?
Google publishes:
{
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs"
}
Add all three hosts to the Envoy cluster and enable auto_sni so TLS SNI matches the :authority used for each call:
- name: google
connect_timeout: 5s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http_protocol_options: {}
upstream_http_protocol_options:
auto_sni: true
load_assignment:
cluster_name: google
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: accounts.google.com
port_value: 443
- endpoint:
address:
socket_address:
address: www.googleapis.com
port_value: 443
- endpoint:
address:
socket_address:
address: oauth2.googleapis.com
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
Keep authority as accounts.google.com for discovery; the plugin uses the token/JWKS URL host for those calls.
Also ensure the Google OAuth client has your redirect_uri (e.g. http://localhost:10000/oidc/callback) allowlisted.
Rust
94.1%
Python
5.2%
A HTTP Filter checking for OIDC Authentication, made for Envoy Plugins, written in Rust
37
stars
450
commits
Rust
primary language
Sep 9, 2026
updated

A Wasm-plugin for the Envoy-Proxy written in Rust acting as an HTTP Filter, that implements the OpenID Authorization Code Flow. Requests sent to the filter are checked for the presence of a valid session cookie. If the cookie is not present, the user is redirected to the authorization_endpoint to authenticate. After successful authentication, the user is redirected back to the original path with the authorization code in the URL query. The plugin then exchanges the code for a token using the token_endpoint and stores the token in the session. If the cookie is present and decryptable, the plugin validates the token and passes the request to the backend, if the token is valid (optional).
Go to demo-page to see the plugin in action. Auth0 is used as the OpenID provider. Simply create an account or login with Google. The plugin has been configured to show httpbin.org as the upstream. Then open the developer tools and check the cookies or use the httpbin cookie inspector. You will see a cookie called oidcSession-0. This is the session, that holds the authorization state. If you delete the cookie and refresh the page, you will be redirected to the authorization_endpoint to authenticate again.
This repo is the result of a bachelor thesis in Information Systems. It is inspired by two other projects: oidc-filter & wasm-oauth-filter. This project has several advantages and improvements:
authorization_endpoint to authenticate again.For developing the Rust Toolchain has to be installed and the WASM target has to be enabled. E.g. for Ubuntu this can be achieved by:
# Install Build essentials
apt install build-essential
# Install Rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Enable WASM compilation target
cargo build --target wasm32-wasip1 --release
Shortcut (make sure to have make installed):
make run
cargo build --target wasm32-wasip1 --release
# or
make build
docker compose up
curl localhost:10000
To deploy the plugin to production, the following steps are needed (either manually or via a CI/CD pipeline):
Build the plugin
1.1 with cargo build --target wasm32-wasip1 --release - this can be done in a initContainer (see k8s folder) and then copy the binary to the path /etc/envoy/proxy-wasm-plugins/ in the envoy container.
1.2 by using the pre-built Docker image antonengelhardt/wasm-oidc-plugin.
Run envoy as a container with the envoy.yaml file mounted through the ConfigMap as a volume.
Set up Service, Certificate, Ingress to expose the Envoy to the internet.
For reference, see the k8s folder.
To generate a detailed documentation, run (also hosted on GitHub Pages):
cargo doc --document-private-items --open
The plugin is configured via the envoy.yaml-file. The following configuration options are required:
| Name | Type | Description | Example | Required |
|---|---|---|---|---|
exclude_hosts | Vec<Regex> | A comma separated list Hosts (in Regex expressions), that are excluded from the filter. | ["localhost:10000"] | ❌ |
exclude_paths | Vec<Regex> | A comma separated list of paths (in Regex expressions), that are excluded from the filter. | ["/health"] | ❌ |
exclude_urls | Vec<Regex> | A comma separated list of URLs (in Regex expressions), that are excluded from the filter. | ["http://localhost:10000/health"] | ❌ |
access_token_header_name | string | If set, this name will be used to forward the access token to the backend. | X-Access-Token | ❌ |
access_token_header_prefix | string | The prefix of the header value, that is used to forward the access token. If empty, "" is used. | Bearer | ❌ |
id_token_header_name | string | If set, this name will be used to forward the id token to the backend. | X-Id-Token | ❌ |
id_token_header_prefix | string | The prefix of the header value, that is used to forward the id token. If empty, "" is used. | Bearer | ❌ |
cookie_name | string | The name of the cookie, that is used to store the session. Will be suffixed with a dash and a number for multiple cookies if the state is too long. | oidcSession | ✅ |
logout_path | string | The path, that is used to logout the user and clear all cookies. The user will be redirected to end_session_endpoint of the OIDC provider, if the server supports this; alternatively the user is sent to "/" | /_wasm-oidc-plugin/logout | ✅ |
filter_plugin_cookies | bool | Whether to filter the cookies that are managed and controlled by the plugin (namely cookie_name and nonce). | true | ✅ |
cookie_duration_in_s | u64 | The duration in seconds, after which the session cookie expires. | 86400 | ✅ |
token_validation | bool | Whether to validate the token or not. | true | ✅ |
aes_key | string | A base64 encoded AES-256 Key: openssl rand -base64 32 | <generated-aes-key> | ✅ |
reload_interval_in_h | u64 | The interval in hours, after which the OpenID configuration is reloaded. | 24 | ✅ |
ticking_interval_in_ms | u64 | The interval in milliseconds, after which the plugin will wait for the discovery endpoint to respond or send a new request. | 500 | ✅ |
open_id_configs | Vec<OpenIdConfig> | A list of OpenID Configuration objects. | See below | ✅ |
OpenIdConfig| Name | Type | Description | Example | Required |
|---|---|---|---|---|
name | string | The name of the OpenID provider (this will be shown on the Auth Page). | Google | ✅ |
image | string | The URL to the image of the OpenID provider (this will be shown on the Auth Page). | https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/2560px-Google_2015_logo.svg.png | ✅ |
config_endpoint | string | The open id configuration endpoint. | https://accounts.google.com/.well-known/openid-configuration | ✅ |
upstream_cluster | string | The name of the upstream cluster in your Envoy configuration. | httpbin | ✅ |
authority | string | The authority/domain of the authorization_endpoint. | accounts.google.com | ✅ |
redirect_uri | string | The redirect URI, that the authorization_endpoint will redirect to. | http://localhost:10000/oidc/callback | ✅ |
client_id | string | The client ID, for getting and exchanging the code. | wasm-oidc-plugin | ✅ |
scope | string | The scope, to validate | openid email | ✅ |
claims | map | The claims to request as defined here | See below | ✅ |
client_secret | string | The client secret, that is used to authenticate with the authorization_endpoint. | secret | ✅ |
audience | string | The audience, that is used to validate the token. | wasm-oidc-plugin | ✅ |
claimsThe claims are defined as a map of strings.
claims:
id_token:
groups: null
username: null
Legacy configs (provider fields at the root) still work and are converted at runtime with a deprecation warning. Prefer migrating to open_id_configs.
Set upstream_cluster to an Envoy cluster name that actually exists — auto-derivation from authority is often wrong.
# Requires PyYAML. Accepts a bare plugin config or a full Envoy/ConfigMap YAML.
python3 scripts/migrate-config.py envoy-legacy.yaml -o envoy-migrated.yaml
A state determines what to load next. The following states are possible and depending on the outcome, the state is changed or not:
| State | Description |
|---|---|
LoadingConfig | The plugin is loading the configuration from all config_endpoints. |
LoadingJwks | The plugin is loading the public keys from all jwks_uri. |
Ready | The plugin is ready to handle requests and will reload the configuration after the reload_interval_in_h has passed. |
Below is a state diagram for one single OpenID Provider
stateDiagram-v2
[*] --> LoadingConfig: on_configure() creates resolvers
note right of LoadingConfig
Plugin config loaded + getting OpenIDConfig
end note
LoadingConfig --> LoadingJwks: OpenID config response received
note right of LoadingJwks
Getting Keys from Endpoint
end note
LoadingJwks --> Ready: JWKS response received
note right of Ready
Keys stored, providers created, queued requests resumed
end note
Ready --> LoadingConfig: [every reload_interval_in_h]
Ready --> Ready: Process HTTP requests
When a new request arrives, the root context creates a new http context with the information that has been loaded previously.
Then, one of the following cases is handled:
authorization_endpoint after successful authentication. The plugin exchanges the code for a token using the token_endpoint and stores the token in the session. Then, the user is redirected back to the original request.authorization_endpoint to authenticate. Once, the user returns, the second case is handled. If multiple OpenID providers are configured, the user is redirected to a page where they can select the provider to authenticate with.sequenceDiagram
participant User
participant Plugin
participant Auth as Auth-Endpoint
participant Token as Token-Endpoint
participant App as App/Backend
Note over User, App: [No Cookies Present]
User->>Plugin: Tries to Access App/Backend
Plugin-->>User: Redirect to Auth-Endpoint
User->>Auth: User authenticates
Auth-->>Plugin: Authorization Code
Plugin->>Token: Exchange Code for Tokens
Token-->>Plugin: Tokens
Plugin-->>User: Reply with Cookies and redirect to Original Path
Note over User, App: [Cookies Present]
User->>Plugin: Tries to Access App/Backend
critical [Validation]
Plugin->>Plugin: Validate Cookie with keys
end
alt [Valid]
Plugin->>App: Allow Request
else [Invalid]
Plugin-->>User: Redirect to Auth-Endpoint
end
Git-Cliff automates the generation of changelogs, when following conventional commit messages
We are using Gitleaks to protect from unwanted secret leaking and prevent security incidents by detecting passwords, secrets, API keys, tokens and more in git repos.
To run gitleaks, install it first and then run:
gitleaks protect
# To get the list of leaks
gitleaks protect --verbose
If you want to install a pre-commit hook - you should - install pre-commit and run (from the root of the project):
pre-commit install
The pre-commit hook will also check for a clean code style, formatting best practices and enforce clippy lints.
Cargo-deny checks all dependencies for security vulnerabilities and license issues.
Install cargo-deny:
cargo install --locked cargo-deny
And then run:
cargo-deny check licenses
cargo-deny check advisories
These commands are also run in the CI pipeline.
My OpenID provider uses different hosts for discovery, JWKS, and token exchange (e.g. Google). How can I configure this?
Google publishes:
{
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs"
}
Add all three hosts to the Envoy cluster and enable auto_sni so TLS SNI matches the :authority used for each call:
- name: google
connect_timeout: 5s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http_protocol_options: {}
upstream_http_protocol_options:
auto_sni: true
load_assignment:
cluster_name: google
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: accounts.google.com
port_value: 443
- endpoint:
address:
socket_address:
address: www.googleapis.com
port_value: 443
- endpoint:
address:
socket_address:
address: oauth2.googleapis.com
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
Keep authority as accounts.google.com for discovery; the plugin uses the token/JWKS URL host for those calls.
Also ensure the Google OAuth client has your redirect_uri (e.g. http://localhost:10000/oidc/callback) allowlisted.
Rust
94.1%
Python
5.2%