Skip to content

Conversation

@5Amogh
Copy link
Member

@5Amogh 5Amogh commented Nov 12, 2025

📋 Description

JIRA ID: AMM-1927

Overview

This summarizes the granular CORS security implementation to fix CWE-942 vulnerability (Insecure HTTP Method). The implementation enforces strict origin validation, endpoint-specific HTTP method control, and eliminates wildcard CORS headers.

Security Vulnerability Addressed

CWE-942: Insecure HTTP Method

  • Issue: OPTIONS requests were accepted from any origin without validation, allowing attackers to discover server capabilities
  • Status: ✅ RESOLVED

Changes Made

1. JwtUserIdValidationFilter.java

File: src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java

Changes:

  • ✅ Added strict origin validation before OPTIONS handling
  • ✅ Implemented endpoint-specific HTTP method control via ENDPOINT_ALLOWED_METHODS map
  • ✅ Configured DELETE endpoint: /dynamicForm/delete/*/field
  • ✅ Default allowed methods for unconfigured endpoints: GET, POST, OPTIONS only
  • ✅ Added comprehensive security logging with indicators (✅, 🚫)
  • ✅ CORS headers only added for validated origins (never wildcard)
  • ✅ OPTIONS requests without Origin header are blocked (403 Forbidden)
  • ✅ Method validation returns 405 for disallowed methods

Key Features:

  • Origin validation happens before OPTIONS handling
  • Unauthorized origins are blocked immediately with 403 Forbidden
  • Endpoint-specific method restrictions enforced
  • Wildcard pattern support for path matching (e.g., /dynamicForm/delete/*/field)

2. CorsConfig.java

File: src/main/java/com/iemr/common/config/CorsConfig.java

Changes:

  • ✅ Restricted allowedHeaders from * to specific headers: Authorization, Content-Type, Accept, Jwttoken
  • ✅ Added comprehensive documentation explaining two-layer security approach
  • ✅ Kept allowedMethods as GET, POST, PUT, DELETE, OPTIONS (Spring level - actual enforcement in filter)

Rationale: Spring CORS config is permissive at framework level, but JwtUserIdValidationFilter enforces granular restrictions. This two-layer approach allows Spring to handle CORS preflight requests while the filter enforces security policies.

3. HTTPRequestInterceptor.java

File: src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java

Changes:

  • ✅ Removed wildcard CORS header (Access-Control-Allow-Origin: *)
  • ✅ Added origin validation for error responses
  • ✅ Injected @Value("${cors.allowed-origins}") for origin validation
  • ✅ Added isOriginAllowed() helper method (same logic as filter for consistency)
  • ✅ Only adds CORS headers for validated origins, even in error responses
  • ✅ Added warning log when origin is not allowed in error responses

Impact: Error responses (401 Unauthorized) now include CORS headers only for allowed origins, preventing frontend CORS errors while maintaining security.

Endpoints Requiring PUT/DELETE

DELETE Endpoints

File Line Method Name Endpoint Path HTTP Method
DynamicFormController.java 73 deleteField /dynamicForm/delete/{fieldId}/field DELETE

Configuration:

ENDPOINT_ALLOWED_METHODS.put("/dynamicForm/delete/*/field", Set.of("GET", "POST", "DELETE"));

PUT Endpoints

None found - No PUT endpoints exist in the codebase.

Security Improvements

Before Implementation

❌ OPTIONS requests from unauthorized origins → 200 OK (Information disclosure)
❌ All HTTP methods allowed on all endpoints
❌ Wildcard CORS headers: Access-Control-Allow-Origin: *
❌ No endpoint-specific method restrictions

After Implementation

✅ OPTIONS requests from unauthorized origins → 403 Forbidden (Blocked)
✅ Endpoint-specific method control enforced
✅ Origin-specific CORS headers (never wildcard)
✅ Default deny approach: GET, POST, OPTIONS only
✅ Unauthorized origins blocked immediately

Test Cases Covered

  1. ✅ OPTIONS from unauthorized origin → 403 Forbidden
  2. ✅ OPTIONS from allowed origin → 200 OK
  3. ✅ OPTIONS without Origin header → 403 Forbidden
  4. ✅ DELETE on unconfigured endpoint → 405 Method Not Allowed
  5. ✅ DELETE on configured endpoint → 200/401 (depends on JWT)
  6. ✅ PUT on any endpoint → 405 Method Not Allowed
  7. ✅ TRACE method → 403/405 (blocked)
  8. ✅ Verify no wildcard CORS headers in responses
  9. ✅ GET from allowed origin → Works with CORS headers
  10. ✅ POST from allowed origin → Works with CORS headers

