diff --git a/README.md b/README.md index 10c4238..9f8fa72 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,9 @@ The store's home page should look like this: ![eShopOnWeb home page screenshot](https://user-images.githubusercontent.com/782127/88414268-92d83a00-cdaa-11ea-9b4c-db67d95be039.png) -Most of the site's functionality works with just the web application running. However, the site's Admin page relies on Blazor WebAssembly running in the browser, and it must communicate with the server using the site's PublicApi web application. You'll need to also run this project. You can configure Visual Studio to start multiple projects, or just go to the PublicApi folder in a terminal window and run `dotnet run` from there. After that from the Web folder you should run `dotnet run --launch-profile Web`. Now you should be able to browse to `https://localhost:5001/`. Note that if you use this approach, you'll need to stop the application manually in order to build the solution (otherwise you'll get file locking errors). +Most of the site's functionality works with just the web application running. However, the site's Admin page relies on Blazor WebAssembly running in the browser, and it must communicate with the server using the site's PublicApi web application. You'll need to also run this project. You can configure Visual Studio to start multiple projects, or just go to the PublicApi folder in a terminal window and run `dotnet run` from there. After that from the Web folder you should run `dotnet run --launch-profile Web`. Now you should be able to browse to `https://localhost:5001/`. The admin part in Blazor is accessible to `https://localhost:5001/admin` + +Note that if you use this approach, you'll need to stop the application manually in order to build the solution (otherwise you'll get file locking errors). After cloning or downloading the sample you must setup your database. To use the sample with a persistent database, you will need to run its Entity Framework Core migrations before you will be able to run the app. diff --git a/src/BlazorAdmin/CustomAuthStateProvider.cs b/src/BlazorAdmin/CustomAuthStateProvider.cs index b12d232..30cf42c 100644 --- a/src/BlazorAdmin/CustomAuthStateProvider.cs +++ b/src/BlazorAdmin/CustomAuthStateProvider.cs @@ -63,7 +63,7 @@ public class CustomAuthStateProvider : AuthenticationStateProvider if (user == null || !user.IsAuthenticated) { - return null; + return new ClaimsPrincipal(new ClaimsIdentity()); } var identity = new ClaimsIdentity( diff --git a/src/BlazorAdmin/Pages/Logout.razor b/src/BlazorAdmin/Pages/Logout.razor index ddcd25a..ada679c 100644 --- a/src/BlazorAdmin/Pages/Logout.razor +++ b/src/BlazorAdmin/Pages/Logout.razor @@ -7,7 +7,7 @@ protected override async Task OnInitializedAsync() { - await HttpClient.PostAsync("Identity/Account/Logout", null); + await HttpClient.PostAsync("User/Logout", null); await new Route(JSRuntime).RouteOutside("/Identity/Account/Login"); } diff --git a/src/BlazorAdmin/Shared/RedirectToLogin.razor b/src/BlazorAdmin/Shared/RedirectToLogin.razor index 810a66e..de9c49d 100644 --- a/src/BlazorAdmin/Shared/RedirectToLogin.razor +++ b/src/BlazorAdmin/Shared/RedirectToLogin.razor @@ -1,9 +1,12 @@ -@inject NavigationManager Navigation +@using System.Web; + +@inject NavigationManager Navigation +@inject IJSRuntime JsRuntime @code { protected override void OnInitialized() - { - Navigation.NavigateTo($"Identity/Account/Login?returnUrl=" + - $"/{Uri.EscapeDataString(Navigation.ToBaseRelativePath(Navigation.Uri))}"); + { + var returnUrl = HttpUtility.UrlEncode($"/{Uri.EscapeDataString(Navigation.ToBaseRelativePath(Navigation.Uri))}"); + JsRuntime.InvokeVoidAsync("location.replace", $"Identity/Account/Login?returnUrl={returnUrl}"); } } \ No newline at end of file diff --git a/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs index 8af0afb..80bb467 100644 --- a/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Identity; @@ -10,7 +7,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.eShopWeb.Infrastructure.Identity; using Microsoft.eShopWeb.Web.Configuration; using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Logging; namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account; diff --git a/src/Web/Controllers/UserController.cs b/src/Web/Controllers/UserController.cs index 05d55c2..79968c1 100644 --- a/src/Web/Controllers/UserController.cs +++ b/src/Web/Controllers/UserController.cs @@ -1,11 +1,14 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Security.Claims; using BlazorShared.Authorization; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.eShopWeb.Infrastructure.Identity; +using Microsoft.eShopWeb.Web.Configuration; +using Microsoft.Extensions.Caching.Memory; namespace Microsoft.eShopWeb.Web.Controllers; @@ -14,10 +17,19 @@ namespace Microsoft.eShopWeb.Web.Controllers; public class UserController : ControllerBase { private readonly ITokenClaimsService _tokenClaimsService; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + private readonly IMemoryCache _cache; - public UserController(ITokenClaimsService tokenClaimsService) + public UserController(ITokenClaimsService tokenClaimsService, + SignInManager signInManager, + ILogger logger, + IMemoryCache cache) { _tokenClaimsService = tokenClaimsService; + _signInManager = signInManager; + _logger = logger; + _cache = cache; } [HttpGet] @@ -26,6 +38,25 @@ public class UserController : ControllerBase public async Task GetCurrentUser() => Ok(await CreateUserInfo(User)); + [Route("Logout")] + [HttpPost] + [Authorize] + [AllowAnonymous] + public async Task Logout() + { + await _signInManager.SignOutAsync(); + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + var userId = _signInManager.Context.User.Claims.First(c => c.Type == ClaimTypes.Name); + var identityKey = _signInManager.Context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName]; + _cache.Set($"{userId.Value}:{identityKey}", identityKey, new MemoryCacheEntryOptions + { + AbsoluteExpiration = DateTime.Now.AddMinutes(ConfigureCookieSettings.ValidityMinutesPeriod) + }); + + _logger.LogInformation("User logged out."); + return Ok(); + } + private async Task CreateUserInfo(ClaimsPrincipal claimsPrincipal) { if (claimsPrincipal.Identity == null || claimsPrincipal.Identity.Name == null || !claimsPrincipal.Identity.IsAuthenticated)