JWT Testing Guide¶
JWT Helper CLI Utility¶
Installation and Build¶
Build JWT Helper:
# Using Makefile (recommended)
make jwt-build
# Manual build
cd backend && go build -o bin/jwt-helper cmd/jwt-helper/main.go
Verify Installation:
# Check binary exists
ls -la backend/bin/jwt-helper
# Test help command
cd backend && ./bin/jwt-helper --help
Basic Usage¶
Generate Admin Token:
# Using Makefile (recommended - includes detailed output)
make jwt-admin EMAIL=admin@saga-test.com
# Output:
✅ Admin JWT Token Generated:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
📧 Email: admin@saga-test.com
⏰ Expires: 2025-10-07 11:00:00 UTC (24 hours)
🔑 Issuer: saga-defi-platform
Generate Admin Token (Token Only):
# Using Makefile (clean output - perfect for scripts)
make jwt-admin-token EMAIL=admin@saga-test.com
# Output (только токен):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Generate User Token:
# Using Makefile (requires database with existing user)
make jwt-user EMAIL=user@saga-test.com
# Output:
✅ User JWT Token Generated:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
📧 Email: user@saga-test.com
👤 User ID: 123
💼 Wallet: 0x742d35Cc9Cf3C4C3a3F5d7B5f
⏰ Expires: 2025-10-07 11:00:00 UTC
Validate Token:
# Using Makefile
make jwt-validate TOKEN=<jwt_token>
# Direct CLI
cd backend && ./bin/jwt-helper validate <jwt_token>
# Output:
✅ Token Valid
📧 Email: admin@saga-test.com
⏰ Issued: 2025-10-06 11:00:00 UTC
⏰ Expires: 2025-10-07 11:00:00 UTC
🔑 Issuer: saga-defi-platform
Introspect Token (Detailed Analysis):
cd backend && ./bin/jwt-helper introspect <jwt_token>
# Output:
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"email": "admin@saga-test.com",
"permissions": [
"approve_withdrawals",
"view_all_users"
],
"exp": 1696531200,
"iat": 1696444800,
"iss": "saga-defi-platform"
},
"signature": {
"valid": true,
"algorithm": "HS256"
}
}
Test Admin API Endpoints¶
Test Admin Ping:
# Generate token and test in one command
TOKEN=$(make jwt-admin-token EMAIL=admin@saga-test.com) && \
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/admin/ping
# Output:
{
"message": "Admin pong",
"admin_email": "admin@saga-test.com",
"timestamp": "2025-10-06T11:00:00Z"
}
Test Admin Users List:
cd backend && ./bin/jwt-helper test-admin-api <admin_token>
# Automated test of:
# - GET /api/admin/users
# - GET /api/admin/investments
# - GET /api/admin/withdrawals
Test User API Endpoints¶
Test User Balance:
# Generate user token
TOKEN=$(make jwt-user-token EMAIL=user@saga-test.com) && \
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/user/balance
# Output:
{
"balance": "1000.00",
"currency": "USDC",
"user_id": 123
}
Test User Endpoints:
cd backend && ./bin/jwt-helper test-user-api <user_token>
# Automated test of:
# - GET /api/user/balance
# - GET /api/user/investments
# - GET /api/user/transactions
Unit Testing JWT Services¶
Test JWT Generation¶
Actual Test Files:
- backend/tests/integration/admin_jwt_integration_test.go - admin JWT tests
- backend/tests/unit/jwt_validation_test.go - JWT validation logic
Example Test Pattern:
func TestGenerateAdminToken(t *testing.T) {
cfg := config.LoadTestConfig()
jwtService := jwt_service.NewJWTService(cfg)
// Generate admin token
token, err := jwtService.CreateAdminToken("admin@saga-test.com")
require.NoError(t, err)
require.NotEmpty(t, token)
// Parse token
parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
return []byte(cfg.GetJWTSecret()), nil
})
require.NoError(t, err)
// Verify claims
claims := parsedToken.Claims.(jwt.MapClaims)
assert.Equal(t, "admin@saga-test.com", claims["email"])
assert.NotNil(t, claims["permissions"])
assert.Equal(t, cfg.GetJWTIssuer(), claims["iss"])
}
Test User Token Generation:
func TestGenerateUserToken(t *testing.T) {
cfg := config.LoadTestConfig()
jwtService := jwt_service.NewJWTService(cfg)
// Generate user token
token, err := jwtService.CreateUserToken(123, "user@saga-test.com", "0x742d35...")
require.NoError(t, err)
// Parse and verify
parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
return []byte(cfg.GetJWTSecret()), nil
})
require.NoError(t, err)
claims := parsedToken.Claims.(jwt.MapClaims)
assert.Equal(t, float64(123), claims["user_id"])
assert.Equal(t, "user@saga-test.com", claims["email"])
assert.Equal(t, "0x742d35...", claims["wallet_address"])
}
Test JWT Validation¶
Actual Test Files:
- backend/tests/unit/jwt_validation_test.go - core validation logic
- backend/tests/security/unified_jwt_validation_test.go - security validation
Example Test Pattern:
func TestValidateToken(t *testing.T) {
cfg := config.LoadTestConfig()
validator := jwt_validator.NewJWTValidator(cfg)
jwtService := jwt_service.NewJWTService(cfg)
// Create valid token
validToken, _ := jwtService.CreateAdminToken("admin@saga-test.com")
// Test validation
token, err := validator.ValidateToken(validToken)
assert.NoError(t, err)
assert.True(t, token.Valid)
}
func TestValidateExpiredToken(t *testing.T) {
cfg := config.LoadTestConfig()
validator := jwt_validator.NewJWTValidator(cfg)
// Create token with 1-second expiration
claims := jwt_service.AdminClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(1 * time.Second).Unix(),
IssuedAt: time.Now().Unix(),
Issuer: cfg.GetJWTIssuer(),
},
Email: "admin@saga-test.com",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, _ := token.SignedString([]byte(cfg.GetJWTSecret()))
// Wait for expiration
time.Sleep(2 * time.Second)
// Validate (should fail)
_, err := validator.ValidateToken(tokenString)
assert.Error(t, err)
assert.Contains(t, err.Error(), "expired")
}
Test Signature Verification¶
func TestInvalidSignature(t *testing.T) {
cfg := config.LoadTestConfig()
validator := jwt_validator.NewJWTValidator(cfg)
// Create token with wrong secret
claims := jwt_service.AdminClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
IssuedAt: time.Now().Unix(),
},
Email: "admin@saga-test.com",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, _ := token.SignedString([]byte("wrong-secret"))
// Validate (should fail)
_, err := validator.ValidateToken(tokenString)
assert.Error(t, err)
assert.Contains(t, err.Error(), "signature")
}
Integration Testing with Middleware¶
Test Admin Auth Middleware¶
Actual Test Files:
- backend/tests/integration/jwt_middleware_direct_test.go - middleware integration tests
- backend/tests/integration/admin_jwt_integration_test.go - admin auth tests
Example Test Pattern:
func TestAdminAuthMiddleware(t *testing.T) {
cfg := config.LoadTestConfig()
jwtService := jwt_service.NewJWTService(cfg)
// Create admin token
adminToken, err := jwtService.CreateAdminToken("admin@saga-test.com")
require.NoError(t, err)
// Create test handler
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
adminEmail := r.Context().Value("admin_email").(string)
assert.Equal(t, "admin@saga-test.com", adminEmail)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
// Wrap with middleware
handler := middleware.RequireAdminAuth(cfg)(testHandler)
// Test request
req := httptest.NewRequest("GET", "/api/admin/users", nil)
req.Header.Set("Authorization", "Bearer "+adminToken)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
}
Test Unauthorized Access:
func TestAdminAuthMiddleware_Unauthorized(t *testing.T) {
cfg := config.LoadTestConfig()
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("Should not reach handler")
})
handler := middleware.RequireAdminAuth(cfg)(testHandler)
// Test without token
req := httptest.NewRequest("GET", "/api/admin/users", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
}
Test Non-Admin User:
func TestAdminAuthMiddleware_NonAdmin(t *testing.T) {
cfg := config.LoadTestConfig()
jwtService := jwt_service.NewJWTService(cfg)
// Create user token (not admin)
userToken, _ := jwtService.CreateUserToken(123, "user@saga-test.com", "0x...")
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("Should not reach handler")
})
handler := middleware.RequireAdminAuth(cfg)(testHandler)
req := httptest.NewRequest("GET", "/api/admin/users", nil)
req.Header.Set("Authorization", "Bearer "+userToken)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
}
Test User Auth Middleware¶
Actual Test Files:
- backend/tests/integration/jwt_middleware_direct_test.go - user auth middleware tests
- backend/tests/integration/balance_api_admin_jwt_test.go - API auth integration
Example Test Pattern:
func TestUserAuthMiddleware(t *testing.T) {
cfg := config.LoadTestConfig()
jwtService := jwt_service.NewJWTService(cfg)
userToken, _ := jwtService.CreateUserToken(123, "user@saga-test.com", "0x742d35...")
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value("user_id").(int64)
email := r.Context().Value("email").(string)
walletAddr := r.Context().Value("wallet_address").(string)
assert.Equal(t, int64(123), userID)
assert.Equal(t, "user@saga-test.com", email)
assert.Equal(t, "0x742d35...", walletAddr)
w.WriteHeader(http.StatusOK)
})
handler := middleware.RequireAuth(cfg)(testHandler)
req := httptest.NewRequest("GET", "/api/user/balance", nil)
req.Header.Set("Authorization", "Bearer "+userToken)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
}
2E Testing with Playwright¶
Admin E2E Authentication¶
File: frontend/e2e/tests/admin/auth.spec.ts
import { test, expect } from '@playwright/test';
import { generateAdminJWT } from '../../utils/admin-jwt-helper';
test.describe('Admin JWT Authentication E2E', () => {
test('Admin can access protected endpoints with valid JWT', async ({ page }) => {
// Generate admin token
const adminToken = await generateAdminJWT('admin@saga-test.com');
// Set Authorization header for all requests
await page.setExtraHTTPHeaders({
'Authorization': `Bearer ${adminToken}`
});
// Navigate to admin page
await page.goto('http://admin.saga.local:8080/');
// Verify admin access
await expect(page.locator('text=Admin Dashboard')).toBeVisible();
// Test protected API call
const response = await page.request.get('http://localhost:8080/api/admin/users', {
headers: {
'Authorization': `Bearer ${adminToken}`
}
});
expect(response.status()).toBe(200);
const data = await response.json();
expect(Array.isArray(data)).toBe(true);
});
test('Admin cannot access with invalid JWT', async ({ page }) => {
const invalidToken = 'invalid.jwt.token';
await page.setExtraHTTPHeaders({
'Authorization': `Bearer ${invalidToken}`
});
const response = await page.request.get('http://localhost:8080/api/admin/users', {
headers: {
'Authorization': `Bearer ${invalidToken}`
},
failOnStatusCode: false
});
expect(response.status()).toBe(401);
});
test('Admin cannot access with expired JWT', async ({ page }) => {
// Generate token with 1-second expiration
const expiredToken = await generateAdminJWT('admin@saga-test.com', 1);
// Wait for expiration
await new Promise(resolve => setTimeout(resolve, 2000));
const response = await page.request.get('http://localhost:8080/api/admin/users', {
headers: {
'Authorization': `Bearer ${expiredToken}`
},
failOnStatusCode: false
});
expect(response.status()).toBe(401);
const error = await response.json();
expect(error.error).toContain('expired');
});
});
User E2E Authentication¶
File: frontend/e2e/tests/user/auth.spec.ts
test.describe('User JWT Authentication E2E', () => {
test('User can access user endpoints with valid JWT', async ({ page }) => {
// Create test user and get token
const { token, userId } = await createTestUserAndGetToken('user@saga-test.com');
await page.setExtraHTTPHeaders({
'Authorization': `Bearer ${token}`
});
// Navigate to user page
await page.goto('http://app.saga.local:8080/');
// Verify user access
await expect(page.locator('text=My Balance')).toBeVisible();
// Test protected API
const response = await page.request.get('http://localhost:8080/api/user/balance', {
headers: {
'Authorization': `Bearer ${token}`
}
});
expect(response.status()).toBe(200);
const data = await response.json();
expect(data.user_id).toBe(userId);
});
test('User cannot access admin endpoints', async ({ page }) => {
const { token } = await createTestUserAndGetToken('user@saga-test.com');
const response = await page.request.get('http://localhost:8080/api/admin/users', {
headers: {
'Authorization': `Bearer ${token}`
},
failOnStatusCode: false
});
expect(response.status()).toBe(403);
});
});
JWT Helper Utilities for E2E¶
File: frontend/e2e/utils/admin-jwt-helper.ts
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
export async function generateAdminJWT(email: string, expirationSeconds?: number): Promise<string> {
try {
// Use Makefile command to generate token
const { stdout } = await execAsync(`make jwt-admin-token EMAIL=${email}`);
const token = stdout.trim();
if (!token || token.includes('error')) {
throw new Error(`Failed to generate admin JWT: ${stdout}`);
}
return token;
} catch (error) {
console.error('Error generating admin JWT:', error);
throw error;
}
}
export async function validateJWT(token: string): Promise<boolean> {
try {
const { stdout } = await execAsync(`make jwt-validate TOKEN=${token}`);
return stdout.includes('Token Valid');
} catch (error) {
return false;
}
}
File: frontend/e2e/utils/real-auth-helpers.ts
export async function createTestUserAndGetToken(email: string): Promise<{ token: string; userId: number }> {
// Create user in database
const user = await createTestUser(email);
// Generate user JWT using backend helper
const { stdout } = await execAsync(`make jwt-user EMAIL=${email}`);
const token = extractTokenFromOutput(stdout);
return {
token,
userId: user.id
};
}
function extractTokenFromOutput(output: string): string {
// Extract JWT token from make command output
const lines = output.split('\n');
const tokenLine = lines.find(line => line.startsWith('eyJ'));
return tokenLine?.trim() || '';
}
Running JWT Tests¶
Unit Tests¶
# Run all JWT-related unit tests
go test ./backend/auth/service/... -v
go test ./backend/shared/http/... -v
go test ./backend/shared/middleware/... -v
# Run specific test
go test ./backend/auth/service -run TestGenerateAdminToken -v
# Run with coverage
go test ./backend/auth/service/... -cover -coverprofile=coverage.out
go tool cover -html=coverage.out
Integration Tests¶
# Run integration tests with JWT
make core
# Run specific integration test
cd backend && go test ./tests/integration -run TestAdminAPIAuth -v
2E Tests¶
# Run admin auth E2E tests
make e2e-admin-quick
# Run user auth E2E tests
make e2e-smoke
# Run specific E2E test
make e2e-single FILE=tests/admin/auth.spec.ts
ll Test Suite¶
# Run all tests (unit + integration + E2E)
make test-all
# Run foundation test suite (smoke + unit + core + e2e-smoke)
make test
Troubleshooting JWT Tests¶
Common Issues¶
Issue: "signature is invalid"
# Check JWT_SECRET consistency
echo $JWT_SECRET
docker exec saga printenv JWT_SECRET
# Regenerate tokens with correct secret
export JWT_SECRET="test-jwt-secret-for-smoke-tests-minimum-32-chars-required"
make jwt-admin EMAIL=admin@saga-test.com
Issue: "token contains an invalid number of segments"
# Verify token format (should have 3 parts: header.payload.signature)
echo "<token>" | awk -F. '{print NF-1}' # Should output: 2
# xtract and decode parts
echo "<header>" | base64 -d
echo "<payload>" | base64 -d
Issue: E2E test fails with 401
# Debug: Check token generation
TOKEN=$(make jwt-admin-token EMAIL=admin@saga-test.com)
echo "Token: $TOKEN"
# Debug: Validate token
make jwt-validate TOKEN=$TOKEN
# Debug: Test API directly
curl -v -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/admin/ping
Issue: Admin test fails with 403 (Forbidden)
# Check admin configuration
grep -A 5 "ADMIN_WALLETS\|admins:" config/auth.yaml
# Verify email is in admin list
rg "admin@saga-test.com" config/
Test Coverage Goals¶
JWT System Coverage Targets:¶
- Unit Tests: >90% coverage
- jwt_mvp.go: 95% (critical path)
- jwt_validator.go: 90%
-
token_manager.go: 85%
-
Integration Tests: >80% coverage
- Admin auth middleware: 90%
- User auth middleware: 90%
-
Protected endpoints: 85%
-
E2E Tests: >70% coverage
- Admin authentication flows: 80%
- User authentication flows: 80%
- Token expiration scenarios: 70%
Running Coverage Reports:¶
# Generate JWT service coverage
cd backend && go test ./auth/service/... -coverprofile=jwt-coverage.out
go tool cover -html=jwt-coverage.out -o jwt-coverage.html
# Generate middleware coverage
go test ./shared/middleware/... -coverprofile=middleware-coverage.out
go tool cover -html=middleware-coverage.out
Related Documentation¶
- JWT Architecture - Complete JWT system design
- Authentication Flow - Web3 auth flow
- Testing Guide - General testing practices
📋 Метаданные¶
Версия: 2.4.82
Обновлено: 2025-10-21
Статус: Published