Manual Testing

Test unauthorized origin:

curl -X OPTIONS http://localhost:8083/user/userAuthenticate \
  -H "Origin: https://evil.com" \
  -v
# Expected: 403 Forbidden

Test allowed origin:

curl -X OPTIONS http://localhost:8083/user/userAuthenticate \
  -H "Origin: http://localhost:3000" \
  -v
# Expected: 200 OK with CORS headers

Test DELETE on unconfigured endpoint:

curl -X DELETE http://localhost:8083/user/userAuthenticate \
  -H "Origin: http://localhost:3000" \
  -v
# Expected: 405 Method Not Allowed

Test DELETE on configured endpoint:

curl -X DELETE http://localhost:8083/dynamicForm/delete/123/field \
  -H "Origin: http://localhost:3000" \
  -H "Authorization: Bearer <token>" \
  -v
# Expected: 200 OK or 401 Unauthorized (depends on JWT)

Environment Variables

Production

CORS_ALLOWED_ORIGINS=https://abc.xyz.org,https://app.xyz.org

Development

CORS_ALLOWED_ORIGINS=http://localhost:*

Docker

CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}

Security Posture

Security Aspect Status
CWE-942 Vulnerability RESOLVED
Unauthorized origins blocked ENFORCED
Information disclosure eliminated FIXED
Endpoint method restrictions ENFORCED
Wildcard CORS removed ELIMINATED
Origin validation STRICT

Backward Compatibility

Breaking Changes

  1. OPTIONS from unauthorized origins: Now returns 403 Forbidden (previously 200 OK)
    • Impact: This is intentional and required for security
    • Mitigation: Ensure frontend applications use correct cors.allowed-origins values

Non-Breaking Changes

  1. Error response CORS headers: Now origin-specific (previously wildcard)

    • Impact: Frontend must handle CORS for allowed origins only
    • Mitigation: Error responses still include CORS headers for validated origins
  2. Method restrictions: Most endpoints default to GET, POST, OPTIONS only

    • Impact: Minimal - only 1 DELETE endpoint exists and is explicitly configured
    • Mitigation: New endpoints requiring PUT/DELETE must be added to ENDPOINT_ALLOWED_METHODS

Adding New Endpoints with PUT/DELETE

To add a new endpoint that requires PUT or DELETE:

  1. Update JwtUserIdValidationFilter.java:
    static {
        // Existing configuration...
        
        // Add new endpoint
        Set<String> newEndpointMethods = new HashSet<>();
        newEndpointMethods.add("GET");
        newEndpointMethods.add("POST");
        newEndpointMethods.add("PUT"); // or DELETE
        ENDPOINT_ALLOWED_METHODS.put("/api/new-endpoint/*", newEndpointMethods);
    }

Rollback Plan

If issues arise, changes are isolated to 3 files:

  1. JwtUserIdValidationFilter.java
  2. CorsConfig.java
  3. HTTPRequestInterceptor.java

Revert these files to their previous versions to rollback.

Monitoring

Enhanced logging has been added to help identify issues:

  • Successful origin validation: Origin Validated | Origin: {origin} | Method: {method} | URI: {uri}
  • Blocked requests: BLOCKED - Unauthorized Origin | Origin: {origin} | Method: {method} | URI: {uri}
  • Method violations: BLOCKED - Method Not Allowed | Method: {method} | URI: {uri}

Monitor application logs for these indicators to track security enforcement.

Summary

The granular CORS security implementation successfully addresses the CWE-942 vulnerability by:

  1. Blocking unauthorized origins immediately (403 Forbidden)
  2. Enforcing endpoint-specific HTTP method restrictions
  3. Eliminating wildcard CORS headers
  4. Implementing default deny approach (GET, POST, OPTIONS only)
  5. Adding comprehensive security logging

Summary by CodeRabbit

  • Bug Fixes & Security
    • Implemented stricter cross-origin request validation with explicit header whitelisting.
    • Added endpoint-specific HTTP method validation to enforce allowed request types.
    • Enhanced origin verification to prevent unauthorized cross-origin requests.
    • Improved preflight request handling for better cross-origin compatibility.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 12, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Tighten CORS security across the application by restricting allowed headers to explicit values in CorsConfig, implementing endpoint-specific HTTP method validation in JwtUserIdValidationFilter, and adding origin-aware CORS filtering in HTTPRequestInterceptor.

