Implement Envoy proxy JWT authentication with OAuth2 integration

Advanced 45 min Apr 24, 2026 132 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure Envoy proxy with JWT authentication filters integrated with Keycloak OAuth2 provider for secure microservices communication and advanced rate limiting policies.

Prerequisites

  • Root or sudo access
  • Minimum 2GB RAM
  • Open ports 8000, 8080, 9901
  • Basic understanding of OAuth2 and JWT

What this solves

This tutorial implements JWT authentication for Envoy proxy using OAuth2 integration with Keycloak as the identity provider. You'll configure authentication filters, security policies, and rate limiting to secure your microservices architecture with token-based authentication.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions.

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget gnupg2 software-properties-common
sudo dnf update -y
sudo dnf install -y curl wget gnupg2

Install Envoy proxy

Download and install the latest Envoy proxy release with official repository setup.

curl -sL 'https://deb.dl.getenvoy.io/public/gpg.8115BA8E629CC074.key' | sudo gpg --dearmor -o /usr/share/keyrings/getenvoy-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/getenvoy-keyring.gpg] https://deb.dl.getenvoy.io/public/deb/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/getenvoy.list
sudo apt update
sudo apt install -y getenvoy-envoy
curl -sL 'https://rpm.dl.getenvoy.io/public/gpg.CF716AF503183491.key' | sudo rpm --import -
curl -sL 'https://rpm.dl.getenvoy.io/public/config.rpm.txt' | sudo tee /etc/yum.repos.d/getenvoy-rpm-stable.repo
sudo dnf install -y getenvoy-envoy

Install and configure Keycloak OAuth2 provider

Set up Keycloak as the OAuth2 identity provider for JWT token generation and validation.

cd /opt
sudo wget https://github.com/keycloak/keycloak/releases/download/23.0.3/keycloak-23.0.3.tar.gz
sudo tar -xzf keycloak-23.0.3.tar.gz
sudo mv keycloak-23.0.3 keycloak
sudo chown -R keycloak:keycloak /opt/keycloak
sudo useradd -r -s /bin/false keycloak

Configure Keycloak service

Create systemd service file for Keycloak with production configuration.

[Unit]
Description=Keycloak OAuth2 Provider
After=network.target

[Service]
Type=exec
User=keycloak
Group=keycloak
ExecStart=/opt/keycloak/bin/kc.sh start-dev --http-host=0.0.0.0 --http-port=8080
Restart=on-failure
RestartSec=5
Environment=KEYCLOAK_ADMIN=admin
Environment=KEYCLOAK_ADMIN_PASSWORD=SecurePassword123!
WorkingDirectory=/opt/keycloak

[Install]
WantedBy=multi-user.target

Start Keycloak service

Enable and start the Keycloak service to begin OAuth2 provider setup.

sudo systemctl daemon-reload
sudo systemctl enable --now keycloak
sudo systemctl status keycloak

Configure Keycloak realm and client

Create a realm and OAuth2 client for JWT token management. Access Keycloak admin console at http://your-server:8080.

# Create realm configuration
sudo mkdir -p /opt/keycloak/data/import
sudo tee /opt/keycloak/data/import/envoy-realm.json > /dev/null << 'EOF'
{
  "realm": "envoy-auth",
  "enabled": true,
  "clients": [
    {
      "clientId": "envoy-client",
      "enabled": true,
      "protocol": "openid-connect",
      "publicClient": false,
      "clientAuthenticatorType": "client-secret",
      "secret": "envoy-client-secret-123",
      "redirectUris": ["*"],
      "webOrigins": ["*"]
    }
  ],
  "users": [
    {
      "username": "testuser",
      "enabled": true,
      "credentials": [
        {
          "type": "password",
          "value": "TestPassword123!"
        }
      ]
    }
  ]
}
EOF

Create Envoy configuration directory

Set up directory structure for Envoy configuration files and certificates.

