See how to enhance the security on NServiceBus endpoints by
using generic and elegant code.
On a previous post, we discussed why as developers, it's important to consider security. Then we discussed how to secure our Asp.Net MVC application using a mix of custom role/claims providers. Today well's see how we can protect our NServiceBus endpoints.
NServiceBus
NServiceBus (NSB) is a messaging and workflow for .NET and .NET Core that,Supports a variety of messaging patterns and workflows on multiple transports like MSMQ, RabbitMQ, Azure, and Amazon SQS. Developers focus on core logic, fully abstracted from the underlying infrastructure. Runs on .NET or .NET Core on Windows, Linux, or in Docker containers.In other words, It helps us separating the concerns in our application, create simpler services using distributed messaging, simplify our workflow, easy to test and helping us implement interesting design patterns like CQRS.
Securing NserviceBus Endpoints
But how could we integrate a robust, generic security authorization in a backend?
On previous post we discussed how to implement
SecurityController, a db-independent security mechanism to our Asp.Net website. In order to use
it in our business layer, we need to inject its dependencies (roles and
groups) via its Init
factory method described below:
public class SecurityController
{
public static void Init(List<Role> roles, List<Group> groups)
{
allRoles = roles ?? new List<Role>();
allGroups = groups ?? new List<Group>();
allPerms = allRoles.SelectMany(r => r.Permissions).Distinct().ToList();
}
}
{
public static void Init(List<Role> roles, List<Group> groups)
{
allRoles = roles ?? new List<Role>();
allGroups = groups ?? new List<Group>();
allPerms = allRoles.SelectMany(r => r.Permissions).Distinct().ToList();
}
}
How would we integrate our custom groups and roles in the NSB backend?
NServiceBus.INeedInitialization
Within NSB, we can use INeedInitialization interface to hook our code in the NSB initialization pipeline. By using INeedInitialization you are supposed to implement this inerface:
public void Customize(BusConfiguration busConfiguration)
Our custom SecurityRegistry
When we initialize our NSB endpoint, we know that NSB will invoke classes that
implement that interface, including our own
SecurityRegistry:
public class SecurityRegistry : INeedInitialization
{
public void Customize(BusConfiguration busConfiguration)
{
// todo :: load roles, groups from your repository...
// init the security controller...
SecurityController.Init(roles, groups);
}
}
{
public void Customize(BusConfiguration busConfiguration)
{
// todo :: load roles, groups from your repository...
// init the security controller...
SecurityController.Init(roles, groups);
}
}
Now, let's review how do we plug our validator in the NSB pipeline so it gets
executed whenever a new message is executed in our service layer.
NServiceBus.IMutateIncomingMessages
The elegant way of doing that using NSB is trough
message mutators. Yes, a fancy name for a plugin, filter or whatever else you want to name
it. Message mutators allow we injecting our own business logic before the
messages reach your NSB Message handler. With mutators, you can mutate
incoming and outcoming messages. Since we're talking about security
here, don't think you wanna mutate outcoming messages, right
Creating a MessageMutator
So, let's go back to the code: we know that we need to create a class that
would implement IMutateIncomingMessages where we want to validate
commands submitted to our service layer. Please, just note that, in order for
this to automatically work, we will need:
- a base generic Command where we can now beforehand who submitted what, when;
- a common user;
- the permission associated to a particular message;
- access to our SecurityController that will validate if that user has access to submit that command or not.
The code below shows a sample validator implementing
IMutateIncomingMessages:
public class PermissionValidator : IMutateIncomingMessages
{
private CommandBase cmd;
private User user;
public object MutateIncoming(object message)
{
ValitatePermission(message);
return message;
}
private void ValitatePermission(object message)
{
cmd = message as CommandBase;
if (cmd == null)
return;
// tries to load custom security permissions from command
var pa = cmd.GetType().GetCustomAttribute(typeof (RequirePermissionAttribute), true) as RequirePermissionAttribute;
// if class not decorated with RequirePermission, nothing to validate
if (pa == null)
return;
var user = LoadUser();
if (user is null)
throw new SecurityException("Is the User null?");
ValidatePermission(pa.Permission);
}
private User LoadUser()
{
if (cmd.SubmittedBy == null)
return null;
// todo :: load our user from repo...
return user;
}
private void ValidatePermission(string permission)
{
// todo :: add your custom permission validation...
}
}
{
private CommandBase cmd;
private User user;
public object MutateIncoming(object message)
{
ValitatePermission(message);
return message;
}
private void ValitatePermission(object message)
{
cmd = message as CommandBase;
if (cmd == null)
return;
// tries to load custom security permissions from command
var pa = cmd.GetType().GetCustomAttribute(typeof (RequirePermissionAttribute), true) as RequirePermissionAttribute;
// if class not decorated with RequirePermission, nothing to validate
if (pa == null)
return;
var user = LoadUser();
if (user is null)
throw new SecurityException("Is the User null?");
ValidatePermission(pa.Permission);
}
private User LoadUser()
{
if (cmd.SubmittedBy == null)
return null;
// todo :: load our user from repo...
return user;
}
private void ValidatePermission(string permission)
{
// todo :: add your custom permission validation...
}
}
The RequirePermissionAttribute
The last part in the puzzle is how to automatically map permissions to
commands. This can be easily done by creating a custom attribute like:
public class RequirePermissionAttribute : Attribute
{
public string Permission { get; set; }
/// <summary>
/// Validates only if the SubmittedBy is an existing User
/// </summary>
public RequirePermissionAttribute()
{
}
/// <summary>
/// Validates if the SubmittedBy is an existing User AND if that user has specified permission
/// </summary>
/// <param name="p"></param>
public RequirePermissionAttribute(string p)
{
Permission = p;
}
}
{
public string Permission { get; set; }
/// <summary>
/// Validates only if the SubmittedBy is an existing User
/// </summary>
public RequirePermissionAttribute()
{
}
/// <summary>
/// Validates if the SubmittedBy is an existing User AND if that user has specified permission
/// </summary>
/// <param name="p"></param>
public RequirePermissionAttribute(string p)
{
Permission = p;
}
}
That attribute could be now used to decorate commands present in our service
layer like:
[RequirePermissionAttribute(Permissions.UpdateUser)]
public class UpdateUser : ContentCommandBase
{
[Required]
[StringLength(100)]
public string Name { get; set; }
// etc...
}
public class UpdateUser : ContentCommandBase
{
[Required]
[StringLength(100)]
public string Name { get; set; }
// etc...
}
Associating permissions and Commands
The last step is to build the association between permissions and commands. The line below does exactly that. The code binds permission to its associated commands:
// load custom security permissions from command
var pa = cmd.GetType().GetCustomAttribute(typeof (RequirePermissionAttribute), true) as RequirePermissionAttribute;
var pa = cmd.GetType().GetCustomAttribute(typeof (RequirePermissionAttribute), true) as RequirePermissionAttribute;
Conclusion
On this post we reviewed how we can inject our custom security logic into a
messaging framework like NServiceBus. Since most of these frameworks have
extension points in their pipelines, it shouldn't be complicated to do a
similar approach with
MassTransit, for example. If want to read other posts about NServiceBus, please also consider:
References
See Also
- My journey to 1 million articles read
- Adding Application Insights to your ASP.NET Core website
- Send emails from ASP.NET Core websites using SendGrid and Azure
- NServiceBus backends to Azure WebJobs
- MassTransit, a real alternative to NServiceBus?
- Async Request/Response with MassTransit, RabbitMQ, Docker and .NET core
- Creating ASP.NET Core websites with Docker
- Configuration in .NET Core console applications
- ASP.NET - Anonymous requests on secure endpoints