Skip to content

JWT Authentication

Configure webhook-based JWT verification for securing SSE connections.

How It Works

RevenProx delegates JWT verification to your authentication service via HTTP webhook:

1. Client connects with Authorization header
2. Proxy extracts JWT token
3. Proxy calls your webhook with token + topic
4. Webhook validates and returns result
5. Proxy caches result and establishes connection

Configuration Options

webhook_url

URL of your JWT verification endpoint.

[jwt_verifier]
webhook_url = "https://auth.example.com/verify"

The webhook receives POST requests with JSON body:

{
  "jwt": "eyJhbGciOiJIUzI1NiIs...",
  "topic": "a1b2c3d4e5f67890abcdef1234567890"
}

timeout_ms

Maximum time to wait for webhook response.

[jwt_verifier]
timeout_ms = 5000

Recommendations: - Local webhook: 1000-2000ms - Remote webhook: 5000-10000ms - Include network latency buffer

retry_attempts

Number of retries on webhook failure.

[jwt_verifier]
retry_attempts = 3

Retries use exponential backoff: 100ms, 200ms, 300ms, etc.

cache_ttl_sec

How long to cache successful verification results.

[jwt_verifier]
cache_ttl_sec = 300

Trade-offs: - Higher TTL: Fewer webhook calls, delayed revocation - Lower TTL: More webhook calls, faster revocation

cache_max_size

Maximum number of cached verification results.

[jwt_verifier]
cache_max_size = 10000

When exceeded, oldest entries are evicted (LRU policy).

Memory usage: ~500 bytes per entry

circuit_breaker_threshold

Failures before circuit breaker opens.

[jwt_verifier]
circuit_breaker_threshold = 10

When open, all authentication requests fail immediately.

circuit_breaker_timeout_sec

Time circuit breaker stays open before testing.

[jwt_verifier]
circuit_breaker_timeout_sec = 60

After timeout, one request is allowed through (half-open state).

rate_limit_per_sec

Maximum verification requests per second.

[jwt_verifier]
rate_limit_per_sec = 1000

Protects webhook from being overwhelmed.

require_authentication

Enable or disable authentication requirement.

[jwt_verifier]
require_authentication = true

Danger

Only set to false for development/testing!

Webhook Implementation

Request Format

Your webhook receives:

POST /verify HTTP/1.1
Content-Type: application/json

{
  "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzM1Njg5NjAwfQ.signature",
  "topic": "a1b2c3d4e5f67890abcdef1234567890"
}

Response Format

Return JSON with verification result:

{
  "valid": true,
  "user_id": "user123",
  "expires_at": 1735689600
}
Field Type Description
valid boolean Whether the token is valid
user_id string User identifier (for logging)
expires_at integer Unix timestamp when token expires

Example Implementations

Node.js / Express

const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();
app.use(express.json());

const JWT_SECRET = process.env.JWT_SECRET;

app.post('/verify', (req, res) => {
  const { jwt: token, topic } = req.body;

  try {
    const decoded = jwt.verify(token, JWT_SECRET);

    // Optional: Check topic access
    if (!canAccessTopic(decoded.sub, topic)) {
      return res.json({
        valid: false,
        user_id: null,
        expires_at: null
      });
    }

    res.json({
      valid: true,
      user_id: decoded.sub,
      expires_at: decoded.exp
    });
  } catch (error) {
    res.json({
      valid: false,
      user_id: null,
      expires_at: null
    });
  }
});

function canAccessTopic(userId, topicHex) {
  // Implement your authorization logic
  return true;
}

app.listen(9000);

Python / Flask

from flask import Flask, request, jsonify
import jwt
import os

app = Flask(__name__)
JWT_SECRET = os.environ['JWT_SECRET']

@app.route('/verify', methods=['POST'])
def verify():
    data = request.get_json()
    token = data.get('jwt')
    topic = data.get('topic')

    try:
        decoded = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])

        return jsonify({
            'valid': True,
            'user_id': decoded.get('sub'),
            'expires_at': decoded.get('exp')
        })
    except jwt.InvalidTokenError:
        return jsonify({
            'valid': False,
            'user_id': None,
            'expires_at': None
        })

if __name__ == '__main__':
    app.run(port=9000)

Go

package main

import (
    "encoding/json"
    "net/http"
    "os"

    "github.com/golang-jwt/jwt/v5"
)

type VerifyRequest struct {
    JWT   string `json:"jwt"`
    Topic string `json:"topic"`
}

type VerifyResponse struct {
    Valid     bool    `json:"valid"`
    UserID    *string `json:"user_id"`
    ExpiresAt *int64  `json:"expires_at"`
}

func verifyHandler(w http.ResponseWriter, r *http.Request) {
    var req VerifyRequest
    json.NewDecoder(r.Body).Decode(&req)

    token, err := jwt.Parse(req.JWT, func(t *jwt.Token) (interface{}, error) {
        return []byte(os.Getenv("JWT_SECRET")), nil
    })

    w.Header().Set("Content-Type", "application/json")

    if err != nil || !token.Valid {
        json.NewEncoder(w).Encode(VerifyResponse{Valid: false})
        return
    }

    claims := token.Claims.(jwt.MapClaims)
    userID := claims["sub"].(string)
    exp := int64(claims["exp"].(float64))

    json.NewEncoder(w).Encode(VerifyResponse{
        Valid:     true,
        UserID:    &userID,
        ExpiresAt: &exp,
    })
}

func main() {
    http.HandleFunc("/verify", verifyHandler)
    http.ListenAndServe(":9000", nil)
}

Topic-Based Authorization

The webhook receives the topic UUID, enabling fine-grained access control:

function canAccessTopic(userId, topicHex) {
  // Example: User can only access their own notification topic
  const userTopicPrefix = hashUserId(userId);
  return topicHex.startsWith(userTopicPrefix);
}

Security Best Practices

  1. Use HTTPS for webhook communication
  2. Validate token signature using your secret key
  3. Check token expiration (exp claim)
  4. Verify issuer (iss claim) if using multiple services
  5. Implement topic authorization based on user permissions
  6. Set appropriate cache TTL balancing performance and security
  7. Monitor authentication failures for attack detection

Disabling Authentication

For development only:

[jwt_verifier]
webhook_url = ""
require_authentication = false

Warning

Never disable authentication in production. Anyone could connect to any topic.

Troubleshooting

401 Unauthorized

  • Check JWT token is included in Authorization: Bearer <token> header
  • Verify webhook is reachable and responding correctly
  • Check webhook response format matches expected schema

Webhook Timeout

  • Increase timeout_ms if webhook is slow
  • Check network connectivity to webhook
  • Monitor webhook performance

Circuit Breaker Open

  • Check webhook health and availability
  • Review circuit_breaker_threshold setting
  • Wait for circuit_breaker_timeout_sec or restart proxy

Next Steps