Changes

Cohort / File(s) Change Summary
CORS Configuration
src/main/java/com/iemr/common/config/CorsConfig.java
Replaced wildcard allowedHeaders with explicit list: "Authorization", "Content-Type", "Accept", "Jwttoken". Added JavaDoc describing two-layer CORS/security handling.
JWT Filter Enhancement
src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java
Added endpoint-aware HTTP method validation with DEFAULT\_ALLOWED\_METHODS and ENDPOINT\_ALLOWED\_METHODS map supporting wildcards. Enhanced doFilter with strict origin validation, preflight OPTIONS handling, dynamic allowed methods resolution, and improved token retrieval with cookie/header fallback logic.
HTTP Interceptor
src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java
Introduced configurable allowedOrigins property via @Value("${cors.allowed-origins}") and isOriginAllowed helper method. Changed CORS header setting to conditional based on origin validation; logs warnings for disallowed origins.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant JwtFilter as JwtUserIdValidationFilter
    participant CorsConfig
    participant App as Application<br/>Endpoint

    Client->>JwtFilter: HTTP Request
    Note over JwtFilter: Extract origin and<br/>HTTP method
    
    alt Origin Not Allowed
        JwtFilter-->>Client: 403 Forbidden
        Note right of JwtFilter: Origin validation failed
    else OPTIONS Preflight
        JwtFilter->>JwtFilter: Resolve allowed methods<br/>for endpoint
        JwtFilter-->>Client: 200 OK + CORS Headers
        Note right of JwtFilter: Preflight response,<br/>skip JWT validation
    else Origin Allowed & Method Valid
        JwtFilter->>JwtFilter: Validate JWT token
        alt JWT Valid
            JwtFilter->>App: Forward request
            App-->>JwtFilter: Response
            JwtFilter-->>Client: Response + CORS Headers
        else JWT Invalid
            JwtFilter-->>Client: 401 Unauthorized
        end
    else Origin Allowed & Method Invalid
        JwtFilter-->>Client: 405 Method Not Allowed
        Note right of JwtFilter: Method not in<br/>endpoint allowlist
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–25 minutes

  • Attention areas:
    • Origin validation logic in JwtUserIdValidationFilter and HTTPRequestInterceptor for consistency and security implications
    • Wildcard pattern matching in ENDPOINT\_ALLOWED\_METHODS map and token retrieval fallback logic
    • Configuration property bindings and their defaults (${cors.allowed-origins})
    • OPTIONS preflight handling and JWT validation bypass flow

Possibly related PRs

Suggested reviewers

  • devikasuresh20
  • drtechie
  • vishwab1

Poem

🐰 A rabbit's ode to tighter security:
Headers locked down, origins now see,
No wildcards roaming wild and free,
Each method checked with certainty,
CORS gates gently guard our spree! 🔐

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main objective: fixing an HTTP OPTIONS vulnerability through CORS controls by implementing granular origin and method validation across three security-critical files.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@5Amogh
Copy link
Member Author

5Amogh commented Nov 12, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 12, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java (1)

198-219: Extract duplicated origin validation to shared utility.

The isOriginAllowed method is duplicated identically in JwtUserIdValidationFilter (lines 189-206). Consider extracting this logic to a shared utility class (e.g., CorsUtils or OriginValidator) to maintain DRY principles and ensure consistent origin validation behavior across the application.

Example shared utility:

public class OriginValidator {
    public static boolean isOriginAllowed(String origin, String allowedOrigins) {
        if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) {
            return false;
        }
        
        return Arrays.stream(allowedOrigins.split(","))
                .map(String::trim)
                .anyMatch(pattern -> {
                    String regex = pattern
                            .replace(".", "\\.")
                            .replace("*", ".*")
                            .replace("http://localhost:.*", "http://localhost:\\d+");
                    return origin.matches(regex);
                });
    }
}

Then both classes can call: OriginValidator.isOriginAllowed(origin, allowedOrigins)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddee5bd and e41a47e.

📒 Files selected for processing (3)
  • src/main/java/com/iemr/common/config/CorsConfig.java (2 hunks)
  • src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java (5 hunks)
  • src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: sandipkarmakar3
