Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AspNet.Core, IdentityServer 4: Unauthorized (401) during websocket handshake with SignalR 1.0 using JWT bearer token

I have two aspnet.core services. One for IdentityServer 4, and one for the API used by Angular4+ clients. The SignalR hub runs on the API. The whole solution runs on docker but that should not matter (see below).

I use implicit auth flow which works flawlessly. The NG app redirects to the login page of IdentityServer where the user logs in. After that the browser is redirected back to the NG app with the access token. The token is then used to call the API and to build up the communication with SignalR. I think I've read everything that is available (see sources below).

Since SignalR is using websockets that does not support headers, the token should be sent in the querystring. Then on the API side the token is extracted and set for the request just as it was in the header. Then the token is validated and the user is authorized.

The API works without any problem the users gets authorized and the claims can be retrieved on the API side. So there should be no problem with the IdentityServer then since SignalR does not need any special configuration. Am I right?

When I do not use the [Authorized] attribute on the SignalR hub the handshake succeeds. This is why I think there is nothing wrong with the docker infrastructure and reverse proxy I use (the proxy is set to enable websockets).

So, without authorization SignalR works. With authorization the NG client gets the following response during handshake:

Failed to load resource: the server responded with a status of 401
Error: Failed to complete negotiation with the server: Error
Error: Failed to start the connection: Error

The request is

Request URL: https://publicapi.localhost/context/negotiate?signalr_token=eyJhbGciOiJSUz... (token is truncated for simplicity)
Request Method: POST
Status Code: 401 
Remote Address: 127.0.0.1:443
Referrer Policy: no-referrer-when-downgrade

The response I get:

access-control-allow-credentials: true
access-control-allow-origin: http://localhost:4200
content-length: 0
date: Fri, 01 Jun 2018 09:00:41 GMT
server: nginx/1.13.10
status: 401
vary: Origin
www-authenticate: Bearer

According to the logs, the token is validated successfully. I can include the full logs however I suspect where the problem is. So I will include that part here:

[09:00:41:0561 Debug] Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler AuthenticationScheme: Identity.Application was not authenticated.
[09:00:41:0564 Debug] Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler AuthenticationScheme: Identity.Application was not authenticated.

I get these in the log file and I am not sure what it means. I include the code part on the API where I get and extract the token along with the authentication configuration.

services.AddAuthentication(options =>
    {
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultSignOutScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddIdentityServerAuthentication(options =>
        {
            options.Authority = "http://identitysrv";
            options.RequireHttpsMetadata = false;
            options.ApiName = "publicAPI";
            options.JwtBearerEvents.OnMessageReceived = context =>
            {
                if (context.Request.Query.TryGetValue("signalr_token", out StringValues token))
                {
                    context.Options.Authority = "http://identitysrv";
                    context.Options.Audience = "publicAPI";
                    context.Token = token;
                    context.Options.Validate();
                }

                return Task.CompletedTask;
            };
        });

There are no other errors, exceptions in the system. I can debug the app and everything seems to be fine.

What does the included log lines mean? How can I debug what is going on during the authorization?

EDIT: I almost forgot to mention, that I thought the problem was with the authentication schemes so, I set every scheme to the one I think was needed. However sadly it did not help.

I am kind of clueless here, so I appreciate any suggestion. Thanks.

Sources of information:

Pass auth token to SignalR

Securing SignalR with IdentityServer

Microsoft docs on SignalR authorization

Another GitHub question

Authenticate against SignalR

Identity.Application was not authenticated

like image 837
Daniel Leiszen Avatar asked Jun 01 '18 09:06

Daniel Leiszen


1 Answers

I know this is an old thread, but in case someone stumbles upon this like I did. I found an alternative solution.

TLDR: JwtBearerEvents.OnMessageReceived, will catch the token before it is checked when used as illustrated below:

public void ConfigureServices(IServiceCollection services)
{
    // Code removed for brevity
    services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
    .AddIdentityServerAuthentication(options =>
    {
        options.Authority = "https://myauthority.io";
        options.ApiName = "MyApi";
        options.JwtBearerEvents = 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/myhubname")))
                {
                    // Read the token out of the query string
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    });
}

This Microsoft doc gave me a hint: https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1. However, in the Microsoft example, options.Events is called, because it is not an example using IdentityServerAuthentication. If options.JwtBearerEvents is used the same way as options.Events in the Microsoft example, IdentityServer4 is happy!

like image 164
Alex Gemma Avatar answered Sep 28 '22 06:09

Alex Gemma