Microservices Not responding after subscribing to RabbitMQ fanout exchange - rabbitmq

My .net core3.1 web application has say 4 microservices (MasterMS, PartyMS, ProductMS, PurchaseMS)
and uses Rabbitmq as message broker.
In one specific scenario, the MasterMS publishes an event (insert/update in Company table) to Rabbitmq exchange (xAlexa), from where it is fanned-out to the respective queues of all subscribing MSs (PartyMS, ProductMS).
PartyMS get the event from CompanyEventPartyMS queue and ProductMS gets it from CompanyEventProductMS queue. Thereby both Party and Product updates their respective Company table and everything is in Sync and perfect. Btw, PurchaseMS is not subscribing and so not bothered.
Now comes the real problem.. The subscribing MSs (Consumers) does not respond when their web page is requested. PartyMS and ProductMS webpages throws SocketException, while the non-subscriber PurchaseMS works fine. Now if i Comment out the line where PartyMS subscribes, it starts working again though it no longer gets the CompanyEvent and goes out-of-sync.
Any insights friends ?
SocketException: No connection could be made because the target machine actively refused it.
System.Net.Http.ConnectHelper.ConnectAsync(string host, int port, CancellationToken cancellationToken)
public void Publish<T>(T #event) where T : Event
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "xAlexa", type: ExchangeType.Fanout);
var message = JsonConvert.SerializeObject(#event);
var body = Encoding.UTF8.GetBytes(message);
var eventName = #event.GetType().Name;
channel.BasicPublish(exchange: "xAlexa",
routingKey: eventName, //string.Empty,
basicProperties: null,
body: body);
}
}
StartBasicConsume
private void StartBasicConsume<T>() where T : Event
{
var factory = new ConnectionFactory()
{
HostName = "localhost",
DispatchConsumersAsync = true
};
var connection = factory.CreateConnection();
var channel = connection.CreateModel();
var eventName = typeof(T).Name;
var msName = typeof(T).FullName;
string[] str = { };
str = msName.Split('.');
eventName += str[1];
channel.ExchangeDeclare(exchange: "xAlexa",
type: ExchangeType.Fanout);
channel.QueueDeclare(eventName, true, false, false, null); //channel.QueueDeclare().QueueName;
channel.QueueBind(queue: eventName,
exchange: "xAlexa",
routingKey: string.Empty);
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += Consumer_Received;
channel.BasicConsume(eventName, true, consumer);
Console.WriteLine("Consumer Started");
Console.ReadLine();
}
private async Task Consumer_Received(object sender, BasicDeliverEventArgs e)
{
var eventName = e.RoutingKey;
var body = e.Body.ToArray();
//var body = e.Body.Span;
var message = Encoding.UTF8.GetString(body);
//var message = Encoding.UTF8.GetString(e.Body);
Console.WriteLine(message);
try
{
await ProcessEvent(eventName, message).ConfigureAwait(false);
}
catch (Exception ex)
{
}
}
The call to ProductsMS Api from MVC app (here is where it fails if subscribed and works if not subscribed to CompanyEvent !)
public class ProductService:IProductService
{
private readonly HttpClient _apiCLient;
public ProductService(HttpClient apiCLient)
{
_apiCLient = apiCLient;
}
public async Task<List<Product>> GetProducts()
{
var uri = "https://localhost:5005/api/ProductApi";
List<Product> userList = new List<Product>();
HttpResponseMessage response = await _apiCLient.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var readTask = response.Content.ReadAsStringAsync().Result;
userList = JsonConvert.DeserializeObject<List<Product>>(readTask);
}
return userList;
}
}
Find ProductsMS Api Startup.cs below:
namespace Alexa.ProductMS.Api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var connectionString = Configuration["DbContextSettings:ConnectionString"];
var dbPassword = Configuration["DbContextSettings:DbPassword"];
var builder = new NpgsqlConnectionStringBuilder(connectionString)
{
Password = dbPassword
};
services.AddDbContext<ProductsDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));
services.AddMediatR(typeof(Startup));
RegisterServices(services);
}
private void RegisterServices(IServiceCollection services)
{
DependencyContainer.RegisterServices(services);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
ConfigureEventBus(app); //WORKS IF COMMENTED; FAILS OTHERWISE <---
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<CompanyEvent, CompanyEventHandler>();
eventBus.Subscribe<PartyEvent, PartyEventHandler>();
}
}
}
Also see the images:
RabbitMQ fanout exchange
RabbitMQ Queues
exchange
queues

