ASP.NET Core の CORS で許可するオリジンを動的に決定する

例えば REST API を提供していて、ブラウザ上から JavaScript で呼び出せるように、CORS 対応したい。でも、許可するのは登録してもらったオリジンだけにしたい。そんなとき。

Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider を実装したクラスを用意し、DI コンテナに登録すればいい。

namespace CorsSample;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;

public class MyCorsPolicyProvider : ICorsPolicyProvider
{
    public async Task<CorsPolicy?> GetPolicyAsync(HttpContext context, string? policyName)
    {
        // とりあえずの実装なので、ポリシー関係なく全適用
        var origin = GetCorsOrigin(context.Request);
        if (origin != null)
        {
            if (await IsOriginAllowedAsync(context, origin))
            {
                return Allow(origin);
            }
        }
        return null;
    }

    private CorsPolicy Allow(string origin)
    {
        return new CorsPolicyBuilder()
            .WithOrigins(origin)
            .AllowAnyHeader()
            .AllowAnyMethod()
            .Build();
    }

    private async Task<bool> IsOriginAllowedAsync(HttpContext context, string origin)
    {
        origin = origin.ToLowerInvariant();

        var dbContext = context.RequestServices.GetRequiredService<ApplicationDbContext>();

        return await dbContext.ClientCorsOrigins
            .Where(x => x.Origin == origin)
            .AnyAsync();
    }

    private string? GetCorsOrigin(HttpRequest request)
    {
        var origin = request.Headers["Origin"].FirstOrDefault();
        var thisOrigin = request.Scheme + "://" + request.Host;
        if (origin != null && origin != thisOrigin)
        {
            return origin;
        }
        return null;
    }
}

こいつを、AddCors した後に AddTransient で登録する。

using CorsSample;
using Microsoft.AspNetCore.Cors.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

// CORS を登録し、
// ICorsPolicyProvider の実装を差し替える。
builder.Services.AddCors();
builder.Services.AddTransient<ICorsPolicyProvider, MyCorsPolicyProvider>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

// CORS ミドルウェアを組み込む
app.UseCors();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

本番では、許可済みオリジンを一定時間キャッシュしたほうがいい。