sudo mkdir -p /etc/envoy
sudo mkdir -p /etc/envoy/certs
sudo mkdir -p /var/log/envoy
sudo useradd -r -s /bin/false envoy
sudo chown -R envoy:envoy /var/log/envoy

Configure Envoy with JWT authentication

Create the main Envoy configuration with JWT authentication filters and OAuth2 integration.

admin:
  address:
    socket_address:
      protocol: TCP
      address: 127.0.0.1
      port_value: 9901

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 8000
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          access_log:
          - name: envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
          http_filters:
          - name: envoy.filters.http.jwt_authn
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
              providers:
                keycloak_provider:
                  issuer: http://localhost:8080/realms/envoy-auth
                  audiences:
                  - envoy-client
                  remote_jwks:
                    http_uri:
                      uri: http://localhost:8080/realms/envoy-auth/protocol/openid-connect/certs
                      cluster: keycloak_cluster
                      timeout: 5s
                    cache_duration:
                      seconds: 300
                  payload_in_metadata: jwt_payload
              rules:
              - match:
                  prefix: /
                requires:
                  provider_name: keycloak_provider
          - name: envoy.filters.http.local_ratelimit
            typed_config:
              "@type": type.googleapis.com/udpa.type.v1.TypedStruct
              type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
              value:
                stat_prefix: http_local_rate_limiter
                token_bucket:
                  max_tokens: 100
                  tokens_per_fill: 100
                  fill_interval: 60s
                filter_enabled:
                  runtime_key: local_rate_limit_enabled
                  default_value:
                    numerator: 100
                    denominator: HUNDRED
                filter_enforced:
                  runtime_key: local_rate_limit_enforced
                  default_value:
                    numerator: 100
                    denominator: HUNDRED
                response_headers_to_add:
                - append: false
                  header:
                    key: x-local-rate-limit
                    value: 'true'
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: backend_service

  clusters:
  - name: backend_service
    connect_timeout: 30s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: backend_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 3000
    health_checks:
    - timeout: 1s
      interval: 10s
      unhealthy_threshold: 3
      healthy_threshold: 2
      http_health_check:
        path: "/health"

  - name: keycloak_cluster
    connect_timeout: 5s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: keycloak_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 8080

Configure advanced security policies

Add additional security filters for enhanced protection including CORS and security headers.

# Additional HTTP filters for security
security_filters:
  cors_filter:
    name: envoy.filters.http.cors
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
  
  security_headers_filter:
    name: envoy.filters.http.local_reply
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.local_reply.v3.LocalReply
      mappers:
      - filter:
          status_code_filter:
            comparison:
              op: EQ
              value:
                default_value: 200
                runtime_key: unused_key
        headers_to_add:
        - header:
            key: "X-Frame-Options"
            value: "DENY"
          append: false
        - header:
            key: "X-Content-Type-Options"
            value: "nosniff"
          append: false
        - header:
            key: "X-XSS-Protection"
            value: "1; mode=block"
          append: false

Create Envoy systemd service

Configure systemd service for Envoy proxy with proper user permissions and resource limits.

[Unit]
Description=Envoy Proxy
After=network.target
Wants=network.target

[Service]
Type=simple
User=envoy
Group=envoy
ExecStart=/usr/bin/envoy -c /etc/envoy/envoy.yaml --service-cluster envoy-proxy --service-node envoy-node-1
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=30

[Install]
WantedBy=multi-user.target

Set proper file permissions

Configure correct ownership and permissions for Envoy configuration files and directories.

Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions.
sudo chown -R envoy:envoy /etc/envoy
sudo chmod 755 /etc/envoy
sudo chmod 644 /etc/envoy/envoy.yaml
sudo chmod 644 /etc/envoy/security-config.yaml
sudo chown -R envoy:envoy /var/log/envoy
sudo chmod 755 /var/log/envoy

Create sample backend service

Set up a simple Node.js backend service to test JWT authentication integration.

sudo mkdir -p /opt/backend
sudo tee /opt/backend/server.js > /dev/null << 'EOF'
const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  
  // CORS headers
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
  res.setHeader('Content-Type', 'application/json');
  
  if (req.method === 'OPTIONS') {
    res.writeHead(200);
    res.end();
    return;
  }
  
  if (parsedUrl.pathname === '/health') {
    res.writeHead(200);
    res.end(JSON.stringify({status: 'healthy', timestamp: new Date().toISOString()}));
  } else if (parsedUrl.pathname === '/api/protected') {
    res.writeHead(200);
    res.end(JSON.stringify({
      message: 'Access granted - JWT authentication successful',
      user: req.headers['x-envoy-jwt-payload'] || 'No JWT payload',
      timestamp: new Date().toISOString()
    }));
  } else {
    res.writeHead(404);
    res.end(JSON.stringify({error: 'Not found'}));
  }
});

server.listen(3000, '127.0.0.1', () => {
  console.log('Backend service listening on 127.0.0.1:3000');
});
EOF

Install Node.js and start backend service

Install Node.js and create systemd service for the backend application.

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo dnf install -y nodejs npm
sudo useradd -r -s /bin/false backend
sudo chown -R backend:backend /opt/backend

Create backend service systemd unit

Configure systemd service for the backend application with proper resource limits.

[Unit]
Description=Backend Service for Envoy JWT Testing
After=network.target

[Service]
Type=simple
User=backend
Group=backend
WorkingDirectory=/opt/backend
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

Configure firewall rules

Open required ports for Envoy, Keycloak, and backend services with specific security rules.

sudo ufw allow 8000/tcp comment 'Envoy proxy'
sudo ufw allow 8080/tcp comment 'Keycloak OAuth2'
sudo ufw allow 9901/tcp comment 'Envoy admin'
sudo ufw --force enable
sudo firewall-cmd --permanent --add-port=8000/tcp --add-port=8080/tcp --add-port=9901/tcp
sudo firewall-cmd --reload

Start all services

Enable and start Envoy proxy, Keycloak, and backend services in the correct order.

sudo systemctl daemon-reload
sudo systemctl enable --now backend
sudo systemctl enable --now envoy
sudo systemctl status keycloak envoy backend

Configure OAuth2 client credentials

Obtain OAuth2 access token

Test OAuth2 token generation using Keycloak's token endpoint with client credentials flow.

curl -X POST \
  http://localhost:8080/realms/envoy-auth/protocol/openid-connect/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password' \
  -d 'client_id=envoy-client' \
  -d 'client_secret=envoy-client-secret-123' \
  -d 'username=testuser' \
  -d 'password=TestPassword123!' | jq .

Create token extraction script

Create a helper script to extract and use JWT tokens for API testing.

#!/bin/bash

Extract JWT token from Keycloak

TOKEN_RESPONSE=$(curl -s -X POST \ http://localhost:8080/realms/envoy-auth/protocol/openid-connect/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'grant_type=password' \ -d 'client_id=envoy-client' \ -d 'client_secret=envoy-client-secret-123' \ -d 'username=testuser' \ -d 'password=TestPassword123!') if [ $? -eq 0 ]; then ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token') if [ "$ACCESS_TOKEN" != "null" ]; then echo "Bearer $ACCESS_TOKEN" else echo "Error: Could not extract access token" echo $TOKEN_RESPONSE | jq . exit 1 fi else echo "Error: Failed to get token from Keycloak" exit 1 fi
sudo chmod 755 /usr/local/bin/get-jwt-token

Verify your setup

Test the complete JWT authentication flow with OAuth2 integration.

# Check all services are running
sudo systemctl status keycloak envoy backend

Test Keycloak OAuth2 endpoint

curl -s http://localhost:8080/realms/envoy-auth/.well-known/openid-configuration | jq .

Test without authentication (should fail)

curl -v http://localhost:8000/api/protected

Get JWT token and test authenticated request

JWT_TOKEN=$(get-jwt-token) echo "Using token: ${JWT_TOKEN:0:50}..."

Test with valid JWT token

curl -H "Authorization: $JWT_TOKEN" \ -v http://localhost:8000/api/protected

Check Envoy admin interface

curl -s http://localhost:9901/stats | grep jwt

Test rate limiting

for i in {1..10}; do curl -H "Authorization: $JWT_TOKEN" \ -s -o /dev/null -w "%{http_code}\n" \ http://localhost:8000/api/protected sleep 0.1 done

Advanced rate limiting configuration

Configure global rate limiting

Add Redis-backed global rate limiting for distributed environments. First install Redis.

sudo apt install -y redis-server
sudo systemctl enable --now redis-server
sudo dnf install -y redis
sudo systemctl enable --now redis

Update Envoy configuration for global rate limiting

Add global rate limiting configuration to work alongside JWT authentication. For more advanced Envoy configurations, see our Envoy gRPC load balancing tutorial.

# Global rate limiting configuration
rate_limit_service:
  grpc_service:
    envoy_grpc:
      cluster_name: rate_limit_cluster
    timeout: 0.25s

Add to clusters section in main envoy.yaml

rate_limit_cluster: name: rate_limit_cluster connect_timeout: 1s type: STRICT_DNS lb_policy: ROUND_ROBIN protocol_selection: USE_CONFIGURED_PROTOCOL http2_protocol_options: {} load_assignment: cluster_name: rate_limit_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 8081

Security policy enforcement

Configure JWT validation policies

Add strict JWT validation rules including audience, issuer, and expiry checks.

# Enhanced JWT validation policies
jwt_validation_rules:
  - name: strict_validation
    requirements:
      - provider_name: keycloak_provider
        audiences: ["envoy-client"]
        requirement_type: REQUIRES_ANY
    match_conditions:
      - name: "api_routes"
        prefix: "/api/"
      - name: "admin_routes"
        prefix: "/admin/"
        additional_requirements:
          - custom_claim: "realm_access.roles"
            values: ["admin", "super-admin"]

Token refresh validation

token_validation: max_age_seconds: 3600 clock_skew_seconds: 30 verify_audience: true verify_issuer: true require_expiry: true

Implement request logging and monitoring

Configure comprehensive logging for JWT authentication events and security monitoring.

# Access logging configuration for JWT events
access_log_config:
  - name: jwt_access_log
    filter:
      status_code_filter:
        comparison:
          op: GE
          value:
            default_value: 400
    format: |
      [%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"
      %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT%
      %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%
      JWT_SUBJECT:"%REQ(X-JWT-SUBJECT)%" JWT_ISSUER:"%REQ(X-JWT-ISSUER)%"
      REMOTE_ADDR:"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
      USER_AGENT:"%REQ(USER-AGENT)%"
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
      path: "/var/log/envoy/jwt-access.log"

Common issues

Symptom Cause Fix
Envoy fails to start Configuration syntax error envoy --mode validate -c /etc/envoy/envoy.yaml
JWT validation fails JWKS endpoint unreachable Check curl http://localhost:8080/realms/envoy-auth/protocol/openid-connect/certs
401 Unauthorized responses Invalid client secret or token expired Regenerate token with get-jwt-token script
Rate limiting not working Missing rate limit configuration Check Envoy stats: curl localhost:9901/stats | grep rate_limit
Keycloak connection refused Service not started or port blocked sudo systemctl restart keycloak && sudo netstat -tlpn | grep 8080
Backend service unreachable Upstream cluster misconfigured Check curl localhost:3000/health and Envoy cluster stats

Next steps

Running this in production?

Need this managed? Running JWT authentication at scale adds complexity around token rotation, JWKS caching, rate limit coordination, and monitoring across multiple Envoy instances. See how we run infrastructure like this for European SaaS and fintech teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.