-
Notifications
You must be signed in to change notification settings - Fork 615
[TESTING][SECURITY]: Core authentication manual test plan (JWT, Basic Auth, API tokens, email/password) #2390
Description
🔐 Testing: Core Authentication Manual Test Plan
Goal
Produce a comprehensive manual test plan for all local/internal authentication mechanisms including JWT tokens, Basic Auth, API tokens, and email/password authentication. This plan validates that authentication works correctly, securely rejects invalid credentials, and properly handles edge cases.
Why Now?
Core authentication is the foundation of the security model. Manual testing is needed to:
- Security Baseline: Validate that all authentication methods correctly identify and authenticate users before Release 1.0.0-RC1
- Credential Handling: Ensure invalid, expired, and malformed credentials are properly rejected with appropriate error messages
- Integration Validation: Verify authentication works consistently across all API endpoints and transports
- Regression Prevention: Establish baseline for automated regression testing
📖 User Stories
US-1: Security Engineer - JWT Token Validation
As a Security Engineer
I want comprehensive tests for JWT token validation
So that I can verify tokens are properly verified and invalid tokens are rejected
Acceptance Criteria:
Feature: JWT Token Validation
Scenario: Valid JWT token grants access
Given a JWT token signed with the correct secret
And the token has not expired
And the token contains valid claims (sub, exp, iat)
When I make a request with the token in Authorization header
Then the request should return status 200
And the user context should be populated from token claims
Scenario: Expired JWT token is rejected
Given a JWT token that expired 1 hour ago
When I make a request with the expired token
Then the request should return status 401
And the response should indicate "Token has expired"
Scenario: JWT with invalid signature is rejected
Given a JWT token signed with an incorrect secret
When I make a request with the invalid token
Then the request should return status 401
And the response should indicate "Invalid token signature"
Scenario: JWT with missing required claims is rejected
Given a JWT token without the "sub" claim
When I make a request with the token
Then the request should return status 401
And the response should indicate "Invalid token claims"
Scenario: JWT extracted from cookie when header missing
Given a valid JWT token stored in the "jwt_token" cookie
And no Authorization header is present
When I make a request with the cookie
Then the request should return status 200
And the user should be authenticated from the cookie token
Scenario Outline: JWT audience/issuer validation
Given REQUIRE_TOKEN_EXPIRATION is <exp_required>
And a JWT token with <token_state>
When I make a request with the token
Then the request should return status <status>
Examples:
| exp_required | token_state | status |
| true | no exp claim | 401 |
| false | no exp claim | 200 |
| true | valid exp claim | 200 |Technical Requirements:
- JWT extracted from
Authorization: Bearer <token>header - JWT extracted from
jwt_tokencookie as fallback - Signature verification using JWT_SECRET_KEY
- Configurable expiration requirement (REQUIRE_TOKEN_EXPIRATION)
- JTI claim support for revocation (REQUIRE_JTI)
US-2: Platform Admin - Basic Authentication
As a Platform Administrator
I want tests for Basic Auth username/password validation
So that I can verify admin access is properly secured
Acceptance Criteria:
Feature: Basic Authentication
Scenario: Valid Basic Auth credentials grant access
Given BASIC_AUTH_USER is "admin"
And BASIC_AUTH_PASSWORD is "secure-password-123"
When I make a request with Authorization: Basic <base64(admin:secure-password-123)>
Then the request should return status 200
And the user should be authenticated as admin
Scenario: Invalid Basic Auth password is rejected
Given BASIC_AUTH_USER is "admin"
And BASIC_AUTH_PASSWORD is "secure-password-123"
When I make a request with Authorization: Basic <base64(admin:wrong-password)>
Then the request should return status 401
And the response should indicate "Invalid credentials"
Scenario: Invalid Basic Auth username is rejected
Given BASIC_AUTH_USER is "admin"
When I make a request with Authorization: Basic <base64(wronguser:any-password)>
Then the request should return status 401
Scenario: Malformed Basic Auth header is rejected
When I make a request with Authorization: Basic not-valid-base64!!!
Then the request should return status 401
And the response should indicate "Invalid authorization header"
Scenario: Basic Auth with empty password is rejected
When I make a request with Authorization: Basic <base64(admin:)>
Then the request should return status 401
Scenario: Basic Auth header without credentials is rejected
When I make a request with Authorization: Basic
Then the request should return status 401Technical Requirements:
- Base64 decoding of credentials
- Constant-time password comparison to prevent timing attacks
- No information leakage in error messages (don't reveal if username exists)
US-3: Developer - API Token Authentication
As a Developer
I want tests for API token authentication
So that I can verify programmatic access works correctly
Acceptance Criteria:
Feature: API Token Authentication
Scenario: Valid API token in Authorization: Bearer header grants access
Given an active API token exists in the database
When I make a request with Authorization: Bearer <token-value>
Then the request should return status 200
And the user context should match the token owner
Scenario: Valid API token in Authorization Bearer header grants access
Given an active API token exists in the database
When I make a request with Authorization: Bearer <token-value>
Then the request should return status 200
Scenario: Revoked API token is rejected
Given an API token that has been revoked
When I make a request with the revoked token
Then the request should return status 401
And the response should indicate "Token revoked"
Scenario: Expired API token is rejected
Given an API token with expires_at in the past
When I make a request with the expired token
Then the request should return status 401
And the response should indicate "Token expired"
Scenario: Non-existent API token is rejected
When I make a request with Authorization: Bearer non-existent-token-12345
Then the request should return status 401
And the response should indicate "Invalid token"
Scenario: Inactive API token is rejected
Given an API token with is_active=false
When I make a request with the inactive token
Then the request should return status 401
Scenario: API token usage is logged
Given a valid API token
When I make a request with the token
Then a TokenUsageLog entry should be created
And the entry should include timestamp, endpoint, and IP addressTechnical Requirements:
- Token lookup in database by token value
- Check is_active, expires_at, and revocation status
- Log token usage for auditing
- Support both Authorization: Bearer and Authorization Bearer headers
US-4: End User - Email/Password Authentication
As an End User
I want tests for email/password login flow
So that I can verify the login process works securely
Acceptance Criteria:
Feature: Email/Password Authentication
Scenario: Valid email and password grants access
Given a user exists with email "user@example.com"
And the user's password is "ValidPass123!"
When I POST to /auth/login with email and password
Then the request should return status 200
And the response should contain a JWT access token
And the response should contain a refresh token
Scenario: Invalid password is rejected
Given a user exists with email "user@example.com"
When I POST to /auth/login with email and wrong password
Then the request should return status 401
And the response should indicate "Invalid credentials"
And an EmailAuthEvent should be logged with success=false
Scenario: Non-existent email is rejected
When I POST to /auth/login with email "nobody@example.com"
Then the request should return status 401
And the response should indicate "Invalid credentials"
And the error should NOT reveal that the email doesn't exist
Scenario: Inactive user cannot login
Given a user exists with is_active=false
When I POST to /auth/login with the user's credentials
Then the request should return status 401
And the response should indicate "Account disabled"
Scenario: Password change required redirects appropriately
Given a user with password_change_required=true
When I POST to /auth/login with valid credentials
Then the request should return status 200
And the response should include password_change_required=true
And limited access should be granted until password is changed
Scenario: Successful login logs authentication event
Given a valid user
When I POST to /auth/login with valid credentials
Then an EmailAuthEvent should be created
And the event should include client_ip and user_agent
And the event should have success=true
Scenario: Failed login attempts are tracked
Given a valid user
When I POST to /auth/login with wrong password 5 times
Then 5 EmailAuthEvent entries should be created with success=false
And the security logger should detect potential brute forceTechnical Requirements:
- Argon2id password verification
- JWT token generation on successful login
- Authentication event logging
- Password change enforcement
- Brute force detection integration
US-5: Operations - Proxy Header Authentication
As an Operations Engineer
I want tests for proxy/header-based authentication
So that I can verify authentication works behind a reverse proxy
Acceptance Criteria:
Feature: Proxy Header Authentication
Scenario: Trusted proxy header authenticates user
Given TRUST_PROXY_AUTH is true
And PROXY_USER_HEADER is "X-Authenticated-User"
When I make a request with X-Authenticated-User: admin@example.com
Then the request should be authenticated as admin@example.com
Scenario: Proxy header ignored when trust disabled
Given TRUST_PROXY_AUTH is false
When I make a request with X-Authenticated-User: admin@example.com
Then the request should return status 401
And proxy headers should be ignored
Scenario: Platform admin email grants admin access
Given TRUST_PROXY_AUTH is true
And PLATFORM_ADMIN_EMAIL is "superadmin@example.com"
When I make a request with X-Authenticated-User: superadmin@example.com
Then the user should have is_admin=true
Scenario: Empty proxy header is rejected
Given TRUST_PROXY_AUTH is true
When I make a request with X-Authenticated-User: (empty)
Then the request should return status 401
Scenario: Malformed email in proxy header is rejected
Given TRUST_PROXY_AUTH is true
When I make a request with X-Authenticated-User: not-an-email
Then the request should return status 401
And the response should indicate "Invalid user identifier"Technical Requirements:
- TRUST_PROXY_AUTH configuration toggle
- Configurable header name (PROXY_USER_HEADER)
- Email validation for proxy header value
- Platform admin email matching
US-6: Security Auditor - Authentication Failure Handling
As a Security Auditor
I want tests for authentication failure handling
So that I can verify errors don't leak sensitive information
Acceptance Criteria:
Feature: Authentication Failure Handling
Scenario: No authentication provided returns 401
Given AUTH_REQUIRED is true
When I make a request without any authentication
Then the request should return status 401
And the response should indicate "Authentication required"
Scenario: Error messages don't reveal internal details
When I make a request with an invalid JWT token
Then the response should NOT contain stack traces
And the response should NOT contain internal error messages
And the response should NOT contain file paths
Scenario: Authentication errors are logged
When authentication fails for any reason
Then the failure should be logged with:
| Field | Value |
| level | WARNING or ERROR |
| reason | failure type |
| client_ip | request IP |
| endpoint | request path |
Scenario: Multiple auth methods tried in order
Given a request with both Authorization header and Authorization: Bearer
When the request is processed
Then Bearer token should be tried first
And API key should be tried if Bearer fails
And the first successful auth should be used
Scenario: Malformed Authorization header type is rejected
When I make a request with Authorization: Digest abc123
Then the request should return status 401
And only Bearer and Basic types should be acceptedTechnical Requirements:
- Consistent error response format
- No information disclosure in errors
- Proper logging of all authentication attempts
- Clear authentication method precedence
🏗 Test Architecture
Authentication Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION DECISION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────┐
│ Incoming Request │
└────────┬─────────┘
│
┌────────▼─────────┐
│ Has Authorization│
│ Header? │
└────────┬─────────┘
│
┌──────────────────┼──────────────────┐
│ YES │ NO
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Parse Header │ │ Has Authorization: Bearer │
│ Type │ │ Header? │
└────────┬────────┘ └────────┬────────┘
│ │
┌──────────┼──────────┐ ┌──────────┼──────────┐
│ │ │ │ YES │ NO
▼ ▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌───────────┐ ┌───────────────┐
│ Bearer │ │ Basic │ │ Other │ │ API Token │ │ Has Cookie? │
│ Token │ │ Auth │ │ Type │ │ Lookup │ │ jwt_token │
└───┬────┘ └───┬────┘ └───┬────┘ └─────┬─────┘ └───────┬───────┘
│ │ │ │ │
▼ ▼ ▼ │ ┌─────────┼─────────┐
┌────────┐ ┌────────┐ ┌────────┐ │ │ YES │ NO
│ JWT │ │ User/ │ │ Reject │ │ ▼ ▼
│ Verify │ │ Pass │ │ 401 │ │ ┌───────────┐ ┌─────────────┐
└───┬────┘ │ Check │ └────────┘ │ │ JWT from │ │ Proxy Auth? │
│ └───┬────┘ │ │ Cookie │ └──────┬──────┘
│ │ │ └─────┬─────┘ │
└──────────┴──────────────────────────┴──────────┴────────────────┘
│
┌────────▼─────────┐
│ Auth Success? │
└────────┬─────────┘
│
┌──────────────────┼──────────────────┐
│ YES │ NO
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Populate │ │ Return 401 │
│ request.state │ │ Unauthorized │
│ .user │ │ │
└─────────────────┘ └─────────────────┘
JWT Token Structure
┌─────────────────────────────────────────────────────────────────────────────┐
│ JWT TOKEN CLAIMS │
└─────────────────────────────────────────────────────────────────────────────┘
Header (Algorithm & Type)
{
"alg": "HS256",
"typ": "JWT"
}
Payload (Claims)
{
"sub": "user@example.com", # Subject (user email) - REQUIRED
"exp": 1735689600, # Expiration time - REQUIRED if REQUIRE_TOKEN_EXPIRATION
"iat": 1735603200, # Issued at time
"jti": "unique-token-id", # JWT ID - REQUIRED if REQUIRE_JTI
"teams": ["team-alpha"], # Team memberships (null = admin)
"is_admin": false, # Admin flag
"permissions": ["tools.read"], # Optional permission restrictions
"environment": "production" # Environment claim for isolation
}
Signature
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
JWT_SECRET_KEY
)
Authentication Method Precedence
┌─────────────────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION METHOD PRECEDENCE │
└─────────────────────────────────────────────────────────────────────────────┘
Priority │ Method │ Header/Source │ Notes
─────────┼─────────────────────┼────────────────────────────┼─────────────────
1 │ Bearer Token (JWT) │ Authorization: Bearer xxx │ Primary method
2 │ Basic Auth │ Authorization: Basic xxx │ Admin access
3 │ API Token │ Authorization: Bearer xxx │ Programmatic
4 │ API Token │ Authorization: Bearer xxx │ Fallback lookup
5 │ Cookie Token │ Cookie: jwt_token=xxx │ Web UI sessions
6 │ Proxy Header │ X-Authenticated-User: xxx │ If TRUST_PROXY_AUTH
📋 Test Environment Setup
Prerequisites
0. Start Docker Compose Stack
# Start the stack
docker compose up -d
# Verify health
curl http://localhost:8080/health1. Configuration
# Core authentication settings
export JWT_SECRET_KEY="test-secret-key-minimum-32-characters-long"
export BASIC_AUTH_USER="admin"
export BASIC_AUTH_PASSWORD="AdminPass123!"
export AUTH_REQUIRED=true
export REQUIRE_TOKEN_EXPIRATION=true
export REQUIRE_JTI=false
# Proxy authentication (disabled by default)
export TRUST_PROXY_AUTH=false
export PROXY_USER_HEADER="X-Authenticated-User"
export PLATFORM_ADMIN_EMAIL="superadmin@example.com"2. Test Users
Create the following test users in the database:
| Password | is_admin | is_active | password_change_required | |
|---|---|---|---|---|
| active@example.com | ActivePass123! | false | true | false |
| admin@example.com | AdminPass123! | true | true | false |
| inactive@example.com | InactivePass123! | false | false | false |
| pwchange@example.com | TempPass123! | false | true | true |
# Create test users via API (requires admin token)
curl -X POST "http://localhost:8080/auth/email/admin/users" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "active@example.com",
"password": "ActivePass123!",
"is_admin": false
}'3. Test Tokens
Generate tokens for testing:
# Valid JWT token (expires in 1 hour)
export VALID_JWT=$(python -m mcpgateway.utils.create_jwt_token \
--username active@example.com \
--exp 60 \
--secret "$JWT_SECRET_KEY")
# Expired JWT token
export EXPIRED_JWT=$(python -m mcpgateway.utils.create_jwt_token \
--username active@example.com \
--exp -60 \
--secret "$JWT_SECRET_KEY")
# JWT with wrong signature
export INVALID_SIG_JWT=$(python -m mcpgateway.utils.create_jwt_token \
--username active@example.com \
--exp 60 \
--secret "wrong-secret-key-for-testing")
# Admin JWT token
export ADMIN_JWT=$(python -m mcpgateway.utils.create_jwt_token \
--username admin@example.com \
--is-admin \
--exp 60 \
--secret "$JWT_SECRET_KEY")
# Base64 encoded Basic Auth
export BASIC_AUTH_VALID=$(echo -n "admin:AdminPass123!" | base64)
export BASIC_AUTH_INVALID=$(echo -n "admin:wrongpassword" | base64)4. API Tokens
Create API tokens in the database for testing:
# Create a valid API token
curl -X POST "http://localhost:8080/tokens" \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "test-api-token",
"expires_in_days": 30
}'
# Save the returned token value as TEST_API_TOKEN🧪 Manual Test Cases
Section 1: JWT Token Authentication
| Case | Token State | Expected | HTTP Status |
|---|---|---|---|
| JWT-01 | Valid, not expired | Authenticated | 200 |
| JWT-02 | Expired | Rejected | 401 |
| JWT-03 | Invalid signature | Rejected | 401 |
| JWT-04 | Missing sub claim | Rejected | 401 |
| JWT-05 | Missing exp (required) | Rejected | 401 |
| JWT-06 | Missing exp (not required) | Authenticated | 200 |
| JWT-07 | Revoked (JTI in blacklist) | Rejected | 401 |
| JWT-08 | Malformed (not valid JWT) | Rejected | 401 |
| JWT-09 | Empty token | Rejected | 401 |
| JWT-10 | From cookie (valid) | Authenticated | 200 |
JWT-01: Valid JWT token authenticates
Preconditions:
- Valid JWT token generated with correct secret
- Token not expired
Steps:
curl -X GET "http://localhost:8080/tools" \
-H "Authorization: Bearer $VALID_JWT" \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 200 OK
- Response contains list of accessible tools
- User context populated from token claims
JWT-02: Expired JWT token rejected
Preconditions:
- JWT token with exp claim in the past
Steps:
curl -X GET "http://localhost:8080/tools" \
-H "Authorization: Bearer $EXPIRED_JWT" \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 401 Unauthorized
- Response:
{"detail": "Token has expired"}or similar - No user context populated
JWT-03: Invalid signature rejected
Preconditions:
- JWT token signed with incorrect secret
Steps:
curl -X GET "http://localhost:8080/tools" \
-H "Authorization: Bearer $INVALID_SIG_JWT" \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 401 Unauthorized
- Response indicates invalid token
- No information about the secret leaked
JWT-10: JWT from cookie authenticates
Preconditions:
- Valid JWT token
- No Authorization header
Steps:
curl -X GET "http://localhost:8080/tools" \
-H "Cookie: jwt_token=$VALID_JWT" \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 200 OK
- User authenticated from cookie token
Section 2: Basic Authentication
| Case | Credentials | Expected | HTTP Status |
|---|---|---|---|
| BASIC-01 | Valid username/password | Authenticated | 200 |
| BASIC-02 | Invalid password | Rejected | 401 |
| BASIC-03 | Invalid username | Rejected | 401 |
| BASIC-04 | Malformed base64 | Rejected | 401 |
| BASIC-05 | Empty password | Rejected | 401 |
| BASIC-06 | Empty username | Rejected | 401 |
| BASIC-07 | Missing colon separator | Rejected | 401 |
BASIC-01: Valid Basic Auth credentials
Preconditions:
- BASIC_AUTH_USER and BASIC_AUTH_PASSWORD configured
Steps:
curl -X GET "http://localhost:8080/health" \
-H "Authorization: Basic $BASIC_AUTH_VALID" \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 200 OK
- User authenticated as admin
BASIC-02: Invalid password rejected
Preconditions:
- BASIC_AUTH_USER configured
Steps:
curl -X GET "http://localhost:8080/health" \
-H "Authorization: Basic $BASIC_AUTH_INVALID" \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 401 Unauthorized
- Response does not reveal that password was wrong (vs username)
Section 3: API Token Authentication
| Case | Token State | Expected | HTTP Status |
|---|---|---|---|
| API-01 | Valid, active | Authenticated | 200 |
| API-02 | Revoked | Rejected | 401 |
| API-03 | Expired | Rejected | 401 |
| API-04 | Non-existent | Rejected | 401 |
| API-05 | Inactive (is_active=false) | Rejected | 401 |
| API-06 | Via Authorization: Bearer header | Authenticated | 200 |
| API-07 | Via Authorization Bearer | Authenticated | 200 |
API-01: Valid API token authenticates
Preconditions:
- Active API token exists in database
Steps:
curl -X GET "http://localhost:8080/tools" \
-H "Authorization: Bearer $TEST_API_TOKEN" \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 200 OK
- User context matches token owner
- TokenUsageLog entry created
Section 4: Email/Password Authentication
| Case | Credentials | Expected | HTTP Status |
|---|---|---|---|
| EMAIL-01 | Valid email/password | JWT returned | 200 |
| EMAIL-02 | Invalid password | Rejected | 401 |
| EMAIL-03 | Non-existent email | Rejected | 401 |
| EMAIL-04 | Inactive user | Rejected | 401 |
| EMAIL-05 | Password change required | Limited access | 200 |
EMAIL-01: Valid login returns JWT
Preconditions:
- User active@example.com exists with known password
Steps:
curl -X POST "http://localhost:8080/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "active@example.com",
"password": "ActivePass123!"
}' \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 200 OK
- Response contains jwt_token (JWT)
- Response may contain refresh_token
- EmailAuthEvent logged with success=true
EMAIL-03: Non-existent email rejected without revealing
Preconditions:
- Email does not exist in database
Steps:
curl -X POST "http://localhost:8080/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "nobody@example.com",
"password": "AnyPassword123!"
}' \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 401 Unauthorized
- Response:
{"detail": "Invalid credentials"} - Error message does NOT reveal that email doesn't exist
- Same response as invalid password (timing-safe)
Section 5: Proxy Header Authentication
| Case | Configuration | Header | Expected | HTTP Status |
|---|---|---|---|---|
| PROXY-01 | TRUST=true | Valid email | Authenticated | 200 |
| PROXY-02 | TRUST=false | Valid email | Ignored, 401 | 401 |
| PROXY-03 | TRUST=true | Platform admin | Admin access | 200 |
| PROXY-04 | TRUST=true | Empty header | Rejected | 401 |
| PROXY-05 | TRUST=true | Invalid email | Rejected | 401 |
PROXY-01: Trusted proxy header authenticates
Preconditions:
- TRUST_PROXY_AUTH=true
- PROXY_USER_HEADER=X-Authenticated-User
Steps:
curl -X GET "http://localhost:8080/tools" \
-H "X-Authenticated-User: admin@example.com" \
-w "\nHTTP Status: %{http_code}\n"Expected Result:
- HTTP 200 OK
- User authenticated as admin@example.com
Section 6: Authentication Edge Cases
| Case | Scenario | Expected | HTTP Status |
|---|---|---|---|
| EDGE-01 | No auth, AUTH_REQUIRED=true | Rejected | 401 |
| EDGE-02 | No auth, AUTH_REQUIRED=false | Anonymous access | 200 |
| EDGE-03 | Multiple auth headers | First valid wins | 200 |
| EDGE-04 | Unsupported auth type | Rejected | 401 |
| EDGE-05 | Null/undefined token | Rejected | 401 |
🧾 Complete Test Matrix
JWT Token Validation
| # | Claim State | Secret | Expired | Expected |
|---|---|---|---|---|
| 1 | Valid sub, valid exp | Correct | No | ALLOW |
| 2 | Valid sub, valid exp | Correct | Yes | DENY |
| 3 | Valid sub, valid exp | Wrong | No | DENY |
| 4 | Missing sub | Correct | No | DENY |
| 5 | Valid sub, no exp | Correct | N/A | ALLOW (if not required) |
| 6 | Valid sub, no exp | Correct | N/A | DENY (if required) |
| 7 | Valid, JTI revoked | Correct | No | DENY |
| 8 | Malformed JWT | N/A | N/A | DENY |
Authentication Method Priority
| # | Bearer | Basic | Authorization: Bearer | Cookie | Proxy | Expected Auth |
|---|---|---|---|---|---|---|
| 1 | Valid | - | - | - | - | Bearer JWT |
| 2 | Invalid | Valid | - | - | - | Basic Auth |
| 3 | - | - | Valid | - | - | API Token |
| 4 | - | - | - | Valid | - | Cookie JWT |
| 5 | - | - | - | - | Valid | Proxy Header |
| 6 | - | - | - | - | - | 401 (if required) |
Error Response Validation
| # | Scenario | Response Must NOT Contain |
|---|---|---|
| 1 | Invalid JWT | Stack trace, file paths |
| 2 | Wrong password | "password incorrect" (vs "user not found") |
| 3 | Invalid signature | Actual secret or key info |
| 4 | Expired token | Internal timestamps |
| 5 | Any auth failure | Database error details |
✅ Success Criteria
- All JWT validation scenarios tested (valid, expired, invalid signature, missing claims)
- Basic Auth tested with valid/invalid credentials
- API token authentication tested across all states
- Email/password login flow validated
- Proxy header authentication tested when enabled/disabled
- Error messages verified to not leak sensitive information
- Authentication events properly logged
- Cookie-based JWT authentication working
- All test cases documented with curl examples
- Edge cases covered (no auth, multiple auth headers, malformed input)
🏁 Definition of Done
- Manual test plan reviewed by Security team
- All test users and tokens created in test environment
- All test cases executed with documented results
- JWT validation covers all claim scenarios
- Basic Auth timing-safe password comparison verified
- API token lifecycle (active, revoked, expired) tested
- Email/password flow including password change tested
- Proxy auth tested in both enabled and disabled modes
- Error responses validated for information disclosure
- Authentication logging verified (success and failure)
- Test results documented and any failures triaged
- Plan linked to automated regression suite (#TBD)
🔗 References
mcpgateway/auth.py- Core authentication logicmcpgateway/routers/auth.py- Auth endpointsmcpgateway/services/email_auth_service.py- Email/password servicemcpgateway/config.py- Authentication configurationmcpgateway/middleware/rbac.py- RBAC middleware
📝 Notes
🔹 Timing Attacks: Password comparison must use constant-time comparison to prevent timing attacks that could reveal valid usernames.
🔹 Error Messages: Authentication errors should be generic ("Invalid credentials") rather than specific ("Password incorrect" vs "User not found") to prevent user enumeration.
🔹 Token Precedence: When multiple authentication methods are present, Bearer token takes precedence over API key, which takes precedence over cookies.
🔹 Credential Caching: JWT verification may be cached for performance. Tests should account for cache TTL when testing token revocation.