Appending username using headers

Component: NServiceBus
NuGet Package NServiceBus (6.x)

This sample demonstrates how to append the current username to outgoing messages and how to extract that value when messages are handled. The current principal is made available by using a principal accessor registered through dependency injection.

NOTE This sample doesn't use Thread.CurrentPrincipal because of the behavior of Thread.CurrentPrincipal in combination with asynchronous code is dependent on the framework version the code is executed on. For more information, refer to the excellent guideline Migrate from ClaimsPrincipal.Current of ASP.NET Core.

Fake principal

For demonstration purposes, before sending a message, the principalAccessor.CurrentPrincipal is replaced with a new instance. In a production scenario, the principalAccessor.CurrentPrincipal would be either the impersonated user from IIS by assigning Thread.CurrentPrincipal to it or the current user sending a message.

async Task SendMessage(int userNumber)
{
    var identity = new GenericIdentity($"FakeUser{userNumber}");
    principalAccessor.CurrentPrincipal = new GenericPrincipal(identity, new string[0]);

    var message = new MyMessage();
    await endpointInstance.Send("Samples.UsernameHeader.Endpoint2", message)
        .ConfigureAwait(false);
}

await Task.WhenAll(SendMessage(1), SendMessage(2)).ConfigureAwait(false);

The snippet above uses two concurrent sends to demonstrate how the current principle is properly propagated into the message session.

Custom header with a mutator

The recommended approach for capturing the current user is to create a transport mutator that extracts the current identity and then adds it to the header of every outgoing message.

Outgoing message mutator

The outgoing mutator extracts principalAccessor.CurrentPrincipal.Identity.Name and appends it to a message header.

public class AddUserNameToOutgoingHeadersMutator :
    IMutateOutgoingTransportMessages
{
    static ILog log = LogManager.GetLogger("Handler");
    readonly IPrincipalAccessor principalAccessor;

    public AddUserNameToOutgoingHeadersMutator(IPrincipalAccessor principalAccessor)
    {
        this.principalAccessor = principalAccessor;
    }

    public Task MutateOutgoing(MutateOutgoingTransportMessageContext context)
    {
        if (principalAccessor.CurrentPrincipal?.Identity.Name != null)
        {
            log.Info("Adding CurrentPrincipal user to headers");
            context.OutgoingHeaders["UserName"] = principalAccessor.CurrentPrincipal.Identity.Name;
        }

        return Task.CompletedTask;
    }
}

Register the outgoing message mutator

var principalAccessor = new PrincipalAccessor();
endpointConfiguration.RegisterComponents(
    registration: components =>
    {
        components.RegisterSingleton<IPrincipalAccessor>(principalAccessor);
        components.ConfigureComponent<AddUserNameToOutgoingHeadersMutator>(DependencyLifecycle.InstancePerCall);
    });

Incoming message mutator

The incoming mutator extracts the username header from the message and set the principalAccessor.CurrentPrincipal.

public class SetCurrentPrincipalBasedOnHeaderMutator :
    IMutateIncomingTransportMessages
{
    static ILog log = LogManager.GetLogger("Handler");
    readonly IPrincipalAccessor principalAccessor;

    public SetCurrentPrincipalBasedOnHeaderMutator(IPrincipalAccessor principalAccessor)
    {
        this.principalAccessor = principalAccessor;
    }

    public Task MutateIncoming(MutateIncomingTransportMessageContext context)
    {
        if (context.Headers.TryGetValue("UserName", out var userNameHeader))
        {
            log.Info("Adding CurrentPrincipal user from headers");
            var identity = new GenericIdentity(userNameHeader);
            principalAccessor.CurrentPrincipal = new GenericPrincipal(identity, new string[0]);
        }

        return Task.CompletedTask;
    }
}

Register the incoming message mutator

endpointConfiguration.RegisterComponents(c =>
{
    c.RegisterSingleton<IPrincipalAccessor>(new PrincipalAccessor());
    c.ConfigureComponent<SetCurrentPrincipalBasedOnHeaderMutator>(DependencyLifecycle.InstancePerCall);
});

This sample doesn't register the outgoing message mutator for the receiver. If desired, the outgoing message mutator could be registered on the receiver as well, which would automatically add the username header to all messages sent.

The Handler

From within a handler (or saga), this value can be used as follows:

public class HandlerUsingAccessor :
    IHandleMessages<MyMessage>
{
    static ILog log = LogManager.GetLogger("HandlerUsingAccessor");
    readonly IPrincipalAccessor principalAccessor;

    public HandlerUsingAccessor(IPrincipalAccessor principalAccessor)
    {
        this.principalAccessor = principalAccessor;
    }

    public Task Handle(MyMessage message, IMessageHandlerContext context)
    {
        var headers = context.MessageHeaders;
        var usernameFromHeader = headers["UserName"];
        var usernameFromAccessor = principalAccessor?.CurrentPrincipal?.Identity?.Name ?? "null";
        log.Info($"Username extracted from header: {usernameFromHeader}");
        log.Info($"Username extracted from accessor: {usernameFromAccessor}");
        return Task.CompletedTask;
    }
}

Related Articles


Last modified