From fef0feb741a06f6bac0521533d5b5c1ab353ec26 Mon Sep 17 00:00:00 2001 From: Luke Latham Date: Thu, 22 Oct 2020 09:57:53 -0500 Subject: [PATCH 1/7] Blazor IdS with WebSockets --- .../hosted-with-identity-server.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md b/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md index c3daa50db65a..029e587092a5 100644 --- a/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md +++ b/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md @@ -550,6 +550,50 @@ If troubleshooting a certificate loading problem, execute the following command Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList ``` +## WebSockets and Server-Sent Events + +When the bearer token is transmitted as a query string parameter with WebSockets and Server-Sent Events, additional configuration is required. + +Add a service to the *`Server`* project: + +```csharp +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Options; + +public class ConfigureJwtBearerOptions : IPostConfigureOptions +{ + public void PostConfigure(string name, JwtBearerOptions options) + { + var originalOnMessageReceived = options.Events.OnMessageReceived; + + options.Events.OnMessageReceived = async context => + { + await originalOnMessageReceived(context); + + if (string.IsNullOrEmpty(context.Token)) + { + var accessToken = context.Request.Query["access_token"]; + var path = context.HttpContext.Request.Path; + + if (!string.IsNullOrEmpty(accessToken) && + path.StartsWithSegments("/hubs")) + { + context.Token = accessToken; + } + } + }; + } +} +``` + +Register the service in `Startup.ConfigureServices` after adding services for authentication () and the authentication handler for Identity Server (): + +```csharp +services.TryAddEnumerable( + ServiceDescriptor.Singleton, + ConfigureJwtBearerOptions>()); +``` + [!INCLUDE[](~/includes/blazor-security/troubleshoot.md)] ## Additional resources From 1640372c35aa7ce385c2c89528c111edeeffe8b3 Mon Sep 17 00:00:00 2001 From: Luke Latham Date: Thu, 22 Oct 2020 11:44:05 -0500 Subject: [PATCH 2/7] Update #1 --- aspnetcore/signalr/authn-and-authz.md | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/aspnetcore/signalr/authn-and-authz.md b/aspnetcore/signalr/authn-and-authz.md index 321a5cc164db..84281069f9a6 100644 --- a/aspnetcore/signalr/authn-and-authz.md +++ b/aspnetcore/signalr/authn-and-authz.md @@ -113,6 +113,46 @@ In standard web APIs, bearer tokens are sent in an HTTP header. However, SignalR > [!NOTE] > The query string is used on browsers when connecting with WebSockets and Server-Sent Events due to browser API limitations. When using HTTPS, query string values are secured by the TLS connection. However, many servers log query string values. For more information, see [Security considerations in ASP.NET Core SignalR](xref:signalr/security). SignalR uses headers to transmit tokens in environments which support them (such as the .NET and Java clients). +When using Identity Server, add a service to the project: + +```csharp +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Options; +public class ConfigureJwtBearerOptions : IPostConfigureOptions +{ + public void PostConfigure(string name, JwtBearerOptions options) + { + var originalOnMessageReceived = options.Events.OnMessageReceived; + options.Events.OnMessageReceived = async context => + { + await originalOnMessageReceived(context); + + if (string.IsNullOrEmpty(context.Token)) + { + var accessToken = context.Request.Query["access_token"]; + var path = context.HttpContext.Request.Path; + + if (!string.IsNullOrEmpty(accessToken) && + path.StartsWithSegments("/hubs")) + { + context.Token = accessToken; + } + } + }; + } +} +``` + +Register the service in `Startup.ConfigureServices` after adding services for authentication () and the authentication handler for Identity Server (): + +```csharp +services.AddAuthentication() + .AddIdentityServerJwt(); +services.TryAddEnumerable( + ServiceDescriptor.Singleton, + ConfigureJwtBearerOptions>()); +``` + ### Cookies vs. bearer tokens Cookies are specific to browsers. Sending them from other kinds of clients adds complexity compared to sending bearer tokens. Consequently, cookie authentication isn't recommended unless the app only needs to authenticate users from the browser client. Bearer token authentication is the recommended approach when using clients other than the browser client. From 83825da30311e62423882e0c11d2335d8edeafea Mon Sep 17 00:00:00 2001 From: Luke Latham Date: Thu, 22 Oct 2020 11:45:12 -0500 Subject: [PATCH 3/7] Update #2 --- .../hosted-with-identity-server.md | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md b/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md index 029e587092a5..c3daa50db65a 100644 --- a/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md +++ b/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md @@ -550,50 +550,6 @@ If troubleshooting a certificate loading problem, execute the following command Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList ``` -## WebSockets and Server-Sent Events - -When the bearer token is transmitted as a query string parameter with WebSockets and Server-Sent Events, additional configuration is required. - -Add a service to the *`Server`* project: - -```csharp -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.Options; - -public class ConfigureJwtBearerOptions : IPostConfigureOptions -{ - public void PostConfigure(string name, JwtBearerOptions options) - { - var originalOnMessageReceived = options.Events.OnMessageReceived; - - options.Events.OnMessageReceived = async context => - { - await originalOnMessageReceived(context); - - if (string.IsNullOrEmpty(context.Token)) - { - var accessToken = context.Request.Query["access_token"]; - var path = context.HttpContext.Request.Path; - - if (!string.IsNullOrEmpty(accessToken) && - path.StartsWithSegments("/hubs")) - { - context.Token = accessToken; - } - } - }; - } -} -``` - -Register the service in `Startup.ConfigureServices` after adding services for authentication () and the authentication handler for Identity Server (): - -```csharp -services.TryAddEnumerable( - ServiceDescriptor.Singleton, - ConfigureJwtBearerOptions>()); -``` - [!INCLUDE[](~/includes/blazor-security/troubleshoot.md)] ## Additional resources From e4fabd88a3cdea037f954d8e3002f662b7131a31 Mon Sep 17 00:00:00 2001 From: Luke Latham Date: Thu, 22 Oct 2020 11:49:43 -0500 Subject: [PATCH 4/7] Update signalr-blazor-webassembly.md --- aspnetcore/tutorials/signalr-blazor-webassembly.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aspnetcore/tutorials/signalr-blazor-webassembly.md b/aspnetcore/tutorials/signalr-blazor-webassembly.md index a3d18dc6a9ec..298d01e21542 100644 --- a/aspnetcore/tutorials/signalr-blazor-webassembly.md +++ b/aspnetcore/tutorials/signalr-blazor-webassembly.md @@ -368,4 +368,5 @@ To learn more about building Blazor apps, see the Blazor documentation: ## Additional resources * -* [SignalR cross-origin negotiation for authentication](xref:blazor/fundamentals/additional-scenarios#signalr-cross-origin-negotiation-for-authentication) \ No newline at end of file +* [SignalR cross-origin negotiation for authentication](xref:blazor/fundamentals/additional-scenarios#signalr-cross-origin-negotiation-for-authentication) +* [Bearer token authentication with Identity Server, WebSockets, and Server-Sent Events](xref:signalr/authn-and-authz#bearer-token-authentication) From 29ce809af4fae805e9990b177bd316d1d789fb55 Mon Sep 17 00:00:00 2001 From: Luke Latham Date: Thu, 22 Oct 2020 11:52:52 -0500 Subject: [PATCH 5/7] Update signalr-blazor-webassembly.md --- aspnetcore/tutorials/signalr-blazor-webassembly.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/tutorials/signalr-blazor-webassembly.md b/aspnetcore/tutorials/signalr-blazor-webassembly.md index 298d01e21542..22452a71912c 100644 --- a/aspnetcore/tutorials/signalr-blazor-webassembly.md +++ b/aspnetcore/tutorials/signalr-blazor-webassembly.md @@ -364,9 +364,9 @@ To learn more about building Blazor apps, see the Blazor documentation: > [!div class="nextstepaction"] > +> [Bearer token authentication with Identity Server, WebSockets, and Server-Sent Events](xref:signalr/authn-and-authz#bearer-token-authentication) ## Additional resources * * [SignalR cross-origin negotiation for authentication](xref:blazor/fundamentals/additional-scenarios#signalr-cross-origin-negotiation-for-authentication) -* [Bearer token authentication with Identity Server, WebSockets, and Server-Sent Events](xref:signalr/authn-and-authz#bearer-token-authentication) From f71000fbf36d6e2399822dac54ba368903ed5960 Mon Sep 17 00:00:00 2001 From: Luke Latham Date: Thu, 22 Oct 2020 13:27:07 -0500 Subject: [PATCH 6/7] Add subsections --- aspnetcore/signalr/authn-and-authz.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/aspnetcore/signalr/authn-and-authz.md b/aspnetcore/signalr/authn-and-authz.md index 84281069f9a6..2ce3b5fc0599 100644 --- a/aspnetcore/signalr/authn-and-authz.md +++ b/aspnetcore/signalr/authn-and-authz.md @@ -84,7 +84,9 @@ Cookies are a browser-specific way to send access tokens, but non-browser client The client can provide an access token instead of using a cookie. The server validates the token and uses it to identify the user. This validation is done only when the connection is established. During the life of the connection, the server doesn't automatically revalidate to check for token revocation. -On the server, bearer token authentication is configured using the [JWT Bearer middleware](/dotnet/api/microsoft.extensions.dependencyinjection.jwtbearerextensions.addjwtbearer). +#### Built-in JWT authentication + +On the server, bearer token authentication is configured using the [JWT Bearer middleware](xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A). In the JavaScript client, the token can be provided using the [accessTokenFactory](xref:signalr/configuration#configure-bearer-authentication) option. @@ -113,6 +115,8 @@ In standard web APIs, bearer tokens are sent in an HTTP header. However, SignalR > [!NOTE] > The query string is used on browsers when connecting with WebSockets and Server-Sent Events due to browser API limitations. When using HTTPS, query string values are secured by the TLS connection. However, many servers log query string values. For more information, see [Security considerations in ASP.NET Core SignalR](xref:signalr/security). SignalR uses headers to transmit tokens in environments which support them (such as the .NET and Java clients). +#### Identity Server JWT authentication + When using Identity Server, add a service to the project: ```csharp From a53d22246846e1fc0cb111c28aad0319602f6902 Mon Sep 17 00:00:00 2001 From: Luke Latham Date: Thu, 22 Oct 2020 13:38:01 -0500 Subject: [PATCH 7/7] Update --- aspnetcore/signalr/authn-and-authz.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aspnetcore/signalr/authn-and-authz.md b/aspnetcore/signalr/authn-and-authz.md index 2ce3b5fc0599..faf98b4dfba1 100644 --- a/aspnetcore/signalr/authn-and-authz.md +++ b/aspnetcore/signalr/authn-and-authz.md @@ -84,10 +84,6 @@ Cookies are a browser-specific way to send access tokens, but non-browser client The client can provide an access token instead of using a cookie. The server validates the token and uses it to identify the user. This validation is done only when the connection is established. During the life of the connection, the server doesn't automatically revalidate to check for token revocation. -#### Built-in JWT authentication - -On the server, bearer token authentication is configured using the [JWT Bearer middleware](xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A). - In the JavaScript client, the token can be provided using the [accessTokenFactory](xref:signalr/configuration#configure-bearer-authentication) option. [!code-typescript[Configure Access Token](authn-and-authz/sample/wwwroot/js/chat.ts?range=52-55)] @@ -106,7 +102,11 @@ var connection = new HubConnectionBuilder() > [!NOTE] > The access token function you provide is called before **every** HTTP request made by SignalR. If you need to renew the token in order to keep the connection active (because it may expire during the connection), do so from within this function and return the updated token. -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. To support this on the server, additional configuration is required: +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. + +#### Built-in JWT authentication + +On the server, bearer token authentication is configured using the [JWT Bearer middleware](xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A): [!code-csharp[Configure Server to accept access token from Query String](authn-and-authz/sample/Startup.cs?name=snippet)]