Search code examples
c#asp.net-core-webapi.net-8.0

ASP.NET Core Web API : conditional serialization


I have a user base with different claims to represent their plans. I want to strip some fields from my objects based on their plans.

I found an answer here: ASP.NET WebAPI Conditional Serialization based on User Role

But it is for HttpClient only, since I can't use DelegatingHandler in ASP.NET Core 8 (or don't understand how to) - is there any similar way to manipulate the response of all of my controllers?


Solution

  • According to your requirement, I implement this feature in my side. Here is the detailed steps.

    enter image description here

    ClaimBasedJsonConverter.cs

    using System.Reflection;
    using System.Text.Json.Serialization;
    using System.Text.Json;
    
    namespace WebApplicationApi
    {
        public class ClaimBasedJsonConverter : JsonConverter<object>
        {
            private readonly IHttpContextAccessor _httpContextAccessor;
    
            public ClaimBasedJsonConverter(IHttpContextAccessor httpContextAccessor)
            {
                _httpContextAccessor = httpContextAccessor;
            }
    
            public override bool CanConvert(Type typeToConvert)
            {
                return typeToConvert.GetProperties().Any(prop => prop.IsDefined(typeof(ClaimSensitiveAttribute), true));
            }
    
            public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                throw new NotImplementedException();
            }
    
            public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
            {
                var httpContext = _httpContextAccessor.HttpContext;
                var userClaims = httpContext.User.Claims;
    
                writer.WriteStartObject();
    
                foreach (var property in value.GetType().GetProperties())
                {
                    var propertyValue = property.GetValue(value);
                    var claimSensitiveAttribute = property.GetCustomAttribute<ClaimSensitiveAttribute>();
    
                    if (claimSensitiveAttribute == null)
                    {
                        writer.WritePropertyName(property.Name);
                        JsonSerializer.Serialize(writer, propertyValue, property.PropertyType, options);
                        continue;
                    }
    
                    var hasRequiredClaim = userClaims.Any(c =>
                        c.Type == claimSensitiveAttribute.ClaimType &&
                        claimSensitiveAttribute.ClaimValues.Contains(c.Value));
    
                    if (hasRequiredClaim)
                    {
                        writer.WritePropertyName(property.Name);
                        JsonSerializer.Serialize(writer, propertyValue, property.PropertyType, options);
                    }
                }
    
                writer.WriteEndObject();
            }
        }
    
    }
    

    ClaimSensitiveAttribute.cs

    namespace WebApplicationApi
    {
        [AttributeUsage(AttributeTargets.Property)]
        public class ClaimSensitiveAttribute : Attribute
        {
            public string ClaimType { get; }
            public string[] ClaimValues { get; }
    
            public ClaimSensitiveAttribute(string claimType, params string[] claimValues)
            {
                ClaimType = claimType;
                ClaimValues = claimValues;
            }
        }
    
    }
    

    Register it

    using WebApplicationApi;
    
    var builder = WebApplication.CreateBuilder(args);
    // Register it 
    builder.Services.AddHttpContextAccessor();
    
    builder.Services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new ClaimBasedJsonConverter(builder.Services.BuildServiceProvider()?.GetRequiredService<IHttpContextAccessor>()));
    });
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    app.UseHttpsRedirection();
    // Make sure you have this line
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    

    Apply it in model

    namespace WebApplicationApi.Models
    {
        public class User
        {
            public string? Name { get; set; }
            public string? Email { get; set; }
    
            [ClaimSensitive("Plan", "Free", "Premium")]
            public string? SensitiveData { get; set; }
    
            [ClaimSensitive("Role", "Admin")]
            public string? AdminNotes { get; set; }
        }
    }
    

    Test it in controller

    using Microsoft.AspNetCore.Mvc;
    using System.Security.Claims;
    using WebApplicationApi.Models;
    
    namespace WebApplicationApi.Controllers
    {
        [ApiController]
        [Route("api/[controller]")]
        public class UsersController : ControllerBase
        {
    
            [HttpGet]
            public IActionResult GetUser()
            {
                // If change Plan value to `Unknown`, 
                //{
                // "Name": "John Doe",
                // "Email": "[email protected]",
                // "AdminNotes": "Admin Only Notes"
                //}
                var claims = new List<Claim>
                {
                    
                    new Claim("Plan", "Premium"),  
                    new Claim("Role", "Admin")     
                };
    
                var identity = new ClaimsIdentity(claims, "TestAuth");
                var user = new ClaimsPrincipal(identity);
                HttpContext.User = user;
    
                var userModel = new User
                {
                    Name = "John Doe",
                    Email = "[email protected]",
                    SensitiveData = "Sensitive Premium Info",
                    AdminNotes = "Admin Only Notes"
                };
    
                return Ok(userModel);
            }
    
        }
    }
    

    Test Result

    enter image description here