Remove the last line Console.ReadLine(); of the method StartBasicConsume().
When we are using that line in the function it is waiting for any key press or any input.

Related

Masstransit and RabbitMQ to Console Application (use Asp.net Core Web API)

I created a Solution with ASP.NET Core WEB API project, some class libraries (Domain, DI and ect), and a console application.
A console application that I use as a RabbitMQ Consumer with Masstransit library it should take messages from RabbitMQ (I have Producer project and it sends for RabbitMQ messages without problems)
My ConsoleApplication:
like this Program.cs:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(x =>
{
x.AddConsumer<MessageConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
var connectionString = new Uri("RabbitMQ_URL");
cfg.Host(connectionString);
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService(true);
services.AddHostedService<Worker>();
});
}
With Worker.cs:
public class Worker : BackgroundService
{
readonly IBus _bus;
public Worker(IBus bus)
{
_bus = bus;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var factory = new ConnectionFactory() { Uri = new
Uri("RabbitMQ_URL"), DispatchConsumersAsync = true };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "MessageQueue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
var #event = JsonConvert.DeserializeObject<Event>
(message);
await _bus.Publish(new Event { DataJson = #event });
await Task.Yield();
};
channel.BasicConsume(queue: "MessageQueue",
autoAck: true,
consumer: consumer);
_logger.LogInformation("Received Text: {Text}", context.Message.DataJson);
return Task.CompletedTask;
}
}
}
MessageConsumer.cs:
public class MessageConsumer :
IConsumer<Event>
{
readonly ILogger<MessageConsumer> _logger;
public MessageConsumer(ILogger<MessageConsumer> logger)
{
_logger = logger;
}
public Task Consume(ConsumeContext<Event> context)
{
_logger.LogInInformation("Recieved Text: {Text},
context.Message.DataJson");
return Task.CompletedTask;
}
}
And my Event.cs:
public class Event
{
public ServiceType ServiceType { get; set; }
public string DataJson { get; set; }
}
public enum ServiceType
{
ComplareSitter
}
Please help me
Thanks a lot.
You might start with a clean and simple worker service using one of the MassTransit templates, just to verify your setup/configuration. There is a video available showing how to setup and use the templates.
But an obvious question, why on earth are you connecting to RabbitMQ and creating a basic consumer inside the Consume method? The message has already been deserialized as your Event type and is ready to be used. There is absolutely no need to use any part of the RabbitMQ Client library in your application when using MassTransit.

SignalR + token authorization via a remote service

I'm making a SignalR-based service with a token-based authorization. The token is validated by an external API, which returns the user's ID. Then, notifications are sent to users with specific IDs.
The trouble that I can't get around is that SignalR's client code apparently sends 2 requests: one without a token (authentication fails) and the other with a token (authentication succeeds). For some reason, the first result gets cached and the user does not receive any notifications.
If I comment the checks and always return the correct ID, even if there's no token specified, the code suddenly starts working.
HubTOkenAuthenticationHandler.cs:
public class HubTokenAuthenticationHandler : AuthenticationHandler<HubTokenAuthenticationOptions>
{
public HubTokenAuthenticationHandler(
IOptionsMonitor<HubTokenAuthenticationOptions> options,
ILoggerFactory logFactory,
UrlEncoder encoder,
ISystemClock clock,
IAuthApiClient api
)
: base(options, logFactory, encoder, clock)
{
_api = api;
}
private readonly IAuthApiClient _api;
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
// uncommenting this line makes everything suddenly work
// return SuccessResult(1);
var token = GetToken();
if (string.IsNullOrEmpty(token))
return AuthenticateResult.NoResult();
var userId = await _api.GetUserIdAsync(token); // always returns 1
return SuccessResult(userId);
}
catch (Exception ex)
{
return AuthenticateResult.Fail(ex);
}
}
/// <summary>
/// Returns an identity with the specified user id.
/// </summary>
private AuthenticateResult SuccessResult(int userId)
{
var identity = new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.Name, userId.ToString())
}
);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
/// <summary>
/// Checks if there is a token specified.
/// </summary>
private string GetToken()
{
const string Scheme = "Bearer ";
var auth = Context.Request.Headers["Authorization"].ToString() ?? "";
return auth.StartsWith(Scheme)
? auth.Substring(Scheme.Length)
: "";
}
}
Startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<FakeNotificationService>();
services.AddSingleton<IAuthApiClient, FakeAuthApiClient>();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
services.AddAuthentication(opts =>
{
opts.DefaultAuthenticateScheme = HubTokenAuthenticationDefaults.AuthenticationScheme;
opts.DefaultChallengeScheme = HubTokenAuthenticationDefaults.AuthenticationScheme;
})
.AddHubTokenAuthenticationScheme();
services.AddRouting(opts =>
{
opts.AppendTrailingSlash = false;
opts.LowercaseUrls = false;
});
services.AddSignalR(opts => opts.EnableDetailedErrors = true);
services.AddControllers();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseRouting();
app.UseAuthentication();
app.UseEndpoints(x =>
{
x.MapHub<InfoHub>("/signalr/info");
x.MapControllers();
});
}
}
FakeNotificationsService.cs (Sends a notification to user "1" every 2 seconds):
public class FakeNotificationService: IHostedService
{
public FakeNotificationService(IHubContext<InfoHub> hubContext, ILogger<FakeNotificationService> logger)
{
_hubContext = hubContext;
_logger = logger;
_cts = new CancellationTokenSource();
}
private readonly IHubContext<InfoHub> _hubContext;
private readonly ILogger _logger;
private readonly CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
// run in the background
Task.Run(async () =>
{
var id = 1;
while (!_cts.Token.IsCancellationRequested)
{
await Task.Delay(2000);
await _hubContext.Clients.Users(new[] {"1"})
.SendAsync("NewNotification", new {Id = id, Date = DateTime.Now});
_logger.LogInformation("Sent notification " + id);
id++;
}
});
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_cts.Cancel();
return Task.CompletedTask;
}
}
Debug.cshtml (client code):
<html>
<head>
<title>SignalRPipe Debug Page</title>
</head>
<body>
<h3>Notifications log</h3>
<textarea id="log" cols="180" rows="40"></textarea>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/5.0.11/signalr.min.js"
integrity="sha512-LGhr8/QqE/4Ci4RqXolIPC+H9T0OSY2kWK2IkqVXfijt4aaNiI8/APVgji3XWCLbE5J0wgSg3x23LieFHVK62g=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script language="javascript">
var token = "123";
var conn = new signalR
.HubConnectionBuilder()
.withUrl('/signalr/info', { accessTokenFactory: () => token })
.configureLogging(signalR.LogLevel.Debug)
.build();
var logElem = document.getElementById('log');
var id = 1;
function log(text) {
logElem.innerHTML = text + '\n\n' + logElem.innerHTML;
}
conn.on("NewNotification", alarm => {
log(`[Notification ${id}]:\n${JSON.stringify(alarm)}`);
id++;
});
conn.start()
.then(() => log('Connection established.'))
.catch(err => log(`Connection failed:\n${err.toString()}`));
</script>
</body>
</html>
Minimal repro as a runnable project:
https://github.com/impworks/signalr-auth-problem
I tried the following, to no success:
Adding a fake authorization handler which just allows everything
Extracting the debug view to a separate project (express.js-based server)
What is that I'm missing here?
It doesn't look like you're handling the auth token coming from the query string which is required in certain cases such as a WebSocket connection from the browser.
See https://learn.microsoft.com/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0#built-in-jwt-authentication for some info on how bearer auth should be handled.
Issue solved. As #Brennan correctly guessed, WebSockets do not support headers, so the token is passed via query string instead. We just need a little code to get the token from either source:
private string GetHeaderToken()
{
const string Scheme = "Bearer ";
var auth = Context.Request.Headers["Authorization"].ToString() ?? "";
return auth.StartsWith(Scheme)
? auth.Substring(Scheme.Length)
: null;
}
private string GetQueryToken()
{
return Context.Request.Query["access_token"];
}
And then, in HandleAuthenticateAsync:
var token = GetHeaderToken() ?? GetQueryToken();

