Protecting specific routes in your Kubernetes applications is essential—especially when handling sensitive internal tools or admin panels. One effective way to do this is by using oauth2-proxy at the ingress level, allowing you to offload authentication and authorization directly at the edge. This not only simplifies your app logic but also aligns well with enterprise-grade identity providers like Azure Entra ID (formerly Azure AD).
In this article, we’ll walk through how to protect selected URLs in your Kubernetes clusters using oauth2-proxy, with configurations for both Traefik (k3s-native) and nginx ingress, which differ in key areas. We’ll use Azure Entra ID for identity and AD security groups for access control—enabling seamless integration with existing Microsoft-based environments.

We’ll start by enabling HTTPS with Let’s Encrypt, touch briefly on the OAuth2 flow, then move on to practical step-by-step implementations for both Traefik and nginx.
Setting Up SSL with Let’s Encrypt and cert-manager
To enable HTTPS, we first install cert-manager and configure a Let’s Encrypt ClusterIssuer. This automates certificate provisioning and renewal for our ingress resources.
create letsencrypt-issuer.yaml :
# letsencrypt-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
namespace: kube-system
spec:
acme:
email: [email protected] # adjust it to your real email adress
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: traefik # or nginx
YAMLInstall cert-manager and apply the issuer:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
kubectl apply -f letsencrypt-issuer.yaml
BashWe use a simple Traefik deployment and service to test HTTPS.
Deploy an app to test
Let’s just deploy a simple app for the reference to for demo, all you need is a service (in our example named “nginx-demo-service”) so you feel free to use your own deployment but here for refrence for those who want to test.
Also, remember that you need a valid domain to point to your Kubernetes ingress IP. I am not covering that here.
This creates an nginx web page app (not to be mistaken with nginx ingress) with 2 replicas and a kuberneties service to point to them. nginx-demo.yaml :
# nginx-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-demo
labels:
app: nginx-demo
spec:
replicas: 2
selector:
matchLabels:
app: nginx-demo
template:
metadata:
labels:
app: nginx-demo
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-demo-service
spec:
selector:
app: nginx-demo
ports:
- protocol: TCP
port: 80
targetPort: 80
---
YAMLSet up SSL-enabled Ingress
We use a simple nginx
deployment and service to test HTTPS.
Here’s the key part of the ingress definition:
#ingress.yaml
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: "websecure" # traefik only
traefik.ingress.kubernetes.io/router.tls: "true" # traefik only
# nginx.ingress.kubernetes.io/ssl-redirect: "true"
# nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: traefik # or nginx
tls:
- hosts:
- www.your-domain.com
secretName: your-domain-com-tls # or call it what you want but remember it!
YAMLThis tells cert-manager to:
- Use the
letsencrypt-prod
Cluster Issuer. - Request a TLS certificate for
test-cluster.your-domain.com
. - Store it in the
your-domain-com-tls
secret.
Internally, cert-manager solves the ACME HTTP-01 challenge by creating an endpoint (/.well-known/acme-challenge/...
) via your ingress. Let’s Encrypt verifies it by making an HTTP call to your domain before issuing the certificate.
kubectl apply -f nginx-demo.yaml -n nginx-demo # if you want
kubectl apply -f ingress.yaml -n nginx-demo
BashYou should be able to see the call https://[your-domain.com]/ and get an SSL-protected responce
Installing oauth2-proxy in Kubernetes
oauth2-proxy is a reverse proxy that handles authentication using providers like Azure Entra ID, Google, GitHub, and more. It sits in front of your application and enforces OAuth2-based login. Only authenticated users can access the protected URLs.
In this setup, oauth2-proxy intercepts requests at the ingress level and checks for valid authentication tokens. If none are present, it redirects the user to your identity provider (e.g., Entra ID) for login. Once authenticated, the request proceeds to your backend service.
Configuring oauth2-proxy Helm Chart for Azure Entra ID
The Helm values file is the heart of your oauth2-proxy deployment. It tells the proxy how to authenticate users, what OAuth2 provider to use, and how to expose the proxy via Ingress. Here’s a breakdown of the critical settings when using Azure Entra ID as your identity provider.
📚 For more providers (Google, GitHub, generic OIDC, etc.), refer to the official docs:
https://oauth2-proxy.github.io/oauth2-proxy/configuration/providers/
✅ Set Up Azure Entra ID:
You need to create an App registration in your Azure tenant:
Follow the official oauth2-proxy guide via this YouTube tutorial.
Get the Client ID, Client Secret, and Tenant ID.
🔐 Generate a Cookie Secret
This is used to sign the session cookie:
openssl rand -base64 32
Bash📄 Set Up Values traefik
oauth2-proxy.values.yaml (traefik only)
# oauth2-proxy.values.yaml
replicaCount: 1
config:
clientID: "your-client-id"
clientSecret: "your-client-secret"
cookieSecret: "your-generated-cookie-secret"
configFile: |
provider="entra-id"
oidc_issuer_url="https://login.microsoftonline.com/<TENANT_ID>/v2.0"
scope="openid"
allowed_groups=["<GROUP_OBJECT_ID>"]
email_domains=["your-domain.com"]
whitelist_domains=["other-domain.com", "another-domain.com"]
skip_provider_button= true
ingress:
enabled: true
className: "traefik"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
traefik.ingress.kubernetes.io/router.entrypoints: "websecure"
traefik.ingress.kubernetes.io/router.tls: "true"
hosts:
- your-domain.com #(setup with www?)
- your-second-domain.com
tls:
- secretName: your-domain-com-tls # from our earlier serup of ingress
hosts:
- your-domain.com
- secretName: your-second-domain-com-tls
hosts:
- your-second-domain.com
Bash📄 Set Up Values nginx
oauth2-proxy.values.yaml (nginx only)
# oauth2-proxy.values.yaml
replicaCount: 1
config:
clientID: "your-client-id"
clientSecret: "your-client-secret"
cookieSecret: "your-generated-cookie-secret"
configFile: |
provider="entra-id"
oidc_issuer_url="https://login.microsoftonline.com/<TENANT_ID>/v2.0"
scope="openid"
allowed_groups= ["<GROUP_OBJECT_ID>"]
email_domains= ["your-domain.com"]
whitelist_domains= [ "other-domain.com", "another-domain.com" ]
skip_provider_button= true
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
hosts:
- your-domain.com #(setup with www?)
- your-second-domain.com
tls:
- secretName: your-domain-com-tls # from our earlier serup of ingress
hosts:
- your-domain.com
- secretName: your-second-domain-com-tls
hosts:
- your-second-domain.com
Bash🔎 Key Notes:
clientID
,clientSecret
, andcookieSecret
go underconfig
not insideconfigFile
.- Everything else goes inside
configFile
in the exact syntax oauth2-proxy expects. oidc_issuer_url
is constructed using your Azure Tenant ID.allowed_groups
supports Azure security group object IDs (not names).email_domains
andwhitelist_domains
are optional filters for domain-based access control.- The ingress section sets up SSL and routing using Traefik (can be adapted for nginx by changing className and annotations).
This structure avoids confusion around what belongs where in the values file, which is often unclear in the official docs.
Install oauth2-proxy in your cluster
helm repo add oauth2-proxy https://oauth2-proxy.github.io/manifests
kubectl create namespace oauth2-proxy
helm install oauth2-proxy oauth2-proxy/oauth2-proxy \
--values oauth2-proxy.values.yaml \
-n oauth2-proxy
Bash🔐 Traefik + oauth2-proxy: How It Works
Traefik integrates with oauth2-proxy using a special object called a Middleware, specifically the forwardAuth
feature.
🧩 How It Works
- Request arrives at
/secure-path
onyourdomain.com
. - Traefik attaches the Middleware to that route.
- Middleware forwards the request to oauth2-proxy’s
/oauth2/auth
endpoint. - oauth2-proxy checks:
- Is the user authenticated?
- Is the session valid?
- Do they belong to an allowed group/domain?
- If valid ✅ → the request proceeds to your service. If invalid ❌ → the user gets redirected to the Azure login flow.
🔧 Middleware Configuration
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: oauth2
namespace: nginx-demo
spec:
forwardAuth:
address: http://oauth2-proxy.oauth2-proxy.svc.cluster.local/oauth2/auth
trustForwardHeader: false
# ALTERNATIVE SET UP
# trustForwardHeader: true
# authResponseHeaders:
# - X-Auth-Request-Access-Token
# - Authorization
Bash- http://oauth2-proxy.oauth2-proxy.svc.cluster.local is the kubenties internal service URL, the format is
[service name].[namespace].[svc.cluster.local]
address
: internal cluster address of oauth2-proxy’s/oauth2/auth
endpoint.trustForwardHeader: false
: disables trusting forwarded headers, increasing security, switch to alternative if you need the headers down in your app.
🛡️ Ingress Example
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: media-ingress
namespace: nginx-demo
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: "websecure"
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
traefik.ingress.kubernetes.io/router.middlewares: "nginx-demo-oauth2@kubernetescrd" # <namespace>-<name>@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- www.your-domain.com #(we did not to without www in this setup)
secretName: your-domain-com-tls # from our earlier serup of ingress
rules:
- host: www.your-domain.com
http:
paths:
- path: /secure-path
pathType: Prefix
backend:
service:
name: nginx-demo-service # or your-service
port:
number: 80
Bash🧠 Summary
This setup ensures that only authenticated users (via Azure Entra ID) can access https://yourdomain.com/secure-path/**
. Everything else remains publicly accessible unless you attach the middleware to more paths.
🔐 Using oauth2-proxy with NGINX Ingress
Unlike Traefik, NGINX does not use Middleware objects. Instead, it handles OAuth2 authentication natively via ingress annotations that define how to forward requests to the oauth2-proxy for validation.
🧩 How It Works
- Requests to
/secure-path
are intercepted by NGINX Ingress. - NGINX uses the
auth-url
annotation to send a subrequest to oauth2-proxy’s/oauth2/auth
. - If not authenticated, the user is redirected via
auth-signin
to the Azure login. - Once authenticated, oauth2-proxy sets session cookies and sends headers back.
- The request is forwarded to your service.
🛡️ Secure Path Ingress
Use the following ingress to protect /secure-path/**
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: media-ingress
namespace: nginx-demo
annotations:
nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.oauth2-proxy.svc.cluster.local/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "http://oauth2-proxy.oauth2-proxy.svc.cluster.local/oauth2/start?rd=$escaped_request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "X-Auth-Request-Access-Token,Authorization"
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
spec:
ingressClassName: nginx
tls:
- hosts:
- www.your-domain.com #(we did not to without www in this setup)
secretName: your-domain-com-tls # from our earlier serup of ingress
rules:
- host: www.your-domain.com
http:
paths:
- path: /secure-path
pathType: Prefix
backend:
service:
name: nginx-demo-service # or your-service
port:
number: 80
Bash🧠 Summary
With NGINX, everything is handled via annotations — no extra Middleware objects. Just configure oauth2-proxy properly and use the right ingress annotations, and your paths will be protected via Azure Entra ID authentication.
Leave a Reply