← Files Duende SkillsARCHIVED FILE

skills/identity-security-hardening/docs/cors-csp.md

4.02 KB · Oct 5, 2026 · 18:31 UTC

↓ Download file

# CORS and Content Security Policy for IdentityServer

## Pattern 8: CORS Configuration

CORS for the token endpoint must be locked to known client origins. Never use `AllowAnyOrigin` for IdentityServer endpoints.

### Per-Client CORS Origins

```csharp
// ✅ Restrict CORS to the specific origin of each client
new Client
{
    ClientId = "spa.bff",
    AllowedCorsOrigins =
    {
        "https://app.example.com"
        // No trailing slash, no wildcards, exact scheme+host+port
    }
}
```

### Custom CORS Policy Service

For dynamic tenant scenarios, implement `ICorsPolicyService`:

```csharp
// ✅ Custom CORS policy — validates against a database of allowed origins
public sealed class TenantCorsPolicyService : ICorsPolicyService
{
    // NOTE: IClientStore only exposes FindClientByIdAsync(string clientId).
    // There is no FindEnabledClientsAsync() method. To enumerate all clients for
    // CORS origin checks you need a custom repository or direct DB query.
    // The example below uses a hypothetical IClientRepository; replace with your
    // own abstraction (e.g. direct EF Core DbContext query).
    private readonly IClientRepository _clientRepository;
    private readonly ILogger<TenantCorsPolicyService> _logger;

    public TenantCorsPolicyService(
        IClientRepository clientRepository,
        ILogger<TenantCorsPolicyService> logger)
    {
        _clientRepository = clientRepository;
        _logger = logger;
    }

    public async Task<bool> IsOriginAllowedAsync(string origin)
    {
        // Normalize: strip trailing slash, lowercase
        var normalizedOrigin = origin.TrimEnd('/').ToLowerInvariant();

        var allClients = await _clientRepository.GetAllClientsAsync();
        var isAllowed = allClients
            .SelectMany(c => c.AllowedCorsOrigins)
            .Select(o => o.TrimEnd('/').ToLowerInvariant())
            .Contains(normalizedOrigin);

        if (!isAllowed)
            _logger.LogWarning("CORS request from unlisted origin: {Origin}", origin);

        return isAllowed;
    }
}
```

Register it:

```csharp
// ✅ Replace the default CORS policy service
builder.Services.AddTransient<ICorsPolicyService, TenantCorsPolicyService>();
```

---

## Pattern 9: Content Security Policy

IdentityServer's login, consent, and logout UI pages must carry a strong Content Security Policy to prevent XSS and clickjacking attacks.

```csharp
// ✅ Add CSP middleware for IdentityServer UI pages
app.Use(async (context, next) =>
{
    // Apply CSP only to IdentityServer UI paths
    var path = context.Request.Path.Value ?? string.Empty;
    var isIdentityUiPath =
        path.StartsWith("/account", StringComparison.OrdinalIgnoreCase) ||
        path.StartsWith("/consent", StringComparison.OrdinalIgnoreCase) ||
        path.StartsWith("/diagnostics", StringComparison.OrdinalIgnoreCase);

    if (isIdentityUiPath)
    {
        context.Response.Headers.Append("Content-Security-Policy",
            "default-src 'self'; " +
            "script-src 'self'; " +          // No inline scripts
            "style-src 'self'; " +           // No inline styles
            "img-src 'self' data:; " +       // Allow data: for favicons
            "font-src 'self'; " +
            "frame-ancestors 'none'; " +      // Block embedding in iframes
            "form-action 'self'; " +          // Forms only POST to self
            "base-uri 'self'; " +
            "object-src 'none'");             // No plugins

        // Clickjacking defense — belt-and-suspenders with CSP frame-ancestors
        context.Response.Headers.Append("X-Frame-Options", "DENY");

        // Force MIME type sniffing protection
        context.Response.Headers.Append("X-Content-Type-Options", "nosniff");

        // Referrer control
        context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
    }

    await next();
});
```

> **Note:** If your IdentityServer UI uses a CDN for Bootstrap or other assets, add those origins to `style-src` and `script-src` with explicit `sha256-` or `nonce-` values rather than broad origins.

SHA-256: edcc730fd0a8c557046e9b6497c3c2d65504d42fa1dd4ff987c0a72fc96ac05c