Can a SignalR Hub receive events from clients? And if so, how?

I have a signalR hub that needs to be able to receive an event from a client and then notify all other clients connected to the hub.
Is that possible?
I want my 'hub' application to be able to receive messages and send them. I can only figure out how to do the sending of messages. Here is what I have now:
Application 1-- Hub
Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR().AddHubOptions<EventsHub>(options =>
{
options.HandshakeTimeout = TimeSpan.FromMinutes(5);
options.EnableDetailedErrors = true;
});
services.AddTransient(typeof(BusinessLogic.EventsBusinessLogic));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSignalR((configure) =>
{
configure.MapHub<EventsHub>("/hubs/events", (options) =>
{
});
});
}
Set Up of the Hub in Application 1
public class EventsHub : Hub
{
public EventsHub()
{
}
public override Task OnConnectedAsync()
{
if (UserHandler.ConnectedIds.Count == 0)
{
//Do something on connect
}
UserHandler.ConnectedIds.Add(Context.ConnectionId);
Console.WriteLine("Connection:");
return base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
//Do something on Disconnect
}
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
}
BusinessLogic:
public class EventsBusinessLogic
{
private readonly IHubContext<EventsHub> _eventsHub;
public EventsBusinessLogic(IHubContext<EventsHub> eventsHub)
{
_eventsHub = eventsHub;
}
public async Task<Task> EventReceivedNotification(ProjectMoonEventLog eventInformation)
{
try
{
await _eventsHub.Clients.All.SendAsync("NewEvent", SomeObject);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
}
In the second application, that listens for events or messages from the hub:
Startup.cs
private static void ConfigureAppServices(IServiceCollection services, string Orale, string Sql)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddOptions();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//set up of singletons and transients
services.AddHostedService<Events.EventingHubClient>();
}
The ClientHub to connect to application 1:
public class EventingHubClient : IHostedService
{
private HubConnection _connection;
public EventingHubClient()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:61520/hubs/events")
.Build();
_connection.On<Event>("NewEvent",
data => _ = EventReceivedNotification(data));
}
public async Task<Task> EventReceivedNotification(Event eventInformation)
{
try
{
//Do something when the event happens
return Task.CompletedTask;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// Loop is here to wait until the server is running
while (true)
{
try
{
await _connection.StartAsync(cancellationToken);
Console.WriteLine("Connected");
break;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
await Task.Delay(100);
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _connection.DisposeAsync();
}
}
This works, but now I want application 2 to be able to send a message to application 1? So I need a similar piece of code as in the EventsBusinessLogic class in application2 to send messages to application 1.
I hope this is clear enough? Is this the purpose of SignalR?
Please refer to signalR documentation signalR documentation for .net client
I guess in your Hub method like this
public async Task SendTransaction(Transaction data)
{
await Clients.All.SendAsync("TransactionReceived", data);
}
Then add methods in client side
in constructor add
connection.On<Transaction>("TransactionReceived", (data) =>
{
this.Dispatcher.Invoke(() =>
{
var transactionData = data;
});
});
and then SendTransaction expected on server
private async void SendTransaction(Transaction data)
{
try
{
await connection.InvokeAsync("SendTransaction", data);
}
catch (Exception ex)
{
//
throw
}
}

SignalR core not working with cookie Authentication

I cant seem to get SignalR core to work with cookie authentication. I have set up a test project that can successfully authenticate and make subsequent calls to a controller that requires authorization. So the regular authentication seems to be working.
But afterwards, when I try and connect to a hub and then trigger methods on the hub marked with Authorize the call will fail with this message: Authorization failed for user: (null)
I inserted a dummy middleware to inspect the requests as they come in. When calling connection.StartAsync() from my client (xamarin mobile app), I receive an OPTIONS request with context.User.Identity.IsAuthenticated being equal to true. Directly after that OnConnectedAsync on my hub gets called. At this point _contextAccessor.HttpContext.User.Identity.IsAuthenticated is false. What is responsible to de-authenticating my request. From the time it leaves my middleware, to the time OnConnectedAsync is called, something removes the authentication.
Any Ideas?
Sample Code:
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await this._next(context);
//At this point context.User.Identity.IsAuthenticated == true
}
}
public class TestHub: Hub
{
private readonly IHttpContextAccessor _contextAccessor;
public TestHub(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public override async Task OnConnectedAsync()
{
//At this point _contextAccessor.HttpContext.User.Identity.IsAuthenticated is false
await Task.FromResult(1);
}
public Task Send(string message)
{
return Clients.All.InvokeAsync("Send", message);
}
[Authorize]
public Task SendAuth(string message)
{
return Clients.All.InvokeAsync("SendAuth", message + " Authed");
}
}
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyContext>(options => options.UseInMemoryDatabase(databaseName: "MyDataBase1"));
services.AddIdentity<Auth, MyRole>().AddEntityFrameworkStores<MyContext>().AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options => {
options.Password.RequireDigit = false;
options.Password.RequiredLength = 3;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.User.RequireUniqueEmail = true;
});
services.AddSignalR();
services.AddTransient<TestHub>();
services.AddTransient<MyMiddleware>();
services.AddAuthentication();
services.AddAuthorization();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<MyMiddleware>();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseSignalR(routes =>
{
routes.MapHub<TestHub>("TestHub");
});
app.UseMvc(routes =>
{
routes.MapRoute(name: "default", template: "{controller=App}/{action=Index}/{id?}");
});
}
}
And this is the client code:
public async Task Test()
{
var cookieJar = new CookieContainer();
var handler = new HttpClientHandler
{
CookieContainer = cookieJar,
UseCookies = true,
UseDefaultCredentials = false
};
var client = new HttpClient(handler);
var json = JsonConvert.SerializeObject((new Auth { Name = "craig", Password = "12345" }));
var content = new StringContent(json, Encoding.UTF8, "application/json");
var result1 = await client.PostAsync("http://localhost:5000/api/My", content); //cookie created
var result2 = await client.PostAsync("http://localhost:5000/api/My/authtest", content); //cookie tested and works
var connection = new HubConnectionBuilder()
.WithUrl("http://localhost:5000/TestHub")
.WithConsoleLogger()
.WithMessageHandler(handler)
.Build();
connection.On<string>("Send", data =>
{
Console.WriteLine($"Received: {data}");
});
connection.On<string>("SendAuth", data =>
{
Console.WriteLine($"Received: {data}");
});
await connection.StartAsync();
await connection.InvokeAsync("Send", "Hello"); //Succeeds, no auth required
await connection.InvokeAsync("SendAuth", "Hello NEEDSAUTH"); //Fails, auth required
}
If you are using Core 2 try changing the order of UseAuthentication, place it before the UseSignalR method.
app.UseAuthentication();
app.UseSignalR...
Then inside the hub the Identity property shouldn't be null.
Context.User.Identity.Name
It looks like this is an issue in the WebSocketsTransport where we don't copy Cookies into the websocket options. We currently copy headers only. I'll file an issue to get it looked at.

NServiceBus : Identity context in outgoing message mutators

NServiceBus 4.4.0
Hi !
I use message mutators for impersonification purpose. Basicaly, I serialize the ClaimsPrincipal.Identity in a JWT token in the outgoing mutator and deserialize it in the incoming mutator to add it to the principal of the NServiceBus host app. (based on this article : http://beingabstract.com/2014/10/serializing-the-claimsidentity/). The problem is that when we're in the outgoing mutator (IMutateOutgoingTransportMessages) the ClaimsPrincipal.Identity doesn't contain all the claims. Only the name. But if I'm looking just before the "Bus.Send" command, I have the correct claims (groups, permissions, etc.).
The outgoing message mutator resides in an outside class, which is referenced by my main project. Here's the code from the outgoing mutator :
public class OutgoingAccessMutator : IMutateOutgoingTransportMessages, INeedInitialization
{
public IBus Bus { get; set; }
public void Init()
{
Configure.Instance.Configurer.ConfigureComponent<OutgoingAccessMutator>(DependencyLifecycle.InstancePerCall);
}
public void MutateOutgoing(object[] messages, TransportMessage transportMessage)
{
if (!transportMessage.Headers.ContainsKey(Common.Constants.Securite.AuthenticationTokenID))
{
transportMessage.Headers[Common.Constants.Securite.AuthenticationTokenID] =
TokenHelper.GenerateToken(ClaimsPrincipal.Current.IdentitePrincipale() as ClaimsIdentity);
}
}
}
The GenerateToken is in a static helper class in the mutator dll :
public static string GenerateToken(ClaimsIdentity identity)
{
var now = DateTime.UtcNow;
var tokenHandler = new JwtSecurityTokenHandler();
var securityKey = System.Text.Encoding.Unicode.GetBytes(Common.Constants.Securite.NServiceBusMessageTokenSymetricKey);
var inMemorySymmetricSecurityKey = new InMemorySymmetricSecurityKey(securityKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = identity,
TokenIssuerName = Common.Constants.Securite.NServiceBusMessageTokenIssuer,
AppliesToAddress = Common.Constants.Securite.NServiceBusMessageTokenScope,
Lifetime = new Lifetime(now, now.AddMinutes(5)),
SigningCredentials = new SigningCredentials(inMemorySymmetricSecurityKey, Common.Constants.Securite.SignatureAlgorithm, Common.Constants.Securite.DigestAlgorithm)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return tokenString;
}
Then in the incoming message mutator which lives in another process (windows service executable host), I deserialize it :
public class IncomingAccessTokenMutator : IMutateIncomingTransportMessages, INeedInitialization
{
public IBus Bus { get; set; }
public void Init()
{
Configure.Instance.Configurer.ConfigureComponent<IncomingAccessTokenMutator>(DependencyLifecycle.InstancePerCall);
}
public void MutateIncoming(TransportMessage transportMessage)
{
if (transportMessage.Headers.ContainsKey(Common.Constants.Securite.AuthenticationTokenID))
{
try
{
var token = transportMessage.Headers[Common.Constants.Securite.AuthenticationTokenID];
var identity = TokenHelper.ReadToken(token);
if (identity != null)
{
identity.Label = Common.Constants.Securite.NomIdentitePrincipale;
ClaimsPrincipal.Current.AddIdentity(identity);
}
}
catch (Exception)
{
Bus.DoNotContinueDispatchingCurrentMessageToHandlers();
throw;
}
}
}
}
Does anyone have any idea why the identity context is not the same inside the mutator and in the service that sends the message ?