diff --git a/aspnetcore/signalr/authn-and-authz.md b/aspnetcore/signalr/authn-and-authz.md index d2d612918b..d1c91f7879 100644 --- a/aspnetcore/signalr/authn-and-authz.md +++ b/aspnetcore/signalr/authn-and-authz.md @@ -3,9 +3,9 @@ title: Authentication and authorization in ASP.NET Core SignalR author: bradygaster description: Learn how to use authentication and authorization in ASP.NET Core SignalR. monikerRange: '>= aspnetcore-2.1' -ms.author: anurse +ms.author: bradyg ms.custom: mvc -ms.date: 06/29/2018 +ms.date: 01/31/2019 uid: signalr/authn-and-authz --- @@ -63,22 +63,14 @@ If [Windows authentication](xref:security/authentication/windowsauth) is configu Add a new class that implements `IUserIdProvider` and retrieve one of the claims from the user to use as the identifier. For example, to use the "Name" claim (which is the Windows username in the form `[Domain]\[Username]`), create the following class: -```csharp -public class NameUserIdProvider : IUserIdProvider -{ - public string GetUserId(HubConnectionContext connection) - { - return connection.User?.FindFirst(ClaimTypes.Name)?.Value; - } -} -``` +[!code-csharp[Name based provider](authn-and-authz/sample/nameuseridprovider.cs?name=NameUserIdProvider)] Rather than `ClaimTypes.Name`, you can use any value from the `User` (such as the Windows SID identifier, etc.). > [!NOTE] > The value you choose must be unique among all the users in your system. Otherwise, a message intended for one user could end up going to a different user. -Register this component in your `Startup.ConfigureServices` method **after** the call to `.AddSignalR` +Register this component in your `Startup.ConfigureServices` method. ```csharp public void ConfigureServices(IServiceCollection services) @@ -103,6 +95,27 @@ var connection = new HubConnectionBuilder() Windows Authentication is only supported by the browser client when using Microsoft Internet Explorer or Microsoft Edge. +### Use claims to customize identity handling + +An app that authenticates users can derive SignalR user IDs from user claims. To specify how SignalR creates user IDs, implement `IUserIdProvider` and register the implementation. + +The sample code demonstrates how you would use claims to select the user's email address as the identifying property. + +> [!NOTE] +> The value you choose must be unique among all the users in your system. Otherwise, a message intended for one user could end up going to a different user. + +[!code-csharp[Email provider](authn-and-authz/sample/EmailBasedUserIdProvider.cs?name=EmailBasedUserIdProvider)] + +The account registration adds a claim with type `ClaimsTypes.Email` to the ASP.NET identity database. + +[!code-csharp[Adding the email to the ASP.NET identity claims](authn-and-authz/sample/pages/account/Register.cshtml.cs?name=AddEmailClaim)] + +Register this component in your `Startup.ConfigureServices`. + +```csharp +services.AddSingleton(); +``` + ## Authorize users to access hubs and hub methods By default, all methods in a hub can be called by an unauthenticated user. In order to require authentication, apply the [Authorize](/dotnet/api/microsoft.aspnetcore.authorization.authorizeattribute) attribute to the hub: diff --git a/aspnetcore/signalr/authn-and-authz/sample/EmailBasedUserIdProvider.cs b/aspnetcore/signalr/authn-and-authz/sample/EmailBasedUserIdProvider.cs new file mode 100644 index 0000000000..7c08cc57de --- /dev/null +++ b/aspnetcore/signalr/authn-and-authz/sample/EmailBasedUserIdProvider.cs @@ -0,0 +1,15 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.SignalR; + +namespace SignalRAuthenticationSample +{ +#region EmailBasedUserIdProvider + public class EmailBasedUserIdProvider : IUserIdProvider + { + public virtual string GetUserId(HubConnectionContext connection) + { + return connection.User?.FindFirst(ClaimTypes.Email)?.Value; + } + } +#endregion +} \ No newline at end of file diff --git a/aspnetcore/signalr/authn-and-authz/sample/NameUserIdProvider.cs b/aspnetcore/signalr/authn-and-authz/sample/NameUserIdProvider.cs index f1a085fb88..0f9e97321f 100644 --- a/aspnetcore/signalr/authn-and-authz/sample/NameUserIdProvider.cs +++ b/aspnetcore/signalr/authn-and-authz/sample/NameUserIdProvider.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.SignalR; namespace SignalRAuthenticationSample { +#region NameUserIdProvider public class NameUserIdProvider : IUserIdProvider { public string GetUserId(HubConnectionContext connection) @@ -9,4 +10,5 @@ namespace SignalRAuthenticationSample return connection.User?.Identity?.Name; } } +#endregion } \ No newline at end of file diff --git a/aspnetcore/signalr/authn-and-authz/sample/Pages/Account/Register.cshtml.cs b/aspnetcore/signalr/authn-and-authz/sample/Pages/Account/Register.cshtml.cs index 350b2fe4f7..a8e644a24d 100644 --- a/aspnetcore/signalr/authn-and-authz/sample/Pages/Account/Register.cshtml.cs +++ b/aspnetcore/signalr/authn-and-authz/sample/Pages/Account/Register.cshtml.cs @@ -1,11 +1,11 @@ using System.ComponentModel.DataAnnotations; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; using SignalRAuthenticationSample.Data; -using SignalRAuthenticationSample.Services; namespace SignalRAuthenticationSample.Pages.Account { @@ -14,18 +14,15 @@ namespace SignalRAuthenticationSample.Pages.Account private readonly SignInManager _signInManager; private readonly UserManager _userManager; private readonly ILogger _logger; - private readonly IEmailSender _emailSender; public RegisterModel( UserManager userManager, SignInManager signInManager, - ILogger logger, - IEmailSender emailSender) + ILogger logger) { _userManager = userManager; _signInManager = signInManager; _logger = logger; - _emailSender = emailSender; } [BindProperty] @@ -62,8 +59,14 @@ namespace SignalRAuthenticationSample.Pages.Account ReturnUrl = returnUrl; if (ModelState.IsValid) { +#region AddEmailClaim + // create a new user var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email }; var result = await _userManager.CreateAsync(user, Input.Password); + + // add the email claim and value for this user + await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Input.Email)); +#endregion if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); diff --git a/aspnetcore/signalr/authn-and-authz/sample/Pages/_Layout.cshtml b/aspnetcore/signalr/authn-and-authz/sample/Pages/_Layout.cshtml index a0f09423ab..c04351dea1 100644 --- a/aspnetcore/signalr/authn-and-authz/sample/Pages/_Layout.cshtml +++ b/aspnetcore/signalr/authn-and-authz/sample/Pages/_Layout.cshtml @@ -13,7 +13,7 @@ - + diff --git a/aspnetcore/signalr/authn-and-authz/sample/Startup.cs b/aspnetcore/signalr/authn-and-authz/sample/Startup.cs index 8cd7961285..ad428c5db4 100644 --- a/aspnetcore/signalr/authn-and-authz/sample/Startup.cs +++ b/aspnetcore/signalr/authn-and-authz/sample/Startup.cs @@ -97,6 +97,12 @@ namespace SignalRAuthenticationSample // If the Name claim isn't unique, users could receive messages // intended for a different user! services.AddSingleton(); + + // Change to use email as the user identifier for SignalR + // services.AddSingleton(); + + // WARNING: use *either* the NameUserIdProvider *or* the + // EmailBasedUserIdProvider, but do not use both. } #endregion diff --git a/aspnetcore/signalr/authn-and-authz/sample/wwwroot/css/site.css b/aspnetcore/signalr/authn-and-authz/sample/wwwroot/css/site.css index 465ee540fa..f2b6e5362e 100644 --- a/aspnetcore/signalr/authn-and-authz/sample/wwwroot/css/site.css +++ b/aspnetcore/signalr/authn-and-authz/sample/wwwroot/css/site.css @@ -1,5 +1,5 @@ body { - padding-top: 50px; + padding-top: 80px; padding-bottom: 20px; }