Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to do authentication middleware? #220

Open
jeremydmiller opened this issue Mar 2, 2023 · 1 comment
Open

How to do authentication middleware? #220

jeremydmiller opened this issue Mar 2, 2023 · 1 comment
Milestone

Comments

@jeremydmiller
Copy link
Member

From #173

Hi @jeremydmiller,

at the risk of flooding you with feedback (take your time!), I today tried to Wolverine-ify RBAC on the granularity of a command (still talking about IMessageBus).

I use KeyCloak for authentication and there is an excellent NuGet with an example to implement RBAC with a Mediatr behavior..

The code has a comment // TODO: consider reflection performance impact which I'm also after.

From my understanding, the Wolverine equivalent of IPipelineBehavior would be a middleware that would incur the same reflection cost per processed message. My idea was to make use of the code generation capability around the handler and pay that cost only once during setup.

using System.Runtime.CompilerServices;

using Application.Authorization;

using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;

using Lamar;

using Microsoft.Extensions.Logging;

using Wolverine.Configuration;
using Wolverine.Runtime.Handlers;

namespace Infrastructure.Authorization;

public class AuthorizationPolicy : IHandlerPolicy
{
  static readonly Dictionary<Type, string[]> MessageTypeToPolicies = new();

  public void Apply(HandlerGraph graph, GenerationRules rules, IContainer container)
  {
    foreach (var chain in graph.Chains)
    {
      Apply(chain);
    }
  }

  void Apply(HandlerChain chain)
  {
    var policies = chain.MessageType
                        .GetCustomAttributes(typeof(AuthorizeAttribute), true)
                        .Cast<AuthorizeAttribute>()
                        .Select(x => x.Policy)
                        .Where(x => !string.IsNullOrWhiteSpace(x))
                        .ToArray();

    if (!policies.Any())
    {
      return;
    }

    MessageTypeToPolicies.Add(chain.MessageType, policies!);

    var method = GetType()
                 .GetMethod(nameof(EnforcePolicy))
                 .MakeGenericMethod(chain.MessageType);

    var methodCall = new MethodCall(GetType(), method);

    chain.Middleware.Add(methodCall);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  public static async Task EnforcePolicy<T>(IIdentityService identityService, ILogger logger, T message)
  {
    var policies = MessageTypeToPolicies[message.GetType()];

    foreach (var policy in policies)
    {
      if (await identityService.AuthorizeAsync(policy))
      {
        continue;
      }

      logger.LogWarning("Failed {Policy} policy authorization with user {User} for {Message}",
                        policy,
                        identityService.UserId,
                        message);

      throw new UnauthorizedAccessException();
    }
  }
}

The Apply does the reflection bits and then injects a method call, like a middleware's Before(). But the Before() variant would always need to reflect, right?

As you can see the handler policy uses a dictionary to cache the roles per message, but my ultimate goal would be to pass the list of roles directly to EnforcePolicy, perhaps as the first argument.

I tried that for a couple of hours, even cloned JasperFx.CodeGeneration, but I could not find a way to make the first argument a constant expression. Perhaps another way is to register "required roles per message" in the container, but that seems a bit too overboard. It would be great if you could share your ideas!

@jeremydmiller
Copy link
Member Author

@agross I'll have to give this one a better look and get back to you. It's exactly the kind of thing that I think Wolverine should do much more efficiently than MediatR et al.

@jeremydmiller jeremydmiller changed the title Middleware How to do authentication middleware? Mar 2, 2023
@jeremydmiller jeremydmiller added this to the 1.0-alpha milestone Apr 26, 2023
@jeremydmiller jeremydmiller modified the milestones: 1.0-alpha, Post 1.0 Jun 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant