corsMiddleware function

Middleware corsMiddleware({
  1. List<String>? allowedOrigins,
  2. List<String> allowedMethods = const ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  3. List<String> allowedHeaders = const ['Origin', 'Content-Type', 'Accept', 'Authorization', 'X-CSRF-Token'],
  4. List<String> exposedHeaders = const [],
  5. bool allowCredentials = true,
  6. int maxAge = 86400,
})

CORS Middleware for handling Cross-Origin Resource Sharing

Example usage:

final handler = Pipeline()
  .addMiddleware(corsMiddleware(
    allowedOrigins: ['https://example.com', 'https://app.example.com'],
    allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  ))
  .addHandler(router);

Implementation

Middleware corsMiddleware({
  List<String>? allowedOrigins,
  List<String> allowedMethods = const ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  List<String> allowedHeaders = const ['Origin', 'Content-Type', 'Accept', 'Authorization', 'X-CSRF-Token'],
  List<String> exposedHeaders = const [],
  bool allowCredentials = true,
  int maxAge = 86400, // 24 hours
}) {
  return (Handler handler) {
    return (Request request) async {
      final origin = request.headers['origin'];

      // Check if origin is allowed
      bool isOriginAllowed = false;
      if (allowedOrigins == null || allowedOrigins.isEmpty) {
        // In development, allow all origins
        isOriginAllowed = true;
      } else if (origin != null) {
        isOriginAllowed = allowedOrigins.any((allowed) {
          if (allowed == '*') return true;
          if (allowed.endsWith('*')) {
            final prefix = allowed.substring(0, allowed.length - 1);
            return origin.startsWith(prefix);
          }
          return origin == allowed;
        });
      }

      // Handle preflight OPTIONS request
      if (request.method == 'OPTIONS') {
        return Response.ok(
          null,
          headers: {
            if (isOriginAllowed && origin != null) 'Access-Control-Allow-Origin': origin,
            'Access-Control-Allow-Methods': allowedMethods.join(', '),
            'Access-Control-Allow-Headers': allowedHeaders.join(', '),
            if (exposedHeaders.isNotEmpty) 'Access-Control-Expose-Headers': exposedHeaders.join(', '),
            if (allowCredentials) 'Access-Control-Allow-Credentials': 'true',
            'Access-Control-Max-Age': maxAge.toString(),
          },
        );
      }

      // Process actual request
      final response = await handler(request);

      // Add CORS headers to response
      return response.change(headers: {
        if (isOriginAllowed && origin != null) 'Access-Control-Allow-Origin': origin,
        if (exposedHeaders.isNotEmpty) 'Access-Control-Expose-Headers': exposedHeaders.join(', '),
        if (allowCredentials) 'Access-Control-Allow-Credentials': 'true',
      });
    };
  };
}