I am trying to get Owin working in an asp.net web app with Azure AD. Here is my startup code that configures Owin.
public class Startup
{
// The Client ID is used by the application to uniquely identify itself to Microsoft identity platform.
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
// RedirectUri is the URL where the user will be redirected to after they sign in.
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
// Tenant is the tenant ID (e.g. contoso.onmicrosoft.com, or 'common' for multi-tenant)
static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
// Authority is the URL for authority, composed by Microsoft identity platform endpoint and the tenant name (e.g. https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0)
string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
/// <summary>
/// Configure OWIN to use OpenIdConnect
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.IdToken,
// ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false // This is a simplification
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
MessageReceived = OnMessageReceived
}
}
);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
private Task OnMessageReceived(MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
string tmp = notification.ProtocolMessage.ErrorDescription;
return Task.FromResult(0);
}
}
This code does not work unless I enable implicit flow in Azure AD. I don't want to use implicit flow because of security reasons. I would rather use authorization flow. What changes do I need to make, so that this code will work without enable implicit flow in Azure?
According to your code, you use OpenID Connect protocol to integrate Azure AD auth in your application. If so, the app registration in the AD portal must have the implicit grant of id_tokens enabled in the Authentication tab (which sets the oauth2AllowIdTokenImplicitFlow flag in the application manifest to true). So that users can successfully request an ID token from the /authorization endpoint. If we do not do that, we will get the unsupported_response error. For more details, please refer to here.
Related
the below is working code.
But I need to implement the solution without sing coockies (UseCookieAuthentication).
After Azure authentication, when we check the AuthenticationTyoe in Identity it shown as Cookies, which i need to avoid. any suggetions/other implementations
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the code id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.CodeIdToken,
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
I know there are several threads on the same but none of the solution works for me, is there any solution. The strange thing is when I run the application in IE it works where as in Edge I got into this issue
IDX21323: RequireNonce is '[PII is hidden]'.
OpenIdConnectProtocolValidationContext.Nonce was null,
OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The
nonce cannot be validated. If you don't need to check the nonce, set
OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a
'nonce' is found it will be evaluated.
Here is the code
public class Startup
{
// The Client ID is used by the application to uniquely identify itself to Azure AD.
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
// RedirectUri is the URL where the user will be redirected to after they sign in.
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
// Tenant is the tenant ID (e.g. contoso.onmicrosoft.com, or 'common' for multi-tenant)
static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
// Authority is the URL for authority, composed by Microsoft identity platform endpoint and the tenant name (e.g. https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0)
string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
/// <summary>
/// Configure OWIN to use OpenIdConnect
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the code id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.CodeIdToken,
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
}
I tired the options available but nothing works, is there any possible solution for this
Referred to this OpenIdConnectProtocolValidationContext.Nonce was null
Please try updating your Microsoft.Owin.Security.OpenIdConnect package to match the version numbers on your other Owin packages, as discussed in this related thread.
Microsoft.Owin [4.1.1]
Microsoft.Owin.Security [4.1.1]
Microsoft.Owin.Security.Cookies [4.1.1]
Make sure you have also updated to the latest version of Chrome if you are using Google Chrome, or test in other browsers. (This guide discusses an issue with Chrome that was causing this error.)
I was tasked with writing an ASP.NET website that uses Azure Active Directory. I went with the route of OAuth and OpenID Connect. I am not able to use implicit flow and therefore must set the ResponseType to be code.
Using MSAL code samples I got most of it working but the problem is that all the samples are using a response type that returns tokens. I think I need to do it in 2 separate steps, first get the authorization code and then get the id token. I'm not exactly sure how to do this and would much appreciate some guidance here.
I have a startup class that look like this:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = authority,
ClientId = clientId,
RedirectUri = redirectUri,
Scope = "openid profile email offline_access user.readbasic.all", // a basic set of permissions for user sign in & profile access
ResponseType = OpenIdConnectResponseType.Code,
ClientSecret = clientSecret,
TokenValidationParameters = new TokenValidationParameters
{
// In a real application you would use ValidateIssuer = true for additional checks and security.
ValidateIssuer = false,
NameClaimType = "name",
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
}
});
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
// Handle any unexpected errors during sign in
context.OwinContext.Response.Redirect("/Error?message=" + context.Exception.Message);
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = GroupManager.Utils.MsalAppBuilder.BuildConfidentialClientApplication(null);
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(new[] { "openid profile email offline_access user.readbasic.all" }, context.Code).ExecuteAsync();
}
How do I take the information from the result's tokens and create a claims identity for the AuthenticationTicket.Identity and access the user info?
Please note that this is an ASP.NET application. Not MVC and not Core.
If you use MSAL, you don't need to handle the code yourself. MSAL will return the token to you after you log in interactively, please see:Overview of Microsoft Authentication Library (MSAL).
Before that, you need to take a look at Add sign-in to Microsoft to an ASP.NET web app,the workflow is:
Code example please check: https://github.com/AzureAdQuickstarts/AppModelv2-WebApp-OpenIDConnect-DotNet
Update:
Try to enable ID token
The following code below is used to authenticate users in ADFS 2016 and to request an Access Token for the resource defined in cp.APIBaseURL:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var cp = UnityConfig.Container.Resolve<IConfigurationProvider>();
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = cp.ClientId,
MetadataAddress = cp.MetadataAddress,
RedirectUri = cp.RedirectUri,
PostLogoutRedirectUri = cp.PostLogoutRedirectUri,
ResponseType = "code id_token",
Scope = "openid",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived
}
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var cp = UnityConfig.Container.Resolve<IConfigurationProvider>();
AuthenticationContext ac = new AuthenticationContext(
configurationProvider.Authority, false,
new InMemoryTokenCache(context.AuthenticationTicket.Identity.Name));
AuthenticationResult ar = await ac.AcquireTokenByAuthorizationCodeAsync(
context.Code, new Uri(cp.RedirectUri),
new ClientCredential(cp.ClientId, cp.ClientSecretKey),
cp.APIBaseURL);
}
I would like to know how to change the code to request a 2nd Access Token for a different API (having a different audience)?
Can I also specify different scopes for the 2nd Access Token I need?
You can use result = await ac.AcquireTokenSilentAsync(resource, clientId); to request the access token for different resouces. Refer here for more details.
Can I also specify different scopes for the 2nd Access Token I need?
No,for v1(adal) Azure AD apps, scopes must be statically configured in the Azure Portal under the API permissions, configured permissions.
I have two application...... one is JavaScript signalR client and the other one is asp.net web application used as signalR server to broadcast the updates to the client. And I was trying to use azure active directory b2c service to offer authentication and authorization for user through client application to access the resources in the server. So, that only the authenticated user of JavaScript client can initiate signalR connection with the asp.net web application hosting signalR server after the token validation.
As, signalR uses web-sockets we cannot supply the token in the HTTP connection request header. It seems that I should use query string to supply authentication token in the signalR connection request.
After receiving that token in the asp.net server application I need to validate that token and allow the JavaScript client application to have a signalR connection.
I want to implement exactly the same thing in this blog post https://kwilson.io/blog/authorize-your-azure-ad-users-with-signalr/ but using azure active directory b2c.
It seems like others might also have same problem using ASP.NET SignalR Client and server architecture.
Actually, with lots of efforts I was able to solve this issue by customizing the AuthorizeModule of signalR hubs. Actually I override AuthorizeHubConnection() and AuthorizeHubMethodInvocation() using AuthorizeAttribute inheritance in CustomAuthorization class.
First of all I added the GlobalHost.HubPipeline.AddModule(module) in app.Map("/signalr", map =>{ .... } in startup Configuration. You can see it in the following startup.cs.
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Owin;
using Microsoft.AspNet.SignalR;
using TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration;
using Microsoft.AspNet.SignalR.Hubs;
[assembly: OwinStartup(typeof(TestCarSurveillance.RealTimeCommunication.Startup))]
namespace TestCarSurveillance.RealTimeCommunication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
//After adding Authorization module in GlobalHost.HubPipeline.AddModule(module)
//program was unable to create the log file so I have added it.
log4net.Config.XmlConfigurator.Configure();
// Branch the pipeline here for requests that start with "/signalr"
//app.UseWelcomePage("/");
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableDetailedErrors = true,
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
EnableJSONP = true
};
// Require authentication for all hubs
var authorizer = new CustomAuthorization();
var module = new AuthorizeModule(authorizer, authorizer);
GlobalHost.HubPipeline.AddModule(module);
map.RunSignalR(hubConfiguration);
});
}
}
}
This Authorize module calls CustomAuthorize.cs class in each signalR hub OnConnected(), OnDisconnected(), OnReconnected() and hub methods that the client can call.
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.AspNet.SignalR.Owin;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
namespace TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration
{
public class CustomAuthorization : AuthorizeAttribute
{
// These values are pulled from web.config for b2c authorization
public static string aadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string signUpInPolicy = ConfigurationManager.AppSettings["ida:SignUpInPolicyId"];
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
//This method is called multiple times before the connection with signalR is established.
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
var metadataEndpoint = string.Format(aadInstance, tenant, signUpInPolicy);
// Extract JWT token from query string.
var userJwtToken = request.QueryString.Get("Authorization");
if (string.IsNullOrEmpty(userJwtToken))
{
return false;
}
// Validate JWT token.
//var tokenValidationParameters = new TokenValidationParameters { ValidAudience = ClientId };
//Contains a set of parameters that are used by a SecurityTokenHandler when validating a SecurityToken.
TokenValidationParameters tvps = new TokenValidationParameters
{
// Accept only those tokens where the audience of the token is equal to the client ID of this app
// This is where you specify that your API only accepts tokens from its own clients
// here the valid audience is supplied to check against the token's audience
ValidAudience = clientId,
ValidateIssuer = false,
// It is the authentication scheme used for token validation
AuthenticationType = signUpInPolicy,
//SaveSigninToken = true,
//I’ve configured the “NameClaimType” of the “TokenValidationParameters” to use the claim named “objectidentifer” (“oid”)
//This will facilitate reading the unique user id for the authenticated user inside the controllers, all we need to call
//now inside the controller is: “User.Identity.Name” instead of querying the claims collection each time
//Gets or sets a String that defines the NameClaimType.
NameClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier"
};
try
{
var jwtFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint));
var authenticationTicket = jwtFormat.Unprotect(userJwtToken);
if(authenticationTicket != null && authenticationTicket.Identity !=null && authenticationTicket.Identity.IsAuthenticated)
{
var email = authenticationTicket.Identity.FindFirst(p => p.Type == "emails").Value;
// It is done to call the async method from sync method
//the ArgumentException will be caught as you’d expect, because .GetAwaiter().GetResult() unrolls the first exception the same way await does.
//This approach follows the principle of least surprise and is easier to understand.
// set the authenticated user principal into environment so that it can be used in the future
request.Environment["server.User"] = new ClaimsPrincipal(authenticationTicket.Identity);
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
log.Error(ex);
//throw ex;
}
return false;
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;
//Check the authenticated user principal from environment
var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
//ClaimsPrincipal supports multiple claims based identities
var principal = environment["server.User"] as ClaimsPrincipal;
if(principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)
{
// create a new HubCallerContext instance with the principal generated from token
// and replace the current context so that in hubs we can retrieve current user identity
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);
return true;
}
return false;
}
}
}
After we receive the token from the query string we need to setup TokenValidationParameters use it in metadataEndpoint for token validation. The token validation is done in before having the hub connection so, that only the authorized user can have a connection and if the connection is not successful it returns 401 response. It is implemented in OpenIdConnectCachingSecurityTokenProvider.cs class. This class is being used by having following line of code in AuthorizeHubConnection() method.
var jwtFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint));
var authenticationTicket = jwtFormat.Unprotect(userJwtToken);
As, the last part of this authorization configuration I have inherited IIssureSecurityKeyProvider in OpenIdConnectCachingSecurityTokenProvider.cs class. The complete implementation of it can be seen in the following code.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
//using System.IdentityModel.Tokens;
namespace TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration
{
//IIssuerSecurityKeyProvider Interface Provides security Key information to the implementing class.
// This class is necessary because the OAuthBearer Middleware does not leverage
// the OpenID Connect metadata endpoint exposed by the STS by default.
internal class OpenIdConnectCachingSecurityTokenProvider : IIssuerSecurityKeyProvider
{
//Manages the retrieval of Configuration data.
public ConfigurationManager<OpenIdConnectConfiguration> _configManager;
private string _issuer;
private IEnumerable<SecurityKey> _keys;
//this class will be responsible for communicating with the “Metadata Discovery Endpoint” and issue HTTP requests to get the signing keys
//that our API will use to validate signatures from our IdP, those keys exists in the jwks_uri which can read from the discovery endpoint
private readonly string _metadataEndpoint;
//Represents a lock that is used to manage access to a resource, allowing multiple threads for reading or exclusive access for writing.
private readonly ReaderWriterLockSlim _synclock = new ReaderWriterLockSlim();
public OpenIdConnectCachingSecurityTokenProvider(string metadataEndpoint)
{
_metadataEndpoint = metadataEndpoint;
//_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint, new OpenIdConnectConfigurationRetriever());
_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint, new OpenIdConnectConfigurationRetriever());
//_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint);
RetrieveMetadata();
}
/// <summary>
/// Gets the issuer the credentials are for.
/// </summary>
/// <value>
/// The issuer the credentials are for.
/// </value>
public string Issuer
{
get
{
RetrieveMetadata();
_synclock.EnterReadLock();
try
{
return _issuer;
}
finally
{
_synclock.ExitReadLock();
}
}
}
/// <summary>
/// Gets all known security keys.
/// </summary>
/// <value>
/// All known security keys.
/// </value>
public IEnumerable<SecurityKey> SecurityKeys
{
get
{
RetrieveMetadata();
_synclock.EnterReadLock();
try
{
return _keys;
}
finally
{
_synclock.ExitReadLock();
}
}
}
private void RetrieveMetadata()
{
_synclock.EnterWriteLock();
try
{
//Task represents an asynchronous operation.
//Task.Run Method Queues the specified work to run on the ThreadPool and returns a task or Task<TResult> handle for that work.
OpenIdConnectConfiguration config = Task.Run(_configManager.GetConfigurationAsync).Result;
_issuer = config.Issuer;
_keys = config.SigningKeys;
}
finally
{
_synclock.ExitWriteLock();
}
}
}
}
After implementing this we do not need to have [Authorize] attribute in any hub method and this middle-ware will handle the request authorization and only authorized user will have a signalR connection and only authorized user can invoke the hub method.
At last I would like to mention that for this client server architecture to work we need to have separate b2c tenant client application and b2c tenant server application and b2c tenant client application should have API access to the b2c tenant server application. Azure b2c application should be configured as in this example https://learn.microsoft.com/en-us/aspnet/core/security/authentication/azure-ad-b2c-webapi?view=aspnetcore-2.1
Although, it is for .net core but it is also valid for asp.net and only difference is that b2c configuration should be at web.config