dotnet / AspNetCore.Docs

Documentation for ASP.NET Core
https://docs.microsoft.com/aspnet/core
Creative Commons Attribution 4.0 International
12.57k stars 25.3k forks source link

Configuring SignalR Core hub's bearer token auth #12340

Open HappyNomad opened 5 years ago

HappyNomad commented 5 years ago

The Bearer token authentication section says:

In standard web APIs, bearer tokens are sent in an HTTP header. However, SignalR is unable to set these headers in browsers when using some transports. When using WebSockets and Server-Sent Events, the token is transmitted as a query string parameter. In order to support this on the server, additional configuration is required...

The doc then continues with an entire ConfigureServices method implementation from a sample app.

I configured my .NET client app to provide a token, and it's correctly doing so, but I see it's arriving at the hub in the authentication header. I confirmed the connection is using WebSockets as the transport. The token isn't in the query string as the doc says. Without adding any of the bits from the doc's sample ConfigureServices method implementation, the claims are available in the hub.

That's nice it worked, but it's also confusing since I don't understand why it worked. Why do the docs say I must "Read the token out of the query string", and provide code for doing so, but it's unnecessary in my case. Is my scenario somehow unusual?

I'm using Azure AD B2C so my ConfigureServices method resembles the one in the Asp.Net Core 2.2 template for "Individual Accounts" authentication. I wasn't sure which part from the sample ConfigureServices implementation I'd need, so I got started by adding the Authorize attribute to my SignalR Core hub. I expected an auth exception to be thrown, but there wasn't one! The claims are populated, without any of the configuration shown in the doc.


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

BrennanConroy commented 5 years ago

The doc was specifically talking about browsers. It does not apply to the .NET Client as that does not run in a browser.

We can probably make that more clear.

HappyNomad commented 5 years ago

Upon rereading my question right after posting it, the word browser in the quote jumped out at me. I see it now, thanks. But yes, please make it clearer.

kiril-chilingarashvili commented 5 years ago

Am I the only one who is concerned with sending a token in a query string? This is huge security issue, unless I am missing something?

HappyNomad commented 5 years ago

@kirill-chilingarashvili You're not the only one. I saw related discussion on SignalR Core's dev repo. Look over there. While not ideal, remember that at least it's encrypted by TLS.

vamboda commented 5 years ago

Having bearer tokens part of query string is not just bad from the standpoint of web security, but also from server side logging. Most WebServers are by default configured to log the URL along with query string in the logs, which poses a huge risk of spilling bearer tokens in the logs. It would be a great if there is a better alternative than cookie authentication.

miroslavp commented 4 years ago

@BrennanConroy Now that we have running .NET Client in the browser, shouldn't we expect the same behavior as for the javascript client? I see no query parameter passed to the hub

BrennanConroy commented 4 years ago

I think you're referring to https://github.com/dotnet/aspnetcore/issues/18697

jelard commented 4 years ago

Followed the above instructions to set up bearer authentication for a .net signalr core client and was getting 401 unathorized. After some debugging, found out that the token was being sent through the authorization header and not the query string. Then tried to set the context.Token from the authorization header and was now getting a 500 internal error. Finally, it worked when I stripped the Bearer from the token that I got from the authorization header. Did something changed that made the above instruction not work?

BrennanConroy commented 4 years ago

You don't need to touch the context.Token if the token is already present in the authorization header. The auth middleware will automatically look there. The sample code in the doc shows setting the token only if the query string "access_token" is set which is needed in some cases in the browser due to browser APIs not allowing the authorize header to be set.

jelard commented 4 years ago

Thank you. It still works when I commented out setting the context.Token. I would also like to mention that I had to set the [Authorize(AuthenticationSchemes = AzureADDefaults.JwtBearerAuthenticationScheme)] in my hub to make it work. Otherwise I am getting the "Invalid negotiation response received error" on the client.

qlagunero commented 3 years ago

This cost me a lot hours trying to make it work. I have an Angular Client which I have implemented JWT Auth and created a Gateway whereas it also includes the Identity Service. I have following:

Angular Client: this.connection = new signalR.HubConnectionBuilder() .withUrl("localhost:2", { accessTokenFactory: () => this.loginToken }) .build();

Gateway: localhost:1 Api1: localhost:2 (contains signalR hub) Api2: localhost:3 (contains signalR hub)

I have followed the document from Microsoft but unable to make it work. (https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.0)

I had even added a middleware from the Gateway, just to add the Bearer Token from the Request in Gateway to Api1 and Api2, but still not working.

I also added Authorize attribute from the Hub class.

As a temporary solution. I had made the Api1 and Api2 hostname public and consume this directly from Angular Client.

Not working: Angular Client-> Gateway -> Api1 Angular Client-> Gateway -> Api2

Working: Angular Client-> Api1 Angular Client-> Api2

This is not the appropriate way because it bypassed the gateway. I had to make a fast solution because I have a tight deadline. Hope there is an alternative way similar to my case? :)