Repo: PSMRI/Common-API PR: 162
File: src/main/java/com/iemr/common/utils/CookieUtil.java:40-47
Timestamp: 2025-02-21T07:42:36.497Z
Learning: In the Common-API project's CookieUtil class, JWT cookies are configured with SameSite=None to support cross-origin requests, which is required for the project's CORS functionality.
📚 Learning: 2025-02-21T07:42:36.497Z
Learnt from: sandipkarmakar3
Repo: PSMRI/Common-API PR: 162
File: src/main/java/com/iemr/common/utils/CookieUtil.java:40-47
Timestamp: 2025-02-21T07:42:36.497Z
Learning: In the Common-API project's CookieUtil class, JWT cookies are configured with SameSite=None to support cross-origin requests, which is required for the project's CORS functionality.

Applied to files:

  • src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_check_coverage
🔇 Additional comments (7)
src/main/java/com/iemr/common/config/CorsConfig.java (1)

15-27: Good documentation of two-layer security approach.

The JavaDoc clearly explains the division of responsibility between Spring's CORS configuration and the filter-based enforcement, which will help future maintainers understand the design.

src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java (2)

50-51: LGTM!

The origin configuration injection aligns with the filter's approach and enables consistent origin validation across authentication layers.


148-155: LGTM - Secure error response handling.

The conditional CORS header injection ensures that only validated origins receive CORS headers in error responses, preventing information leakage and maintaining consistent security posture.

src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java (4)

88-100: LGTM - Proper endpoint-specific method validation.

The method validation correctly returns 405 for disallowed methods and includes helpful logging. The validation order (origin → method → JWT) follows security best practices.


103-111: LGTM - Dynamic CORS headers per endpoint.

