Open Closed

How to update CORS from value saved in DB #8552


User avatar
0
Navneet@aol.com.au created
  • ABP Framework version: v8.2.3
  • UI Type: Angular / MVC / Blazor WASM / Blazor Server
  • Database System: EF Core (PostgreSQL)
  • Tiered (for MVC) or Auth Server Separated (for Angular): yes
  • Exception message and full stack trace:
  • Steps to reproduce the issue:

Hi Team,

My client is looking to set CORS from the value saved in DB instead of appsettings.json, I have extended ConfigureApplication class as below:

ConfigureOpenIddict(openIddict = {
    openIddict. ConfigureApplication(spp =>
        spp. AddOrUpdateProperty<string>(
        "AppCORS", 
            property =>
            {
                property Attributes. Add(new RequiredAttributeD);
            }
        );
    });
}):

However, I don't know How to move the below code to AppServices.cs

context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder
                    .WithOrigins(
                        configuration["App:CorsOrigins"]?
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.Trim().RemovePostFix("/"))
                            .ToArray() ?? Array.Empty<string>()
                    )
                    .WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });

Could you please help here? Thanks, Navneet


15 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    context.Services.AddCors(options = will only execute once.

    You can use MyCorsPolicyProvider to dynamic get doamins from database/cache.

    // context.Services.AddCors(options =>
    // {
    //     options.AddDefaultPolicy(builder =>
    //     {
    //         builder
    //             .WithOrigins(
    //                 configuration["App:CorsOrigins"]?
    //                     .Split(",", StringSplitOptions.RemoveEmptyEntries)
    //                     .Select(o => o.Trim().RemovePostFix("/"))
    //                     .ToArray() ?? Array.Empty<string>()
    //             )
    //             .WithAbpExposedHeaders()
    //             .SetIsOriginAllowedToAllowWildcardSubdomains()
    //             .AllowAnyHeader()
    //             .AllowAnyMethod()
    //             .AllowCredentials();
    //     });
    // });
    
    context.Services.RemoveAll(typeof(ICorsPolicyProvider));
    context.Services.Add(ServiceDescriptor.Transient<ICorsPolicyProvider, MyCorsPolicyProvider>());
    
    public class MyCorsPolicyProvider : ICorsPolicyProvider
    {
        private readonly static Task<CorsPolicy?> NullResult = Task.FromResult<CorsPolicy?>(null);
        private readonly CorsOptions _options;
    
        public MyCorsPolicyProvider(IOptions<CorsOptions> options)
        {
            _options = options.Value;
        }
    
        public async Task<CorsPolicy?> GetPolicyAsync(HttpContext context, string? policyName)
        {
            //get domains from database, remember to cache it
    
            var domains = new List<string> { "https://localhost:44300", "https://localhost:44301" };
    
            var builder = new CorsPolicyBuilder(Array.Empty<string>());
            builder
                .WithOrigins(
                    domains.Select(o => o.Trim().RemovePostFix("/"))
                        .ToArray() ?? Array.Empty<string>()
                )
                .WithAbpExposedHeaders()
                .SetIsOriginAllowedToAllowWildcardSubdomains()
                .AllowAnyHeader()
                .AllowAnyMethod()
                .AllowCredentials();
            var result = builder.Build();
    
            return result;
        }
    }
    
  • User Avatar
    0
    Navneet@aol.com.au created

    Hi maliming,

    Thanks for your code help :-)

    Regarding your comments "get domains from database, remember to cache it" I understand that I need to inject Irepository first as below:

            private readonly IRepository<OpenIddictApplication, Guid> _OpenIddictApplicationRepository;
    

    How do you use cache in the code that is written in TechDB.Application that supports in web Module?

    if I cache it in ApplicationServices as per https://abp.io/docs/latest/framework/infrastructure/entity-cache, will it be an issue if the tenant is switched?

    Thanks, Navneet

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can use IDistributedCache<YourDomainsCacheItem>. It supports multiple tenants.

    ABP uses IDistributedCache<T> everywhere. : )

    https://abp.io/docs/latest/framework/fundamentals/caching

  • User Avatar
    0
    Navneet@aol.com.au created

    hi

    context.Services.AddCors(options = will only execute once.

    You can use MyCorsPolicyProvider to dynamic get doamins from database/cache.

    // context.Services.AddCors(options => 
    // { 
    //     options.AddDefaultPolicy(builder => 
    //     { 
    //         builder 
    //             .WithOrigins( 
    //                 configuration["App:CorsOrigins"]? 
    //                     .Split(",", StringSplitOptions.RemoveEmptyEntries) 
    //                     .Select(o => o.Trim().RemovePostFix("/")) 
    //                     .ToArray() ?? Array.Empty<string>() 
    //             ) 
    //             .WithAbpExposedHeaders() 
    //             .SetIsOriginAllowedToAllowWildcardSubdomains() 
    //             .AllowAnyHeader() 
    //             .AllowAnyMethod() 
    //             .AllowCredentials(); 
    //     }); 
    // }); 
     
    context.Services.RemoveAll(typeof(ICorsPolicyProvider)); 
    context.Services.Add(ServiceDescriptor.Transient<ICorsPolicyProvider, MyCorsPolicyProvider>()); 
    
    public class MyCorsPolicyProvider : ICorsPolicyProvider 
    { 
        private readonly static Task<CorsPolicy?> NullResult = Task.FromResult<CorsPolicy?>(null); 
        private readonly CorsOptions _options; 
     
        public MyCorsPolicyProvider(IOptions<CorsOptions> options) 
        { 
            _options = options.Value; 
        } 
     
        public async Task<CorsPolicy?> GetPolicyAsync(HttpContext context, string? policyName) 
        { 
            //get domains from database, remember to cache it 
     
            var domains = new List<string> { "https://localhost:44300", "https://localhost:44301" }; 
        ...........
        } 
    } 
    

    Hi maliming,

    1. Do I put the above code in the Application or EntityFrameworkCore as the AuthServer does not have direct reference to the Application?

    2. Also, the Client has three webapplications deployed in three regions pointing to one Authserver (seeded all three web applications), how can I find which application the user is coming to use API host

    {
        private readonly static Task<CorsPolicy?> NullResult = Task.FromResult<CorsPolicy?>(null);
        private readonly CorsOptions _options;
         private readonly IRepository<OpenIddictApplication, Guid> _OpenIddictApplicationRepository;
    
        public MyCorsPolicyProvider(
            IOptions<CorsOptions> options,
            IRepository<OpenIddictApplication, Guid> OpenIddictApplicationRepository)
        {
            _options = options.Value;
            _OpenIddictApplicationRepository = OpenIddictApplicationRepository;
        }
    
        public async Task<CorsPolicy?> GetPolicyAsync(HttpContext context, string? policyName)
        {
            //get domains from database, remember to cache it
    
            var selectedApplication = _OpenIddictApplicationRepository.GetAsync(????); 
    

    _OpenIddictApplicationRepository.GetAsync(?) - how to identify which ClientID of application the user is getting authenticated and allowed Cors

    Thanks, Navneet

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    1. Add MyCorsPolicyProvider to your AuthServer(web) project.

    2. You can inject the ICurrentClient, which has an Id property.

  • User Avatar
    0
    Navneet@aol.com.au created

    hi

    1. Add MyCorsPolicyProvider to your AuthServer(web) project.

    2. You can inject the ICurrentClient, which has an Id property.

    that make sense, however, If I add MyCorsPolicyProvider to AuthServer('web'), then how can I search as the authserver does not have direct to repository like:- private readonly IRepository<OpenIddictApplication, Guid> _OpenIddictApplicationRepository;"

    to query like:- var selectedApplication = _OpenIddictApplicationRepository.GetAsync(id)

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I think your authserver indirectly dependent on openiddict domain module.

    so you can inject the openiddict's repository.

    authserver => EF core project => OpenIddict EF Core => OpenIddict Domain

  • User Avatar
    0
    Navneet@aol.com.au created

    hi

    I think your authserver indirectly dependent on openiddict domain module.

    so you can inject the openiddict's repository.

    authserver => EF core project => OpenIddict EF Core => OpenIddict Domain

    Thanks Maliming,

    I have injected "private readonly ICurrentClient _currentClient;" and _currentClient.Id is always null? I am not sure If I am missing anything.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    What is the process of your authentication?

    After signing in to the website, you can get the client id.

    Are you using the code flow to get token?

  • User Avatar
    0
    Navneet@aol.com.au created

    hi

    What is the process of your authentication?

    After signing in to the website, you can get the client id.

    Are you using the code flow to get token?

    I have created an Application to use Allow Authorization Code Flow, below is the screenshot:

    public class MyCorsPolicyProvider : ICorsPolicyProvider
    {
        private readonly static Task<CorsPolicy?> NullResult = Task.FromResult<CorsPolicy?>(null);
        private readonly CorsOptions _options;
         protected IOpenIddictApplicationManager _ApplicationManager { get; }
         private readonly ICurrentClient _currentClient;
    
        public MyCorsPolicyProvider(
            IOptions<CorsOptions> options,
            IOpenIddictApplicationManager applicationManager,
            ICurrentClient currentClient)
        {
            _options = options.Value;
            _ApplicationManager = applicationManager;
            _currentClient = currentClient;
        }
    
        public async Task<CorsPolicy?> GetPolicyAsync(HttpContext context, string? policyName)
        {
            //get domains from database, remember to cache it
    
            var id = _currentClient.Id; // value is coming null
            
            var app = await _ApplicationManager.FindByIdAsync(id);
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    For anonymous requests, there is no way to get the client ID.

    The client id has to exist in a query string or form post.

    You can try to inject the IHttpContextAccessor and call the GetOpenIddictServerRequest extension method.

    var request = HttpContext.GetOpenIddictServerRequest()

    but the OpenIddictServerRequest only exists in the openiddict request.

    eg: connect/token or connect/authorize

  • User Avatar
    0
    Navneet@aol.com.au created

    hi

    For anonymous requests, there is no way to get the client ID.

    The client id has to exist in a query string or form post.

    You can try to inject the IHttpContextAccessor and call the GetOpenIddictServerRequest extension method.

    var request = HttpContext.GetOpenIddictServerRequest()

    but the OpenIddictServerRequest only exists in the openiddict request.

    eg: connect/token or connect/authorize

    Hi maliming,

    you are correct, without authentication, client id cannot be accessed, however, I managed to get the client id by HttpContext.

    1. In your code, you have injected the below and didn't use it, do I need to get anything to enforce CORS by below:-
      private readonly static Task<CorsPolicy?> NullResult = Task.FromResult<CorsPolicy?>(null);
        private readonly CorsOptions _options;
    
    1. Also, when I browse the endpoint via Postman connect/token or connect/authorize I don't reach to MyCorsPolicyProvider method.

    2. Someresone get and set from cache is not working for me, could you please see if you can find any issue with my below code:

    public class MyCorsPolicyProvider : ICorsPolicyProvider
    {
        private readonly static Task<CorsPolicy?> NullResult = Task.FromResult<CorsPolicy?>(null);
        private readonly CorsOptions _options;
    
        private readonly IOpenIddictApplicationRepository _openIddictApplicationrepo;
        private readonly IDistributedCache<string> _cache;
    
        public MyCorsPolicyProvider(
            IOptions<CorsOptions> options,
            IDistributedCache<string> cache,
            IOpenIddictApplicationRepository openIddictApplicationrepo)
        {
            _options = options.Value;
            _cache = cache;
            _openIddictApplicationrepo = openIddictApplicationrepo;
        }
    
        public async Task<CorsPolicy?> GetPolicyAsync(HttpContext context, string? policyName)
        {
            //get domains from database, remember to cache it
    
            var httpContext = context;
    
            // Get the ReturnUrl from the query string
            var returnUrl = httpContext.Request.Query["ReturnUrl"].ToString();
    
            string clientIdFromUrl = "";
    
            if (!string.IsNullOrEmpty(returnUrl))
            {
                // Decode the ReturnUrl
                var decodedReturnUrl = HttpUtility.UrlDecode(returnUrl);
    
                // Check if the decoded URL contains a query string
                var uriParts = decodedReturnUrl.Split('?');
    
                if (uriParts.Length > 1)
                {
                    var innerQueryString = uriParts[1]; // Extract the query string part
    
                    // Parse the inner query string
                    var queryParameters = HttpUtility.ParseQueryString(innerQueryString);
    
                    // Extract the client_id
                    clientIdFromUrl = queryParameters["client_id"];
    
                }
            }
    
            var CorsDomain = GetAsync(clientIdFromUrl);
    
            var domains = new List<string> { CorsDomain.ToString() };
    
    
    
            var builder = new CorsPolicyBuilder(Array.Empty<string>());
            builder
                .WithOrigins(
                    domains.Select(o => o.Trim().RemovePostFix("/"))
                        .ToArray() ?? Array.Empty<string>()
                )
    
                .WithAbpExposedHeaders()
                .SetIsOriginAllowedToAllowWildcardSubdomains()
                .AllowAnyHeader()
                .AllowAnyMethod()
                .AllowCredentials();
            var result = builder.Build();
    
            return result;
        }
    
        private async Task<string?> GetAsync(string clientId)
        {
            var CorsFromCache = await _cache.GetAsync(clientId);
    
            if (CorsFromCache == null)
            {
                var CorsFromDb = GetFromDBAsync(clientId);
    
                await _cache.SetAsync(clientId,CorsFromDb.ToString());
    
                return CorsFromDb.ToString();
            }
    
            return null;
        }
    
        private async Task<string?> GetFromDBAsync(string clientId)
        {
            var appinfo = await _openIddictApplicationrepo.FindByClientIdAsync(clientId);
    
            return appinfo.AppCORS.ToString();
        }
    
    }
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    1. see 3.2

    2. Cors will check if the request header contains Origin etc..

    Check the logic of CorsMiddleware

    https://github.com/dotnet/aspnetcore/blob/release/9.0/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs#L107-L110 https://github.com/dotnet/aspnetcore/blob/release/9.0/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs#L112-L157 https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-9.0

    3.1 You can call _cache.GetOrAddAsync.

    3.2 If there is no domains, you can return Task.FromResult<CorsPolicy?>(null);

    https://github.com/dotnet/aspnetcore/blob/release/9.0/src/Middleware/CORS/src/Infrastructure/DefaultCorsPolicyProvider.cs#L12

  • User Avatar
    0
    Navneet@aol.com.au created

    Hi maliming,

    Thanks for sharing the links, it is now crystal clear how CORS works in the backend with the request header.

    PS:- I have also read your article on extending grant type, it was impressive - Good work mate!!

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Great : )

Made with ❤️ on ABP v9.2.0-preview. Updated on January 08, 2025, 14:09