CORS on Virtuoso

OpenLink Virtuoso is a hybrid database that combines relational, graph, and document data management. It's commonly used for RDF data, SPARQL endpoints, and Linked Open Data, which are frequently accessed cross-origin. Proper CORS configuration is critical for these use cases.

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

Version Requirements

GUI configuration requires:

For older versions, use the VSP code approach shown below.

Method 1: Conductor GUI Configuration

For basic CORS setup using the Virtuoso Conductor:

  1. Navigate to Virtuoso ConductorWeb Application ServerVirtual Domains & Directories
  2. Expand the default Interface store
  3. Click New Directory
  4. Specify the desired Virtual Directory Type, or choose an existing virtual directory to use as a template
  5. Click Next
  6. Specify the Directory Path value
  7. Set the CORS options:
    • Cross-Origin Resource Sharing - Enter a specific origin such as https://example.com or https://app.example.com. Do not use wildcard * in production.
    • Reject Unintended CORS - Check this box to reject non-whitelisted origins by sending an empty response
  8. Click Save changes

Note: GUI configuration is more efficient than VSP code as it processes CORS at the server level before application code executes.

Method 2: VSP Code Implementation

For programmatic control or older Virtuoso versions, implement CORS in VSP (Virtuoso Server Pages) using http_request_header() and http_header() functions.

Basic Implementation with Multiple Origins

<?vsp
-- Enhanced Virtuoso CORS implementation
DECLARE allowed_origins ANY;
DECLARE request_origin VARCHAR;
DECLARE request_method VARCHAR;
DECLARE i INT;

-- List of allowed origins
allowed_origins := vector(
  'https://example.com',
  'https://app.example.com',
  'https://dashboard.example.com'
);

-- Get request origin and method
request_origin := http_request_header(lines, 'Origin', NULL);
request_method := http_request_header(lines, 'REQUEST_METHOD', NULL);

-- Validate origin against whitelist
FOR (i := 0; i < length(allowed_origins); i := i + 1)
{
  IF (request_origin = allowed_origins[i])
  {
    -- Origin is valid, set CORS headers
    http_header(sprintf('Access-Control-Allow-Origin: %s\r\n', request_origin));
    http_header('Vary: Origin\r\n');
    GOTO origin_valid;
  }
}

-- Origin not valid - reject CORS
RETURN;

origin_valid:
-- Handle preflight OPTIONS request
IF (request_method = 'OPTIONS')
{
  http_header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\r\n');
  http_header('Access-Control-Allow-Headers: Content-Type, Authorization, Accept\r\n');
  http_header('Access-Control-Max-Age: 86400\r\n');
  http_status_set(204);
  RETURN;
}

-- Your application logic here
http_header('Content-Type: application/json\r\n');
http('{"message": "CORS enabled"}');
?>

Pattern Matching for Subdomains

To allow all subdomains of a domain using pattern matching:

<?vsp
DECLARE request_origin VARCHAR;
DECLARE origin_pattern VARCHAR;

request_origin := http_request_header(lines, 'Origin', NULL);

-- Allow all subdomains of example.com
origin_pattern := '^https?://([a-zA-Z0-9-]+\\.)?example\\.com$';

IF (regexp_match(origin_pattern, request_origin) IS NOT NULL)
{
  http_header(sprintf('Access-Control-Allow-Origin: %s\r\n', request_origin));
  http_header('Vary: Origin\r\n');
}
ELSE
{
  -- Origin not allowed
  RETURN;
}

-- Continue with application logic...
?>

Performance Note: Pattern matching with regex runs on every request. For better performance, use explicit origin lists or GUI configuration when possible.

SPARQL Endpoint CORS

SPARQL endpoints are frequently accessed cross-origin and require special consideration:

<?vsp
-- CORS for SPARQL endpoint
DECLARE request_origin VARCHAR;
DECLARE allowed_origins ANY;

request_origin := http_request_header(lines, 'Origin', NULL);

allowed_origins := vector(
  'https://example.com',
  'https://sparql-client.example.com'
);

-- Validate origin
IF (position(request_origin, allowed_origins) > 0)
{
  http_header(sprintf('Access-Control-Allow-Origin: %s\r\n', request_origin));
  http_header('Vary: Origin\r\n');

  -- Handle preflight
  IF (http_request_header(lines, 'REQUEST_METHOD', NULL) = 'OPTIONS')
  {
    http_header('Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n');
    http_header('Access-Control-Allow-Headers: Content-Type, Accept\r\n');
    http_header('Access-Control-Max-Age: 3600\r\n');
    http_status_set(204);
    RETURN;
  }
}

-- Process SPARQL query...
?>

CORS with Credentials

When using HTTP authentication, you must use a specific origin (not wildcard):

<?vsp
DECLARE request_origin VARCHAR;

request_origin := http_request_header(lines, 'Origin', NULL);

-- Must use specific origin, not wildcard
IF (request_origin = 'https://example.com')
{
  http_header(sprintf('Access-Control-Allow-Origin: %s\r\n', request_origin));
  http_header('Access-Control-Allow-Credentials: true\r\n');
  http_header('Vary: Origin\r\n');
}

-- Application logic...
?>

Reusable CORS Procedure

For applications with multiple endpoints, create a reusable CORS procedure:

<?vsp
-- Reusable CORS function
CREATE PROCEDURE check_cors(
  IN request_origin VARCHAR,
  IN allowed_origins ANY)
{
  DECLARE i INT;

  FOR (i := 0; i < length(allowed_origins); i := i + 1)
  {
    IF (request_origin = allowed_origins[i])
    {
      http_header(sprintf('Access-Control-Allow-Origin: %s\r\n', request_origin));
      http_header('Vary: Origin\r\n');
      RETURN 1;
    }
  }

  RETURN 0;
}

-- Usage in VSP pages
DECLARE origins ANY;
DECLARE request_origin VARCHAR;

origins := vector('https://example.com', 'https://app.example.com');
request_origin := http_request_header(lines, 'Origin', NULL);

IF (check_cors(request_origin, origins) = 0)
{
  http_status_set(403);
  http('{"error": "Origin not allowed"}');
  RETURN;
}

-- Continue with application logic...
?>

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