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.
The webhook receives POST requests with JSON body:
timeout_ms
Maximum time to wait for webhook response.
Recommendations: - Local webhook: 1000-2000ms - Remote webhook: 5000-10000ms - Include network latency buffer
retry_attempts
Number of retries on webhook failure.
Retries use exponential backoff: 100ms, 200ms, 300ms, etc.
cache_ttl_sec
How long to cache successful verification results.
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.
When exceeded, oldest entries are evicted (LRU policy).
Memory usage: ~500 bytes per entry
circuit_breaker_threshold
Failures before circuit breaker opens.
When open, all authentication requests fail immediately.
circuit_breaker_timeout_sec
Time circuit breaker stays open before testing.
After timeout, one request is allowed through (half-open state).
rate_limit_per_sec
Maximum verification requests per second.
Protects webhook from being overwhelmed.
require_authentication
Enable or disable authentication requirement.
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:
| 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
- Use HTTPS for webhook communication
- Validate token signature using your secret key
- Check token expiration (
expclaim) - Verify issuer (
issclaim) if using multiple services - Implement topic authorization based on user permissions
- Set appropriate cache TTL balancing performance and security
- Monitor authentication failures for attack detection
Disabling Authentication
For development only:
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_msif webhook is slow - Check network connectivity to webhook
- Monitor webhook performance
Circuit Breaker Open
- Check webhook health and availability
- Review
circuit_breaker_thresholdsetting - Wait for
circuit_breaker_timeout_secor restart proxy