diff --git a/src/Web/Areas/Identity/IdentityHostingStartup.cs b/src/Web/Areas/Identity/IdentityHostingStartup.cs
new file mode 100644
index 0000000..57a55bd
--- /dev/null
+++ b/src/Web/Areas/Identity/IdentityHostingStartup.cs
@@ -0,0 +1,21 @@
+using System;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.eShopWeb.Infrastructure.Identity;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+[assembly: HostingStartup(typeof(Microsoft.eShopWeb.Web.Areas.Identity.IdentityHostingStartup))]
+namespace Microsoft.eShopWeb.Web.Areas.Identity
+{
+ public class IdentityHostingStartup : IHostingStartup
+ {
+ public void Configure(IWebHostBuilder builder)
+ {
+ builder.ConfigureServices((context, services) => {
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Web/Areas/Identity/Pages/Account/Login.cshtml b/src/Web/Areas/Identity/Pages/Account/Login.cshtml
new file mode 100644
index 0000000..97c524d
--- /dev/null
+++ b/src/Web/Areas/Identity/Pages/Account/Login.cshtml
@@ -0,0 +1,63 @@
+@page
+@model LoginModel
+
+@{
+ ViewData["Title"] = "Log in";
+}
+
+
+
+
+@section Scripts {
+
+}
diff --git a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs
new file mode 100644
index 0000000..72ecf94
--- /dev/null
+++ b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.eShopWeb.Infrastructure.Identity;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
+{
+ [AllowAnonymous]
+ public class LoginModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly ILogger _logger;
+
+ public LoginModel(SignInManager signInManager, ILogger logger)
+ {
+ _signInManager = signInManager;
+ _logger = logger;
+ }
+
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ public IList ExternalLogins { get; set; }
+
+ public string ReturnUrl { get; set; }
+
+ [TempData]
+ public string ErrorMessage { get; set; }
+
+ public class InputModel
+ {
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+
+ [Required]
+ [DataType(DataType.Password)]
+ public string Password { get; set; }
+
+ [Display(Name = "Remember me?")]
+ public bool RememberMe { get; set; }
+ }
+
+ public async Task OnGetAsync(string returnUrl = null)
+ {
+ if (!string.IsNullOrEmpty(ErrorMessage))
+ {
+ ModelState.AddModelError(string.Empty, ErrorMessage);
+ }
+
+ returnUrl = returnUrl ?? Url.Content("~/");
+
+ // Clear the existing external cookie to ensure a clean login process
+ await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
+
+ ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+
+ ReturnUrl = returnUrl;
+ }
+
+ public async Task OnPostAsync(string returnUrl = null)
+ {
+ returnUrl = returnUrl ?? Url.Content("~/");
+
+ if (ModelState.IsValid)
+ {
+ // This doesn't count login failures towards account lockout
+ // To enable password failures to trigger account lockout, set lockoutOnFailure: true
+ var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
+ if (result.Succeeded)
+ {
+ _logger.LogInformation("User logged in.");
+ return LocalRedirect(returnUrl);
+ }
+ if (result.RequiresTwoFactor)
+ {
+ return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
+ }
+ if (result.IsLockedOut)
+ {
+ _logger.LogWarning("User account locked out.");
+ return RedirectToPage("./Lockout");
+ }
+ else
+ {
+ ModelState.AddModelError(string.Empty, "Invalid login attempt.");
+ return Page();
+ }
+ }
+
+ // If we got this far, something failed, redisplay form
+ return Page();
+ }
+ }
+}
diff --git a/src/Web/Areas/Identity/Pages/Account/_ViewImports.cshtml b/src/Web/Areas/Identity/Pages/Account/_ViewImports.cshtml
new file mode 100644
index 0000000..65840eb
--- /dev/null
+++ b/src/Web/Areas/Identity/Pages/Account/_ViewImports.cshtml
@@ -0,0 +1 @@
+@using Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
\ No newline at end of file
diff --git a/src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml b/src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml
new file mode 100644
index 0000000..bacc0ae
--- /dev/null
+++ b/src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/src/Web/Areas/Identity/Pages/_ViewImports.cshtml b/src/Web/Areas/Identity/Pages/_ViewImports.cshtml
new file mode 100644
index 0000000..82c4f63
--- /dev/null
+++ b/src/Web/Areas/Identity/Pages/_ViewImports.cshtml
@@ -0,0 +1,5 @@
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.eShopWeb.Web.Areas.Identity
+@using Microsoft.eShopWeb.Infrastructure.Identity
+@namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/src/Web/Startup.cs b/src/Web/Startup.cs
index f35bd23..93d399a 100644
--- a/src/Web/Startup.cs
+++ b/src/Web/Startup.cs
@@ -114,11 +114,13 @@ namespace Microsoft.eShopWeb.Web
{
options.Conventions.Add(new RouteTokenTransformerConvention(
new SlugifyParameterTransformer()));
+
}
)
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Basket/Checkout");
+ options.AllowAreas = true;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
@@ -164,7 +166,6 @@ namespace Microsoft.eShopWeb.Web
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
- options.LoginPath = "/Account/Signin";
options.LogoutPath = "/Account/Signout";
options.Cookie = new CookieBuilder
{
diff --git a/src/Web/Views/Account/Signin.cshtml b/src/Web/Views/Account/Signin.cshtml
deleted file mode 100644
index 9d1742d..0000000
--- a/src/Web/Views/Account/Signin.cshtml
+++ /dev/null
@@ -1,59 +0,0 @@
-@model LoginViewModel
-@{
- ViewData["Title"] = "Log in";
-}
-
-
-
-@section Scripts {
- @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
-}
diff --git a/src/Web/Views/Shared/_Layout.cshtml b/src/Web/Views/Shared/_Layout.cshtml
index 00ea90a..cd4ae24 100644
--- a/src/Web/Views/Shared/_Layout.cshtml
+++ b/src/Web/Views/Shared/_Layout.cshtml
@@ -24,29 +24,31 @@
-
- @RenderBody()
-
+
+
+ @RenderBody()
+
+
diff --git a/src/Web/Views/Shared/_LoginPartial.cshtml b/src/Web/Views/Shared/_LoginPartial.cshtml
index fce87e4..1f454f6 100644
--- a/src/Web/Views/Shared/_LoginPartial.cshtml
+++ b/src/Web/Views/Shared/_LoginPartial.cshtml
@@ -40,7 +40,7 @@ else
diff --git a/src/Web/Web.csproj b/src/Web/Web.csproj
index c27268f..73d1dbe 100644
--- a/src/Web/Web.csproj
+++ b/src/Web/Web.csproj
@@ -18,6 +18,7 @@
+
@@ -74,7 +75,6 @@
<_ContentIncludedByDefault Remove="Views\Account\Lockout.cshtml" />
<_ContentIncludedByDefault Remove="Views\Account\LoginWith2fa.cshtml" />
<_ContentIncludedByDefault Remove="Views\Account\Register.cshtml" />
- <_ContentIncludedByDefault Remove="Views\Account\Signin.cshtml" />
<_ContentIncludedByDefault Remove="Views\Manage\ChangePassword.cshtml" />
<_ContentIncludedByDefault Remove="Views\Manage\Disable2fa.cshtml" />
<_ContentIncludedByDefault Remove="Views\Manage\EnableAuthenticator.cshtml" />
@@ -96,9 +96,6 @@
-
-
-
diff --git a/src/Web/wwwroot/css/app.component.css b/src/Web/wwwroot/css/app.component.css
index 7b808bb..802c163 100644
--- a/src/Web/wwwroot/css/app.component.css
+++ b/src/Web/wwwroot/css/app.component.css
@@ -4,10 +4,17 @@
margin-top: 2.5rem;
padding-bottom: 2.5rem;
padding-top: 2.5rem;
- width: 100%; }
+ width: 100%;
+ bottom: 0; }
.esh-app-footer-brand {
height: 50px;
width: 230px; }
.esh-app-header {
margin: 15px; }
+
+.esh-app-wrapper {
+ display: flex;
+ min-height: 100vh;
+ flex-direction: column;
+ justify-content: space-between; }
diff --git a/src/Web/wwwroot/css/app.component.min.css b/src/Web/wwwroot/css/app.component.min.css
index 025695c..2841619 100644
--- a/src/Web/wwwroot/css/app.component.min.css
+++ b/src/Web/wwwroot/css/app.component.min.css
@@ -1 +1 @@
-.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%;}.esh-app-footer-brand{height:50px;width:230px;}.esh-app-header{margin:15px;}
\ No newline at end of file
+.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%;bottom:0;}.esh-app-footer-brand{height:50px;width:230px;}.esh-app-header{margin:15px;}.esh-app-wrapper{display:flex;min-height:100vh;flex-direction:column;justify-content:space-between;}
\ No newline at end of file
diff --git a/src/Web/wwwroot/css/app.component.scss b/src/Web/wwwroot/css/app.component.scss
index 6e391df..be55675 100644
--- a/src/Web/wwwroot/css/app.component.scss
+++ b/src/Web/wwwroot/css/app.component.scss
@@ -11,17 +11,23 @@
padding-bottom: $padding;
padding-top: $padding;
width: 100%;
-
+ bottom: 0;
$height: 50px;
&-brand {
height: $height;
width: 230px;
}
-
}
&-header {
margin: 15px;
}
+
+ &-wrapper {
+ display: flex;
+ min-height: 100vh;
+ flex-direction: column;
+ justify-content: space-between
+ }
}
\ No newline at end of file