Passing user identity between endpoints using a custom header

Component: NServiceBus
NuGet Package: NServiceBus (8-pre)
This page targets a pre-release version. Pre-releases are subject to change and samples are not guaranteed to be fully functional.

This sample demonstrates how to attach the current user identity (username) to all outgoing messages and how to extract that value when messages are received. User identity is accessed by a current principal accessor, registered through dependency injection.

This sample doesn't use Thread.CurrentPrincipal. When used in asynchronous code, Thread.CurrentPrincipal depends on the version of the .NET runtime. Refer to the Microsoft guidelines for more details.

Fake principal

The sample replaces the principalAccessor.CurrentPrincipal before sending a message with an ad-hoc value. In a production scenario, the principalAccessor.CurrentPrincipal would likely be set by the hosting environment e.g. IIS, and not in the user code.

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 asynchronous sends to demonstrate that the current principle is properly propagated into the message session.

Custom header with a mutator

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

Outgoing message mutator

The outgoing mutator extracts principalAccessor.CurrentPrincipal.Identity.Name and adds it to the message headers collection.

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();
var mutator = new AddUserNameToOutgoingHeadersMutator(principalAccessor);
endpointConfiguration.RegisterMessageMutator(mutator);

Incoming message mutator

The incoming mutator extracts the username header from the message and sets 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

var principalAccessor = new PrincipalAccessor();
var mutator = new SetCurrentPrincipalBasedOnHeaderMutator(principalAccessor);
endpointConfiguration.RegisterMessageMutator(mutator);

endpointConfiguration.RegisterComponents(c =>
{
    //Register the accessor in the container so that the handler can access it
    c.AddSingleton<IPrincipalAccessor>(principalAccessor);
});

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.

The Handler

From within a handler (or saga), the header value holding user identity can be accessed in the following way:

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