test: add comprehensive tests for secure WebSocket authentication priority

Co-authored-by: Wikid82 <176516789+Wikid82@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-17 12:56:46 +00:00
parent e1474e42aa
commit 36a8b408b8
2 changed files with 184 additions and 0 deletions

View File

@@ -184,3 +184,56 @@ func TestRequireRole_MissingRoleInContext(t *testing.T) {
assert.Equal(t, http.StatusUnauthorized, w.Code)
}
func TestAuthMiddleware_QueryParamFallback(t *testing.T) {
authService := setupAuthService(t)
user, err := authService.Register("test@example.com", "password", "Test User")
require.NoError(t, err)
token, err := authService.GenerateToken(user)
require.NoError(t, err)
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(AuthMiddleware(authService))
r.GET("/test", func(c *gin.Context) {
userID, _ := c.Get("userID")
assert.Equal(t, user.ID, userID)
c.Status(http.StatusOK)
})
// Test that query param auth still works (deprecated fallback)
req, _ := http.NewRequest("GET", "/test?token="+token, http.NoBody)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestAuthMiddleware_PrefersCookieOverQueryParam(t *testing.T) {
authService := setupAuthService(t)
// Create two different users
cookieUser, _ := authService.Register("cookie@example.com", "password", "Cookie User")
cookieToken, _ := authService.GenerateToken(cookieUser)
queryUser, _ := authService.Register("query@example.com", "password", "Query User")
queryToken, _ := authService.GenerateToken(queryUser)
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(AuthMiddleware(authService))
r.GET("/test", func(c *gin.Context) {
userID, _ := c.Get("userID")
// Should use the cookie user, not the query param user
assert.Equal(t, cookieUser.ID, userID)
c.Status(http.StatusOK)
})
// Both cookie and query param provided - cookie should win
req, _ := http.NewRequest("GET", "/test?token="+queryToken, http.NoBody)
req.AddCookie(&http.Cookie{Name: "auth_token", Value: cookieToken})
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}

View File

@@ -0,0 +1,131 @@
# WebSocket Authentication Security
## Overview
This document explains the security improvements made to WebSocket authentication in Charon to prevent JWT tokens from being exposed in access logs.
## Security Issue
### Before (Insecure)
Previously, WebSocket connections authenticated by passing the JWT token as a query parameter:
```
wss://example.com/api/v1/logs/live?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
**Security Risk:**
- Query parameters are logged in web server access logs (Caddy, nginx, Apache, etc.)
- Tokens appear in proxy logs
- Tokens may be stored in browser history
- Tokens can be captured in monitoring and telemetry systems
- An attacker with access to these logs can replay the token to impersonate a user
### After (Secure)
WebSocket connections now authenticate using HttpOnly cookies:
```
wss://example.com/api/v1/logs/live?source=waf&level=error
```
The browser automatically sends the `auth_token` cookie with the WebSocket upgrade request.
**Security Benefits:**
- ✅ HttpOnly cookies are **not logged** by web servers
- ✅ HttpOnly cookies **cannot be accessed** by JavaScript (XSS protection)
- ✅ Cookies are **not visible** in browser history
- ✅ Cookies are **not captured** in URL-based monitoring
- ✅ Token replay attacks are mitigated (tokens still have expiration)
## Implementation Details
### Frontend Changes
**Location:** `frontend/src/api/logs.ts`
Removed:
```typescript
const token = localStorage.getItem('charon_auth_token');
if (token) {
params.append('token', token);
}
```
The browser automatically sends the `auth_token` cookie when establishing WebSocket connections due to:
1. The cookie is set by the backend during login with `HttpOnly`, `Secure`, and `SameSite` flags
2. The axios client has `withCredentials: true`, enabling cookie transmission
### Backend Changes
**Location:** `backend/internal/api/middleware/auth.go`
Authentication priority order:
1. **Authorization header** (Bearer token) - for API clients
2. **auth_token cookie** (HttpOnly) - **preferred for browsers and WebSockets**
3. **token query parameter** - **deprecated**, kept for backward compatibility only
The query parameter fallback is marked as deprecated and will be removed in a future version.
### Cookie Configuration
**Location:** `backend/internal/api/handlers/auth_handler.go`
The `auth_token` cookie is set with security best practices:
- **HttpOnly**: `true` - prevents JavaScript access (XSS protection)
- **Secure**: `true` (in production with HTTPS) - prevents transmission over HTTP
- **SameSite**: `Strict` (HTTPS) or `Lax` (HTTP/IP) - CSRF protection
- **Path**: `/` - available for all routes
- **MaxAge**: 24 hours - automatic expiration
## Verification
### Test Coverage
**Location:** `backend/internal/api/middleware/auth_test.go`
- `TestAuthMiddleware_Cookie` - verifies cookie authentication works
- `TestAuthMiddleware_QueryParamFallback` - verifies deprecated query param still works
- `TestAuthMiddleware_PrefersCookieOverQueryParam` - verifies cookie is prioritized over query param
- `TestAuthMiddleware_PrefersAuthorizationHeader` - verifies header takes highest priority
### Log Verification
To verify tokens are not logged:
1. **Before the fix:** Check Caddy access logs for token exposure:
```bash
docker logs charon 2>&1 | grep "token=" | grep -o "token=[^&]*"
```
Would show: `token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
2. **After the fix:** Check that WebSocket URLs are clean:
```bash
docker logs charon 2>&1 | grep "/logs/live\|/cerberus/logs/ws"
```
Shows: `/api/v1/logs/live?source=waf&level=error` (no token)
## Migration Path
### For Users
No action required. The change is transparent:
- Login sets the HttpOnly cookie
- WebSocket connections automatically use the cookie
- Existing sessions continue to work
### For API Clients
API clients using Authorization headers are unaffected.
### Deprecation Timeline
1. **Current:** Query parameter authentication is deprecated but still functional
2. **Future (v2.0):** Query parameter authentication will be removed entirely
3. **Recommendation:** Any custom scripts or tools should migrate to using Authorization headers or cookie-based authentication
## Related Documentation
- [Authentication Flow](../plans/prev_spec_websocket_fix_dec16.md#authentication-flow)
- [Security Best Practices](https://owasp.org/www-community/HttpOnly)
- [WebSocket Security](https://datatracker.ietf.org/doc/html/rfc6455#section-10)