The endpoint-specific Access-Control-Allow-Methods and origin-specific Access-Control-Allow-Origin provide granular control. Note: The serverAuthorization headers in line 106-107 need to be added to CorsConfig.java (flagged in that file's review).


208-236: DELETE endpoint is properly configured—no action required.

The existing DELETE endpoint at /dynamicForm/delete/*/field is already configured in ENDPOINT_ALLOWED_METHODS (lines 38-42) with DELETE method explicitly allowed. No PUT endpoints were found in the codebase. The default method restrictions will not break existing functionality.


68-73: Verify that this strict Origin requirement aligns with your application's usage patterns.

The code explicitly documents this as "STRICT Origin Validation" (line 67 comment). The implementation deliberately requires the Origin header for OPTIONS requests while making it optional for non-OPTIONS requests—this asymmetry appears intentional for CORS preflight handling. However, without unit tests or documented usage patterns, verify that this strict requirement does not break legitimate same-origin OPTIONS requests from testing tools, health checks, or other internal services in your environment.

@5Amogh 5Amogh requested a review from drtechie November 13, 2025 05:01
String regex = pattern
.replace(".", "\\.")
.replace("*", ".*")
.replace("http://localhost:.*", "http://localhost:\\d+"); // special case for wildcard port
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't get why localhost is defined in code. This ideally should be present in the env variable.
For Dev and UAT, we might support localhost as an allowed origin for UI devs.
But in production that shouldn't be allowed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, have removed it now, thansk for pointing it out.. the code infact did not have an effect if we're restricting even specific ports from the env as tested via postman

@5Amogh 5Amogh requested a review from vanitha1822 November 13, 2025 05:07
@sonarqubecloud
Copy link

@5Amogh
Copy link
Member Author

5Amogh commented Nov 13, 2025

Updated code doc based on latest changes based on reviews

Overview

This PR addresses critical CORS security vulnerabilities (CWE-942: Insecure HTTP Method) and simplifies HTTP method handling by removing unnecessary endpoint-specific restrictions. The implementation now follows security best practices with environment-based configuration and proper separation of concerns.


🔒 Security Vulnerabilities Addressed

CWE-942: Insecure HTTP Method

  • Issue: Overly permissive CORS configuration with wildcard headers and hardcoded localhost access
  • Risk Level: High
  • Impact: Potential unauthorized cross-origin requests, production environment exposure

🔧 Changes Made

1. JwtUserIdValidationFilter.java

File: src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java

Security Fixes:

  • Removed hardcoded localhost logic - Now environment-controlled only
  • Changed mutable HashSet to immutable Set.of() - Prevents runtime tampering
  • Added serverAuthorization header variants to CORS headers

Simplifications:

  • Removed ENDPOINT_ALLOWED_METHODS map - Eliminated endpoint-specific configuration
  • Removed method validation logic - Authorization now handled at endpoint level
  • Removed getAllowedMethodsForEndpoint() helper method - No longer needed
  • Simplified OPTIONS preflight handling - Now returns all standard HTTP methods
  • Added PATCH method support - Aligns with modern REST API standards

Key Changes:

// BEFORE: Endpoint-specific method restrictions
ENDPOINT_ALLOWED_METHODS.put("/dynamicForm/delete/*/field", dynamicFormMethods);

// AFTER: Universal method support
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
// BEFORE: Hardcoded localhost (security risk)
.replace("http://localhost:.*", "http://localhost:\\d+");

// AFTER: Environment-controlled (secure)
// Removed hardcoded localhost handling

2. CorsConfig.java

File: src/main/java/com/iemr/common/config/CorsConfig.java

Changes:

  • Restricted allowedHeaders from wildcard * to explicit list:
    • Authorization, Content-Type, Accept, Jwttoken
    • serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization
  • Added PATCH method to allowedMethods
  • Maintained allowedOrigins from environment variable

Before:

.allowedHeaders("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")

After:

.allowedHeaders("Authorization", "Content-Type", "Accept", "Jwttoken",
    "serverAuthorization", "ServerAuthorization", "serverauthorization", "Serverauthorization")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")

Rationale: Spring CORS configuration provides framework-level defaults, but JwtUserIdValidationFilter enforces security policies with origin validation and JWT verification.


3. HTTPRequestInterceptor.java

File: src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java

Changes:

  • Removed hardcoded localhost logic - Now environment-controlled
  • Added origin validation using @Value("${cors.allowed-origins}")
  • Updated OPTIONS response to include all standard HTTP methods
  • Added origin-aware CORS filtering for error responses

Key Features:

  • Error responses (401 Unauthorized) now include CORS headers only for allowed origins
  • Prevents frontend CORS errors while maintaining security
  • Consistent origin validation with JWT filter

🔐 Security Improvements

Before Implementation

Issue Risk
❌ Wildcard allowedHeaders: * Accepts any header, potential for header injection
❌ Hardcoded localhost in production Allows local attacks in production environment
❌ Inconsistent header configuration Preflight requests fail for legitimate headers
❌ Method restrictions at wrong layer Authorization logic in CORS filter (incorrect separation)

After Implementation

Improvement Benefit
✅ Explicit header whitelist Only necessary headers allowed
✅ Environment-controlled localhost Localhost only in dev/UAT, blocked in production
✅ Consistent header support All required headers explicitly configured
✅ Method authorization at endpoint level Proper separation of concerns
✅ Immutable security configuration Prevents runtime tampering
✅ Universal method support No maintenance overhead for new endpoints

🌍 Environment Configuration

Required Environment Variable

CORS_ALLOWED_ORIGINS=<comma-separated-list-of-origins>

Environment-Specific Configuration

Development

CORS_ALLOWED_ORIGINS=http://localhost:*,https://dev.example.com
  • ✅ Supports UI developers using localhost with any port
  • ✅ Allows development domain

UAT

CORS_ALLOWED_ORIGINS=http://localhost:*,https://uat.example.com
  • ✅ Supports testing with localhost
  • ✅ Allows UAT domain

Production

CORS_ALLOWED_ORIGINS=https://prod.example.com
  • NO localhost - Critical security requirement
  • ✅ Only production domains allowed

Wildcard Pattern Support

The implementation supports wildcard patterns in origins:

  • http://localhost:* → Matches any port (e.g., http://localhost:3000, http://localhost:8080)
  • https://*.example.com → Matches subdomains (e.g., https://app.example.com, https://api.example.com)

📊 Architecture Changes

CORS Security Layers

┌─────────────────────────────────────────────────────────────┐
│  Browser (Preflight OPTIONS Request)                         │
└───────────────────────┬─────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│  Layer 1: Spring CORS Configuration (CorsConfig.java)        │
│  - Framework-level CORS processing                           │
│  - Allows: GET, POST, PUT, PATCH, DELETE, OPTIONS            │
│  - Explicit header whitelist                                 │
└───────────────────────┬─────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│  Layer 2: JwtUserIdValidationFilter                          │
│  - Origin validation (environment-controlled)                │
│  - JWT token validation                                      │
│  - Method-agnostic (all standard methods allowed)            │
└───────────────────────┬─────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│  Layer 3: HTTPRequestInterceptor                             │
│  - Origin validation for error responses                     │
│  - Consistent CORS header injection                          │
└───────────────────────┬─────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│  Layer 4: Spring Security + Endpoint Annotations             │
│  - Method-level authorization (@PreAuthorize, etc.)          │
│  - Role-based access control                                 │
│  - Business logic validation                                 │
└─────────────────────────────────────────────────────────────┘

Separation of Concerns

  • CORS Layer: Browser security (origin validation, preflight handling)
  • Authentication Layer: JWT validation, user identity
  • Authorization Layer: Method permissions, role-based access, business rules

🚀 Benefits

For Development Teams

  • Zero configuration needed for new endpoints with PUT/PATCH/DELETE
  • No filter updates required when adding REST endpoints
  • Consistent behavior across all endpoints
  • Standard REST support including PATCH method

For Security

  • Environment-specific origin control (localhost only in dev/UAT)
  • Immutable security configuration (cannot be modified at runtime)
  • Explicit header whitelist (no wildcard exposure)
  • Proper separation of concerns (CORS ≠ Authorization)

For Operations

  • Single environment variable controls all origin access
  • Clear security boundaries between environments
  • Production-safe by default (no localhost in prod config)
  • Reduced maintenance overhead (no endpoint-specific config)

🧪 Testing

Manual Testing Scenarios

Test 1: Unauthorized Origin

curl -X POST https://api.example.com/endpoint \
  -H "Origin: https://malicious.com" \
  -H "Content-Type: application/json"

Expected: 403 Forbidden - Origin blocked

Test 2: Allowed Origin

curl -X POST https://api.example.com/endpoint \
  -H "Origin: https://prod.example.com" \
  -H "Authorization: Bearer <token>"

Expected: 200 OK - Request processed

Test 3: OPTIONS Preflight

curl -X OPTIONS https://api.example.com/endpoint \
  -H "Origin: https://prod.example.com" \
  -H "Access-Control-Request-Method: PUT"

Expected:

200 OK
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Origin: https://prod.example.com

Test 4: Localhost in Dev (Allowed)

# With CORS_ALLOWED_ORIGINS=http://localhost:*
curl -X GET https://dev.example.com/endpoint \
  -H "Origin: http://localhost:3000"

Expected: 200 OK - Localhost allowed in dev

Test 5: Localhost in Production (Blocked)

# With CORS_ALLOWED_ORIGINS=https://prod.example.com (no localhost)
curl -X GET https://api.example.com/endpoint \
  -H "Origin: http://localhost:3000"

Expected: 403 Forbidden - Localhost blocked in production


📝 Migration Notes

⚠️ Breaking Changes

  1. Localhost access now environment-controlled

    • Impact: If production accidentally had CORS_ALLOWED_ORIGINS with localhost, it will continue to work (but should be removed)
    • Action: Verify production environment variable does NOT include localhost
  2. Endpoint-specific method restrictions removed

    • Impact: All endpoints now accept all standard HTTP methods at CORS layer
    • Action: Ensure endpoint-level security (Spring Security, @PreAuthorize) is properly configured

✅ Non-Breaking Changes

  • Existing endpoints continue to work as before
  • Error response CORS headers now origin-specific (previously wildcard)
  • Added PATCH method support (new capability)

🔍 Code Review Checklist

  • All hardcoded localhost references removed
  • Environment variable cors.allowed-origins properly configured in all environments
  • Production environment does NOT include localhost in CORS_ALLOWED_ORIGINS
  • ENDPOINT_ALLOWED_METHODS map completely removed
  • All OPTIONS responses return full method list
  • Headers explicitly whitelisted (no wildcards)
  • Immutable Set.of() used instead of mutable collections
  • Both JwtUserIdValidationFilter and HTTPRequestInterceptor updated consistently
  • Code compiles without errors
  • No references to deleted helper methods remain

🎯 Summary

This PR transforms the CORS implementation from an overly restrictive, maintenance-heavy configuration to a secure, environment-aware, and developer-friendly approach. By removing endpoint-specific restrictions and fixing critical security vulnerabilities, we achieve:

  1. Better Security: Environment-controlled origins, no hardcoded localhost, immutable configuration
  2. Simpler Code: No endpoint-specific configuration, reduced maintenance
  3. Proper Architecture: CORS handles browser security, endpoints handle authorization
  4. Standard Compliance: Full REST API support including PATCH method

Result: A production-safe, maintainable CORS implementation that follows security best practices and reduces operational overhead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants