Skip to content

Solving CORS Errors in Flutter Web

Problem Statement

When developing Flutter web applications, you may encounter Cross-Origin Resource Sharing (CORS) errors when making HTTP requests to external APIs. The error typically appears as:

Access to XMLHttpRequest at 'https://api.example.com' from origin 'http://localhost:port' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

This occurs because browsers enforce CORS policies that restrict cross-origin requests for security reasons. Unlike mobile apps where these restrictions don't apply, web browsers require explicit permission from the server to allow cross-origin requests.

Important Note

CORS is a browser security feature, not a bug. It cannot be bypassed in production code running in users' browsers. The solutions below are categorized by development vs. production scenarios.

Development Solutions

Using Browser Flags (Flutter 3.3.0+)

For development and testing purposes, you can disable web security in Chrome:

bash
flutter run -d chrome --web-browser-flag "--disable-web-security"

For integration tests:

bash
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d web-server --web-browser-flag="--disable-web-security"

Alternative Development Approach

If the above doesn't work with your setup, you can create a custom Chrome launcher:

bash
#!/bin/zsh
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
set -e
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --test-type --disable-web-security "$@"
set +e &
PID=$!
wait $PID
trap - SIGINT SIGTERM EXIT
wait $PID

Save this as ~/chrome_launcher, make it executable (chmod 755 ~/chrome_launcher), and add to your shell configuration:

bash
export CHROME_EXECUTABLE=~/chrome_launcher

Using flutter_cors Package

For a more streamlined development experience:

bash
dart pub global activate flutter_cors
fluttercors --disable

Development Only

These solutions disable important browser security features and should never be used in production environments.

Production Solutions

For production applications, you must configure your server to properly handle CORS requests. Here are examples for various server technologies:

Node.js/Express Server

javascript
const express = require('express');
const cors = require('cors');
const app = express();

// Enable CORS for all routes
app.use(cors());

// Or configure specific options
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET,PUT,PATCH,POST,DELETE");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
  next();
});

PHP Backend

php
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header("Access-Control-Allow-Headers: X-Requested-With, Content-Type, Authorization");

// Your PHP code here
?>

ASP.NET MVC Web API

csharp
protected void Application_BeginRequest(object sender, EventArgs e)
{
    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "https://yourdomain.com");
    
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        HttpContext.Current.Response.End();
    }
}

Spring Boot (Java)

java
@CrossOrigin
@PostMapping(path="/upload")
public @ResponseBody ResponseEntity<Void> upload(@RequestBody Object object) {
    // Your controller logic
}

NGINX Configuration

nginx
location / {
    # Handle preflight OPTIONS requests
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
        add_header 'Access-Control-Max-Age' 1728000;
        return 204;
    }

    # Add CORS headers
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
    try_files $uri $uri/ =404;
}

Common Pitfalls and Solutions

Protocol Mismatch

Ensure your API URL includes the protocol:

dart
// ❌ Incorrect
baseUrl: "localhost:3000/"

// ✅ Correct  
baseUrl: "http://localhost:3000/"

Simple Requests vs. Preflight Requests

Some requests trigger preflight OPTIONS requests. To avoid this, keep requests simple by using standard content types:

dart
// This may avoid preflight requests
headers: {
  'Content-Type': 'text/plain', // Simple content type
},

CORS Proxy for Development

For testing, you can use a CORS proxy service:

dart
http.post('https://cors-anywhere.herokuapp.com/https://api.example.com/data', ...)

WARNING

Proxy services are for development only and should not be used in production due to security and performance concerns.

Best Practices

  1. Never disable CORS in production - It's a critical security feature
  2. Be specific with origins - Avoid using * in production; specify allowed domains
  3. Handle OPTIONS requests - Ensure your server properly responds to preflight requests
  4. Use environment variables - Configure different CORS settings for development vs production

Conclusion

CORS errors in Flutter web require different approaches for development and production:

  • Development: Use browser flags or local proxies to bypass restrictions
  • Production: Properly configure your server to handle CORS requests

Remember that CORS is a browser security feature designed to protect users, and finding the right server-side solution will ensure your Flutter web application works securely and reliably for all users.