0

My base Request class looks like this:

public class GetAllProjectsQuery : QueryBase<ProjectsListModel>
{
}

public abstract class QueryBase<T> : UserContext, IRequest<T> // IRequest is MediatR interface
{
}

public abstract class UserContext
{
    public string ApplicationUserId { get; set; } // and other properties
}

I want to write a middleware to my .NET Core 3.1 WebApi that will grab JWT from request header amd read ApplicationUserId from it. I started to code something:

public class UserInformation
{
    private readonly RequestDelegate next;

    public UserInformation(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var jwt = context.Request.Headers["Authorization"];
        // read jwt here
        var userContext = (UserContext)context.Request.Body; // i know it wont work
        userContext.ApplicationUserId = //whats next? Any ideas?

        await this.next(context);
    }
}

But to be honest i have no idea how to start so here are my questions:

As you can see, every request will be packed with my UserContext class and so on. How to cast HttpContext.Request.Body to my request object and attach ApplicationUserId to it? Is it possible? I want to acces to user credentials from my JWT from headers and i want to have that information in every request in my API (pass it to controller, then to command etc).

If getting this information from middleware is not the best practice, what is?

EDIT: Mcontroller that using MediatR:

// base controller:
[ApiController]
[Route("[controller]")]
public abstract class BaseController : ControllerBase
{
    private IMediator mediator;

    protected IMediator Mediator => this.mediator ?? (this.mediator = HttpContext.RequestServices.GetService<IMediator>());
}

// action in ProjectControlle

[HttpGet]
[Authorize]
public async Task<ActionResult<ProjectsListModel>> GetAllProjects()
{
    return Ok(await base.Mediator.Send(new GetAllProjectsQuery()));
}

// query:
public class GetAllProjectsQuery : QueryBase<ProjectsListModel>
{
}

// handler:

public class GetAllProjectsQueryHandler : IRequestHandler<GetAllProjectsQuery, ProjectsListModel>
{
    private readonly IProjectRepository projectRepository;

    public GetAllProjectsQueryHandler(IProjectRepository projectRepository)
    {
        this.projectRepository = projectRepository;
    }

    public async Task<ProjectsListModel> Handle(GetAllProjectsQuery request, CancellationToken cancellationToken)
    {
        var projects = await this.projectRepository.GetAllProjectsWithTasksAsync();

        return new ProjectsListModel
        {
            List = projects
        };
    }
}

michasaucer
  • 4,562
  • 9
  • 40
  • 91
  • 1
    Take a look at [this](https://stackoverflow.com/questions/18677837/decoding-and-verifying-jwt-token-using-system-identitymodel-tokens-jwt) post. – Martin Staufcik Mar 21 '20 at 09:24

1 Answers1

1

You might not need a middleware, but need a model binder:

See: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1

Also see: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1

public class UserContextModelBinder : IModelBinder
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IModelBinder _defaultModelBinder;

    public UserContextModelBinder(
        IHttpContextAccessor httpContextAccessor,
        IOptions<MvcOptions> mvcOptions,
        IHttpRequestStreamReaderFactory streamReaderFactory)
    {
        _httpContextAccessor = httpContextAccessor;
        _defaultModelBinder = new BodyModelBinder(mvcOptions.Value.InputFormatters, streamReaderFactory);
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (!typeof(UserContext).IsAssignableFrom(bindingContext.ModelType))
        {
            return;
        }

        await _defaultModelBinder.BindModelAsync(bindingContext);

        if (bindingContext.Result.IsModelSet && bindingContext.Result.Model is UserContext)
        {
            var model = (UserContext)bindingContext.Result.Model;
            var httpContext = _httpContextAccessor.HttpContext;

            // Read JWT
            var jwt = httpContext.Request.Headers["Authorization"];

            model.ApplicationUserId = jwt;

            bindingContext.Result = ModelBindingResult.Success(model);
        }
    }
}

Then add model binder to UserContext class:

[ModelBinder(typeof(UserContextModelBinder))]
public abstract class UserContext
{
    public string ApplicationUserId { get; set; }
}

Also add IHttpContextAccessor to services in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddHttpContextAccessor();
}
weichch
  • 9,306
  • 1
  • 13
  • 25
  • It looks great, could you provide your answer with additional links? I want to read about this binding more because its what i was looking for – michasaucer Mar 21 '20 at 10:31
  • To be honest, your solution not work. I copy-pasted it and ModelBinder is never hited – michasaucer Mar 22 '20 at 17:24
  • @michasaucer That's weird. What I have in the answer was copy pasted from a local project where I tested it working. I had a controller action which is an HTTP Post and takes a parameter of `Query` (derived from `UserContext`), what's your controller action look like? This was mine `[HttpPost] public IEnumerable Get([FromBody]Query query)`. – weichch Mar 22 '20 at 21:43
  • My query hitting `handler`, but not `binder` – michasaucer Mar 23 '20 at 06:20
  • @michasaucer Model binder works when you have a controller like this `public async Task> GetAllProjects([FromQuery] GetAllProjectsQuery query)` if you create a new a query using `new`, it wouldn't trigger model binder. I thought you need to read query from body? – weichch Mar 23 '20 at 09:15
  • You are right, but can i bind int `[FromBody]`? i accepted your answer back – michasaucer Mar 23 '20 at 10:12
  • Sure you can, but only if response body contains one integer I guess. Have a look at the links in my answer. Model binding has been an awesome tool in ASP.Net. – weichch Mar 23 '20 at 10:20
  • what if i want to have that `ApplicationUserId` only on backend side? I dont want to pass it in body because there is no befetis of it. I want to hide it from user – michasaucer Mar 23 '20 at 10:27
  • So that `ApplicationUserId` is not generated from user / client side? If it is static to the server side, then you could make it a configuration for the server side: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1 – weichch Mar 23 '20 at 10:33