Azure + SignalR - Secure hubs to different connection types - azure

I have two hubs in a web role,
1) external facing hub meant to be consumed over https external endpoint for website users.
2) intended to be connected to over http on an internal endpoint by worker roles.
I would like the ability to secure access to the hubs somehow.
Is there anyway I can check to see what connection type the connecting user/worker role is using and accept/deny based on this?
Another method I thought of was perhaps using certificate authentication on the internal hubs but i'd rather not have to for speed etc.
GlobalHost.DependencyResolver.UseServiceBus(connectionString, "web");
// Web external connection
app.MapSignalR("/signalr", new HubConfiguration()
{ EnableJavaScriptProxies = true, EnableDetailedErrors = false });
// Worker internal connection
app.MapSignalR("/signalr-internal", new HubConfiguration()
{ EnableJavaScriptProxies = false, EnableDetailedErrors = true});
EDIT: I've included my own answer

A simple solution you can use roles of client to distinguish between to connections
object GetAuthInfo()
{
var user = Context.User;
return new
{
IsAuthenticated = user.Identity.IsAuthenticated,
IsAdmin = user.IsInRole("Admin"),
UserName = user.Identity.Name
};
}
also other options are fully described here

I ended up probing the request environment variables and checking the servers localPort and request scheme in a custom AuthorizeAttribute. The only downside to this at the moment is that the javascript proxies will still generate the restricted hub info. But i'm working on that :).
I'll leave the question open for a bit to see if anyone can extend on this.
public class SignalrAuthorizeAttribute : Microsoft.AspNet.SignalR.AuthorizeAttribute, Microsoft.AspNet.SignalR.IDependencyResolver
{
public override bool AuthorizeHubConnection(Microsoft.AspNet.SignalR.Hubs.HubDescriptor hubDescriptor, Microsoft.AspNet.SignalR.IRequest request)
{
bool isHttps = request.Environment["owin.RequestScheme"].ToString().Equals("https", StringComparison.OrdinalIgnoreCase) ? true : false;
bool internalPort = request.Environment["server.LocalPort"].ToString().Equals("2000") ? true : false;
switch(hubDescriptor.Name)
{
// External Hubs
case "masterHub":
case "childHub":
if (isHttps && !internalPort) return base.AuthorizeHubConnection(hubDescriptor, request);
break;
// Internal hubs
case "workerInHub":
case "workerOutHub":
if (!isHttps && internalPort) return base.AuthorizeHubConnection(hubDescriptor, request);
break;
default:
break;
}
return false;
}
}

Related

Validate the connection string of Microsoft Azure Service Bus

I have a Microsoft Azure Service Bus connection string that I want to validate if it is a valid one. I want to validate the topics and subscriptions as well if they exist. If the connection string, topics, and subscriptions are invalid then we can take appropriate actions.
The sample connection string can be like - TransportType=AmqpWebSockets;Endpoint=sb://myservicebus.servicebus.windows.net/;SharedAccessKeyName=sendkey;SharedAccessKey=MQHnx3voLhH/xVgoamX3KijzkZ0qb7U6oHTolj7LM9H=;
I tried ServiceBusClient but it is not having such support. Is there a way we can validate the connection string and topics/subscriptions?
The client will validate that the connection string and entity names are well-formed, but there is no dedicated operation for testing its ability to communicate with Service Bus.
Connections and links are established lazily when the first network operation that requires them is invoked. There are two approaches that occur to me which would trigger network resource creation without being intrusive.
Create a message batch
In order to ensure that the batch size does not exceed the limits for a given resource, the ServiceBusSender must query the queue/topic. In the case where the client is misconfigured, it will throw.
// In a real scenario, you would want to create these as
// singletons and reuse them for the lifetime of the application.
await using var client = new ServiceBusClient("<< Connection String>>");
await using var sender = client.CreateSender("<< Queue/Topic >>");
var valid = true;
try
{
using var batch = await sender.CreateMessageBatchAsync().ConfigureAwait(false);
}
catch (Exception ex)
when (ex is ServiceBusException
|| ex is IOException
|| ex is SocketException)
{
valid = false;
}
Peek a message
Peeking a message will not cause the message to be locked or otherwise impact receiving it.
await using var client = new ServiceBusClient("<< Connection String>>");
await using var receiver = client.CreateReceiver("<< Queue/Subscription >>");
var valid = true;
try
{
_ = await receiver.PeekMessageAsync().ConfigureAwait(false);
}
catch (Exception ex)
when (ex is ServiceBusException
|| ex is IOException
|| ex is SocketException)
{
valid = false;
}
Interactions with each queue, topic, and subscription make use of a dedicated AMQP link, so each would need to be tested individually if you'd like to ensure that the name matches a known Service Bus entity.

