Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net Core 3.1 SignalR Client - How to add the JWT Token string to SignalR connection configuration?

I am using the SignalR .net core client in my project with JWT Tokens.

In the sample code below, the string variable "tokenString" has already been configured as an actual token and therefore i don't need to call upon an external method to create the token, that part has already been done before I reach this method. Using debug, and also testing the "toeknString" value on JWT website, I know the token is working, its just the fact I dont know how to use the ready made token in the SignalR connection method.

How do I configure the SignalR client connection to use this tokenString?

localConConnection = new HubConnectionBuilder()
                .WithUrl("https://localhost:44372/LocalConnectorHub", options => 
                {
                    options.AccessTokenProvider = () => Task.FromResult(tokenString); // Not working
                    // Need a solution like this: options.Token = tokenString
                })
                .WithAutomaticReconnect()
                .Build();
like image 374
OJB1 Avatar asked Jan 26 '20 21:01

OJB1


People also ask

How do you implement authentication in SignalR?

When using the browser client, no additional configuration is needed. If the user is logged in to your app, the SignalR connection automatically inherits this authentication. Cookies are a browser-specific way to send access tokens, but non-browser clients can send them.

How do I send a message to a specific signal SignalR?

SignalR allows messages to be sent to a particular client connection, all connections associated with a specific user, as well as to named groups of connections. => await Clients. User(userId).

How do I update my SignalR?

Update your Package Manager to the latest version. Install the Web Platform Installer. In the Web Platform Installer, search for and install ASP.NET and Web Tools 2013.1 for Visual Studio 2012. This will install Visual Studio templates for SignalR classes such as Hub.


1 Answers

The issue was the fact that the [Authorize] attribute I had configured in the SignalR Hub class needed to define the authentication scheme to use, [Authorize] attribute alone was not enough.

SignalR Hub Class:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class LocalConnectorHub : Hub
{
    public async Task SendToMacros(string serverName, string data)
    {
        await Clients.All.SendAsync("MacrosInbound", serverName, data);
    }

    
    public async Task ConnectorStatus(string serverName, string data)
    {
        await Clients.All.SendAsync("UpdateConnectorStatus", serverName, data);
    }
}

SignalR .NET Core Client Connection:

localConConnection = new HubConnectionBuilder()
                .WithUrl("https://localhost:44372/LocalConnectorHub", options => 
                {
                    options.AccessTokenProvider = () => Task.FromResult(tokenString); 
                })
                .WithAutomaticReconnect()
                .Build();

await localConConnection.StartAsync();

Further example code from the startup.cs class (inside configure services method), this is posted to help one of our fellow members in the comments below:

// Retrieve the secret key from the appsettings.json file used for encryption 
// when generating the JWT token for REST API authentication.
var key = Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value);

// Added to original .net core template.
// The preceding code configures multiple authentication methods. The app uses cookie-based authentication to log in
// via the browser using the identity manager. The second methid uses JWT bearer authentication for the REST API.
// The preceding cookie configuration configures Identity with default option values. 
// Services are made available to the app through dependency injection.
// Cookie configuration MUST be called after calling AddIdentity or AddDefaultIdentity.
// IMPORTANT NOTE: 
// When we decorate controllers or classes with use the [Authorize] attribute, it actually binds to the first authentication 
// system by default (in this case cookie authentication) The trick is to change the attribute to specify which authorization
// service we want to use. Anexample for a protected respurce for a REST API controller would be to decorate using:
// "[Authorize(AuthenticationSchemes =  JwtBearerDefaults.AuthenticationScheme)]"
services.AddAuthentication()
.AddCookie(options =>
{
    // Cookie settings
    options.Cookie.Name = "MyCompanyName";

    // HttpOnly is a flag that can be used when setting a cookie to block access to the cookie from client side scripts. 
    // Javascript for example cannot read a cookie that has HttpOnly set. This helps mitigate a large part of XSS attacks 
    // as many of these attempt to read cookies and send them back to the attacker, possibly leaking sensitive information 
    // or worst case scenario, allowing the attacker to impersonate the user with login cookies.
    options.Cookie.HttpOnly = true;

    // CookieAuthenticationOptions.ExpireTimespan is the option that allows you to set how long the issued cookie is valid for.
    // The cookie is valid for (XX) minutes from the time of creation. Once those XX minutes are up the user will have to sign 
    // back in becuase if the SlidingExpiration is set to false.
    // If SlidingExpiration is set to true then the cookie would be re-issued on any request half way through the ExpireTimeSpan. 
    // For example, if the user logged in and then made a second request half way through the permitted timespan then the cookie 
    // would be re-issued for another (XX) minutes. If the user logged in and then made a second request AFTER (XX) minutes later 
    // then the user would be prompted to log in.
    // You can also change the units i.e. TimeSpan.FromHours(10); OR TimeSpan.FromDays(10);
    // In a nutshell, setting the options.ExpireTimeSpan is equivalent to setting an idle time out period...
    options.ExpireTimeSpan = TimeSpan.FromMinutes(10);

    options.LoginPath = "/Identity/Account/Login";
    options.AccessDeniedPath = "/Identity/Account/AccessDenied";

    // Sliding expiration resets the expiration time for a valid authentication cookie if a request is made and more than half of the 
    // timeout interval has elapsed.If the cookie expires, the user must re - authenticate.Setting the SlidingExpiration property to 
    // false can improve the security of an application by limiting the time for which an authentication cookie is valid, based on the 
    // configured timeout value.
    options.SlidingExpiration = true;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        // The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this 
        // claim is generally application specific. The "iss" value is a case-sensitive string containing 
        // a StringOrURI value.  Use of this claim is OPTIONAL.
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        
        // The "iss" (issuer) claim identifies the principal that issued the JWT.The processing of this 
        // claim is generally application specific. The "iss" value is a case-sensitive string containing 
        // a StringOrURI value.Use of this claim is OPTIONAL.
        ValidateIssuer = false,

        // Usually, this is your application base URL
        ValidIssuer = "http://localhost:45092/",

        // The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal 
        // intended to process the JWT MUST identify itself with a value in the audience claim.  If the principal
        // processing the claim does not identify itself with a value in the "aud" claim when this claim is present, 
        // then the JWT MUST be rejected.  In the general case, the "aud" value is an array of case-sensitive strings, 
        // each containing a StringOrURI value.  In the special case when the JWT has one audience, the "aud" value 
        // MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience 
        // values is generally application specific. Use of this claim is OPTIONAL.
        ValidateAudience = false,

        //Here, we are creating and using JWT within the same application.
        //In this case, base URL is fine.
        //If the JWT is created using a web service, then this would be the consumer URL.
        ValidAudience = "http://localhost:45092/",

        // The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted 
        // for processing. The processing of the "exp" claim requires that the current date/time MUST be before the 
        // expiration date/time listed in the "exp" claim.
        RequireExpirationTime = true,

        // Check if token is not expired and the signing key of the issuer is valid (ValidateLifetime = true)
        ValidateLifetime = true,
    };
    // We have to hook the OnMessageReceived event in order to
    // allow the JWT authentication handler to read the access
    // token from the query string when a WebSocket or 
    // Server-Sent Events request comes in.

    // Sending the access token in the query string is required due to
    // a limitation in Browser APIs. We restrict it to only calls to the
    // SignalR hub in this code.
    // See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
    // for more information about security considerations when using
    // the query string to transmit the access token.
    options.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            var accessToken = context.Request.Query["access_token"];

            // If the request is for our hub...
            var path = context.HttpContext.Request.Path;
            if (!string.IsNullOrEmpty(accessToken) &&
                (path.StartsWithSegments("/hubs")))
            {
                // Read the token out of the query string
                context.Token = accessToken;
            }
            return Task.CompletedTask;
        }
    };
});

Appsettings.json file (dont store keys here for production :)

"AppSettings": {
    "Token": "secret key for jwt"
}
like image 145
OJB1 Avatar answered Oct 09 '22 15:10

OJB1