CORS on App Engine

Google App Engine supports multiple runtime environments. Below are modern, secure CORS implementations for Python, Java, and Go.

⚠️ Security Warning: Using Access-Control-Allow-Origin: * allows any website to access your resources. Always specify exact origins in production.

Python (App Engine Standard, Python 3.9+)

Using Flask-CORS (Recommended)

Install the Flask-CORS library:

pip install flask flask-cors

Configure CORS in your Flask application:

# main.py
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# Secure CORS configuration
CORS(app, origins=[
    'https://example.com',
    'https://app.example.com'
], supports_credentials=True)

@app.route('/api/data')
def get_data():
    return {'data': 'value'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Manual Implementation

For more control, implement CORS manually:

# main.py
from flask import Flask, request

app = Flask(__name__)

ALLOWED_ORIGINS = [
    'https://example.com',
    'https://app.example.com'
]

@app.before_request
def handle_cors():
    origin = request.headers.get('Origin', '')

    if origin in ALLOWED_ORIGINS:
        @app.after_request
        def add_cors_headers(response):
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
            response.headers['Vary'] = 'Origin'
            return response

    if request.method == 'OPTIONS':
        response = app.make_response('')
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
        response.headers['Access-Control-Max-Age'] = '86400'
        return response, 204

@app.route('/api/data')
def get_data():
    return {'data': 'value'}

app.yaml:

runtime: python39

handlers:
- url: /.*
  script: auto

Go (App Engine Standard, Go 1.18+)

Implement CORS using middleware:

// main.go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

var allowedOrigins = []string{
    "https://example.com",
    "https://app.example.com",
}

func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        origin := r.Header.Get("Origin")

        // Validate origin
        for _, allowed := range allowedOrigins {
            if origin == allowed {
                w.Header().Set("Access-Control-Allow-Origin", origin)
                w.Header().Set("Access-Control-Allow-Credentials", "true")
                w.Header().Set("Vary", "Origin")
                break
            }
        }

        // Handle preflight
        if r.Method == "OPTIONS" {
            w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
            w.Header().Set("Access-Control-Max-Age", "86400")
            w.WriteHeader(http.StatusNoContent)
            return
        }

        next(w, r)
    }
}

func dataHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "message": "CORS enabled",
    })
}

func main() {
    http.HandleFunc("/api/data", corsMiddleware(dataHandler))

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    fmt.Printf("Server listening on port %s\n", port)
    http.ListenAndServe(":"+port, nil)
}

app.yaml:

runtime: go118

Java (App Engine Standard, Java 11+)

Using Spring Boot

// Application.java
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                    .allowedOrigins("https://example.com", "https://app.example.com")
                    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowedHeaders("Content-Type", "Authorization")
                    .allowCredentials(true)
                    .maxAge(86400);
            }
        };
    }
}

Using Servlet Filter

@WebFilter(urlPatterns = {"/api/*"})
public class CorsFilter implements Filter {
    private static final String[] ALLOWED_ORIGINS = {
        "https://example.com",
        "https://app.example.com"
    };

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String origin = request.getHeader("Origin");

        // Validate origin
        if (origin != null && Arrays.asList(ALLOWED_ORIGINS).contains(origin)) {
            response.addHeader("Access-Control-Allow-Origin", origin);
            response.addHeader("Access-Control-Allow-Credentials", "true");
            response.addHeader("Vary", "Origin");
        }

        // Handle preflight
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
            response.addHeader("Access-Control-Max-Age", "86400");
            response.setStatus(HttpServletResponse.SC_NO_CONTENT);
            return;
        }

        chain.doFilter(req, res);
    }
}

Environment Configuration

For production deployments, use environment variables to manage allowed origins:

# app.yaml
env_variables:
  CORS_ALLOWED_ORIGINS: "https://example.com,https://app.example.com"

Access in Python:

import os
origins = os.environ.get('CORS_ALLOWED_ORIGINS', '').split(',')

Access in Go:

import (
    "os"
    "strings"
)

origins := strings.Split(os.Getenv("CORS_ALLOWED_ORIGINS"), ",")

Testing Your CORS Configuration

For comprehensive testing instructions including curl commands, browser DevTools usage, and troubleshooting common CORS errors, see the CORS Testing Guide.

Additional Resources

Who’s behind this

Monsur Hossain and Michael Hausenblas

Contribute

The content on this site stays fresh thanks to help from users like you! If you have suggestions or would like to contribute, fork us on GitHub.

Buy the book

Save 39% on CORS in Action with promotional code hossainco at manning.com/hossain