Messages not coming thru to Azure SignalR Service

I'm implementing Azure SignalR service in my ASP.NET Core 2.2 app with React front-end. When I send a message, I'm NOT getting any errors but my messages are not reaching the Azure SignalR service.
To be specific, this is a private chat application so when a message reaches the hub, I only need to send it to participants in that particular chat and NOT to all connections.
When I send a message, it hits my hub but I see no indication that the message is making it to the Azure Service.
For security, I use Auth0 JWT Token authentication. In my hub, I correctly see the authorized user claims so I don't think there's any issues with security. As I mentioned, the fact that I'm able to hit the hub tells me that the frontend and security are working fine.
In the Azure portal however, I see no indication of any messages but if I'm reading the data correctly, I do see 2 client connections which is correct in my tests i.e. two open browsers I'm using for testing. Here's a screen shot:
Here's my Startup.cs code:
public void ConfigureServices(IServiceCollection services)
{
// Omitted for brevity
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = authority;
jwtOptions.Audience = audience;
jwtOptions.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// Check to see if the message is coming into chat
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/im")))
{
context.Token = accessToken;
}
return System.Threading.Tasks.Task.CompletedTask;
}
};
});
// Add SignalR
services.AddSignalR(hubOptions => {
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(10);
}).AddAzureSignalR(Configuration["AzureSignalR:ConnectionString"]);
}
And here's the Configure() method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Omitted for brevity
app.UseSignalRQueryStringAuth();
app.UseAzureSignalR(routes =>
{
routes.MapHub<Hubs.IngridMessaging>("/im");
});
}
Here's the method I use to map a user's connectionId to the userName:
public override async Task OnConnectedAsync()
{
// Get connectionId
var connectionId = Context.ConnectionId;
// Get current userId
var userId = Utils.GetUserId(Context.User);
// Add connection
var connections = await _myServices.AddHubConnection(userId, connectionId);
await Groups.AddToGroupAsync(connectionId, "Online Users");
await base.OnConnectedAsync();
}
Here's one of my hub methods. Please note that I'm aware a user may have multiple connections simultaneously. I just simplified the code here to make it easier to digest. My actual code accounts for users having multiple connections:
[Authorize]
public async Task CreateConversation(Conversation conversation)
{
// Get sender
var user = Context.User;
var connectionId = Context.ConnectionId;
// Send message to all participants of this chat
foreach(var person in conversation.Participants)
{
var userConnectionId = Utils.GetUserConnectionId(user.Id);
await Clients.User(userConnectionId.ToString()).SendAsync("new_conversation", conversation.Message);
}
}
Any idea what I'm doing wrong that prevents messages from reaching the Azure SignalR service?
It might be caused by misspelled method, incorrect method signature, incorrect hub name, duplicate method name on the client, or missing JSON parser on the client, as it might fail silently on the server.
Taken from Calling methods between the client and server silently fails
:
Misspelled method, incorrect method signature, or incorrect hub name
If the name or signature of a called method does not exactly match an appropriate method on the client, the call will fail. Verify that the method name called by the server matches the name of the method on the client. Also, SignalR creates the hub proxy using camel-cased methods, as is appropriate in JavaScript, so a method called SendMessage on the server would be called sendMessage in the client proxy. If you use the HubName attribute in your server-side code, verify that the name used matches the name used to create the hub on the client. If you do not use the HubName attribute, verify that the name of the hub in a JavaScript client is camel-cased, such as chatHub instead of ChatHub.
Duplicate method name on client
Verify that you do not have a duplicate method on the client that differs only by case. If your client application has a method called sendMessage, verify that there isn't also a method called SendMessage as well.
Missing JSON parser on the client
SignalR requires a JSON parser to be present to serialize calls between the server and the client. If your client doesn't have a built-in JSON parser (such as Internet Explorer 7), you'll need to include one in your application.
Update
In response to your comments, I would suggest you try one of the Azure SignalR samples, such as
Get Started with SignalR: a Chat Room Example to see if you get the same behavior.
Hope it helps!

Using Azure B2C with an MVC app gets into infinite loop resulting with Bad Request - Request Too Long Http 400 error

So I've built and published a new website that uses Azure B2C as the authentication mechanism.
What I found was that the login and sign would work fine for a while. But after a period of time, say couple of hours after visiting the site post deployment, I would find that on login or signup, after successful authentication, instead of being redirected back to the return url set up in the b2c configuration, my browser would get caught between an infinite loop between the post authentication landing page that is protected with an authorise attribute and the Azure B2C Login page, before finally finishing with Http 400 error message with the message - Bad Request - Request too long.
I did some googling around this and there are number of posts that suggest that the problem is with the cookie, and that deleting the cookie should resolve the issue. This is not the case. The only thing I have found to fix this is restarting the application on the webserver, or waiting say 24 hours for some kind of cache or application pool to reset. Anyone has any ideas what's going on here?
Ok, I think I may have found the answer.
Looks like there is an issue with Microsoft.Owin library and the way it sets cookies. Writing directly to System.Web solves this problem according to this article.
There are three suggested solutions:
Ensure session is established prior to authentication: The conflict between System.Web and Katana cookies is per request, so it may be possible for the application to establish the session on some request prior to the authentication flow. This should be easy to do when the user first arrives, but it may be harder to guarantee later when the session or auth cookies expire and/or need to be refreshed.
Disable the SessionStateModule: If the application is not relying on session information, but the session module is still setting a cookie that causes the above conflict, then you may consider disabling the session state module.
Reconfigure the CookieAuthenticationMiddleware to write directly to System.Web's cookie collection.
I will opt for the third option, which is to overwrite the default Cookie AuthenticationMiddleware, as they have suggested below.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});
public class SystemWebCookieManager : ICookieManager
{
public string GetRequestCookie(IOwinContext context, string key)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
var cookie = webContext.Request.Cookies[key];
return cookie == null ? null : cookie.Value;
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
bool pathHasValue = !string.IsNullOrEmpty(options.Path);
bool expiresHasValue = options.Expires.HasValue;
var cookie = new HttpCookie(key, value);
if (domainHasValue)
{
cookie.Domain = options.Domain;
}
if (pathHasValue)
{
cookie.Path = options.Path;
}
if (expiresHasValue)
{
cookie.Expires = options.Expires.Value;
}
if (options.Secure)
{
cookie.Secure = true;
}
if (options.HttpOnly)
{
cookie.HttpOnly = true;
}
webContext.Response.AppendCookie(cookie);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
AppendResponseCookie(
context,
key,
string.Empty,
new CookieOptions
{
Path = options.Path,
Domain = options.Domain,
Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
});
}
}
I will give that a crack, and post my results back here.

Public Database between chrome extensions

I want to created a public database so other extensions can access it, create tables, add entities, remove entities what they want.
I saw that the only way to do this is to use message passing between multiple extensions, but this solutions is problematic for me, because I need permission to "management" in order to know the other extensions IDs.
There is an option for sending messages to all extensions without knowing their ID? or there is another way of implementing public db without pub-sub synchronization?
btw - I can use localStorage or WebSQL.
Could you create an extension, hub, that is used to register other extensions and has a messaging hub.
All of the extensions that wanted to communicate with the public DB could then do it via the hub. Upon initialization from the background page, each extension could register with the hub their ID and which events they want to subscribe to.
Register action from each extension
chrome.tabs.sendRequest("hub", {
action: "register",
key: "somePrivKey",
id: "extId",
subscribeTo: ["createFoo", "deleteFoo"]
});
Then, each action performed would be communicated to the hub:
chrome.tabs.sendRequest("hub", {
action: "createFoo",
key: "somePrivKey",
context: 1
});
The hub extension would then listen to events. For "register" actions the hub would register the extension as an endpoint for the "subscribeTo" actions. For other actions ("createFoo" or "deleteFoo") the hub would iterate over the list of registered extensions for the event and perform a sendRequest that sends the "action" name and an optional "context".
A shared "key" could be known between the hub and all the extensions that want to communicate to prevent the hub from listening to events not from a known source.
Hub extension background.js:
var actionToExtMap = {};
chrome.extension.onRequestExternal.addListener(function(request, sender, sendResponse) {
if (request.key === "somePrivKey") {
if (request.action === "register") {
for (i = 0; i < request.subscribeTo.length; i++) {
var action = request.subscribeTo[i];
var extsionsForAction = actionToExtMap[action] || [];
extsionsForAction.push(request.id)
}
} else if (request.action) {
var extensionsToSendAction = actionToExtMap[request.action];
for (i = 0; i < extensionsToSendAction.length; i++) {
chrome.extension.sendRequest(extensionsToSendAction[i], {
action: request.action,
context: request.context //pass an option context object
}
}
}
}
});

How to detect if the environment is staging or production in azure hosted service worker role?

I have a worker role in my hosted service.
The worker is sending e-mail daily bases.
But in the hosted service, there are 2 environment, Staging and Production.
So my worker role sends e-mail 2 times everyday.
I'd like to know how to detect if the worker is in stagning or production.
Thanks in advance.
As per my question here, you'll see that there is no fast way of doing this. Also, unless you really know what you are doing, I strongly suggest you not do this.
However, if you want to, you can use a really nice library (Azure Service Management via C#) although we did have some trouble with WCF using it.
Here's a quick sample on how to do it (note, you need to include the management certificate as a resource in your code & deploy it to Azure):
private static bool IsStaging()
{
try
{
if (!CloudEnvironment.IsAvailable)
return false;
const string certName = "AzureManagement.pfx";
const string password = "Pa$$w0rd";
// load certificate
var manifestResourceStream = typeof(ProjectContext).Assembly.GetManifestResourceStream(certName);
if (manifestResourceStream == null)
{
// should we panic?
return true;
}
var bytes = new byte[manifestResourceStream.Length];
manifestResourceStream.Read(bytes, 0, bytes.Length);
var cert = new X509Certificate2(bytes, password);
var serviceManagementChannel = Microsoft.Toolkit.WindowsAzure.ServiceManagement.ServiceManagementHelper.
CreateServiceManagementChannel("WindowsAzureServiceManagement", cert);
using (new OperationContextScope((IContextChannel)serviceManagementChannel))
{
var hostedServices =
serviceManagementChannel.ListHostedServices(WellKnownConfiguration.General.SubscriptionId);
// because we don't know the name of the hosted service, we'll do something really wasteful
// and iterate
foreach (var hostedService in hostedServices)
{
var ad =
serviceManagementChannel.GetHostedServiceWithDetails(
WellKnownConfiguration.General.SubscriptionId,
hostedService.ServiceName, true);
var deployment =
ad.Deployments.Where(
x => x.PrivateID == Zebra.Framework.Azure.CloudEnvironment.CurrentRoleInstanceId).
FirstOrDefault
();
if (deployment != null)
{
return deployment.DeploymentSlot.ToLower().Equals("staging");
}
}
}
return false;
}
catch (Exception e)
{
// if something went wrong, let's not panic
TraceManager.AzureFrameworkTraceSource.TraceData(System.Diagnostics.TraceEventType.Error, "Exception", e);
return false;
}
}
If you're using an SQL server (either Azure SQL or SQL Server hosted in VM), you could stop the Staging worker role from doing work by only allowing the public IP of the Production instance access to the database server.

Resources