diff --git a/src/Areas/Identity/IdentityHostingStartup.cs b/src/Areas/Identity/IdentityHostingStartup.cs
new file mode 100644
index 0000000..d46ea65
--- /dev/null
+++ b/src/Areas/Identity/IdentityHostingStartup.cs
@@ -0,0 +1,22 @@
+using System;
+using IdentityServer4WebApp.Data;
+using IdentityServer4WebApp.Models;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+[assembly: HostingStartup(typeof(IdentityServer4WebApp.Areas.Identity.IdentityHostingStartup))]
+namespace IdentityServer4WebApp.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/Areas/Identity/Pages/Account/ExternalLogin.cshtml b/src/Areas/Identity/Pages/Account/ExternalLogin.cshtml
new file mode 100644
index 0000000..6058974
--- /dev/null
+++ b/src/Areas/Identity/Pages/Account/ExternalLogin.cshtml
@@ -0,0 +1,33 @@
+@page
+@model ExternalLoginModel
+@{
+ ViewData["Title"] = "Register";
+}
+
+
@ViewData["Title"]
+
Associate your @Model.LoginProvider account.
+
+
+
+ You've successfully authenticated with @Model.LoginProvider.
+ Please enter an email address for this site below and click the Register button to finish
+ logging in.
+
+
+
+
+
+
+
+
+@section Scripts {
+
+}
diff --git a/src/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs b/src/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
new file mode 100644
index 0000000..88eb4de
--- /dev/null
+++ b/src/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Security.Claims;
+using System.Security.Principal;
+using System.Threading.Tasks;
+using IdentityModel;
+using Microsoft.AspNetCore.Authorization;
+using IdentityServer4WebApp.Models;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Server.IISIntegration;
+using Microsoft.Extensions.Logging;
+
+namespace IdentityServer4WebApp.Areas.Identity.Pages.Account
+{
+ [AllowAnonymous]
+ public class ExternalLoginModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly UserManager _userManager;
+ private readonly ILogger _logger;
+
+ public ExternalLoginModel(
+ SignInManager signInManager,
+ UserManager userManager,
+ ILogger logger)
+ {
+ _signInManager = signInManager;
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ [BindProperty]
+ public InputModel Input { get; set; }
+
+ public string LoginProvider { get; set; }
+
+ public string ReturnUrl { get; set; }
+
+ [TempData]
+ public string ErrorMessage { get; set; }
+
+ public class InputModel
+ {
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+ }
+
+ public IActionResult OnGetAsync()
+ {
+ return RedirectToPage("./Login");
+ }
+
+ public async Task OnPost(string provider, string returnUrl = null)
+ {
+ if (IISDefaults.AuthenticationScheme == provider)
+ {
+ return await ProcessWindowsLoginAsync(returnUrl);
+ }
+
+ var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
+ var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
+ return new ChallengeResult(provider, properties);
+ }
+
+ public async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
+ {
+ returnUrl = returnUrl ?? Url.Content("~/");
+ if (remoteError != null)
+ {
+ ErrorMessage = $"Error from external provider: {remoteError}";
+ return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
+ }
+ var info = await _signInManager.GetExternalLoginInfoAsync();
+ if (info == null)
+ {
+ ErrorMessage = "Error loading external login information.";
+ return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
+ }
+
+ // Sign in the user with this external login provider if the user already has a login.
+ var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor : true);
+ if (result.Succeeded)
+ {
+ var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
+
+ await UpdateClaims(info, user, "hasUsersGroup");
+
+ _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
+ return LocalRedirect(returnUrl);
+ }
+ if (result.IsLockedOut)
+ {
+ return RedirectToPage("./Lockout");
+ }
+ else
+ {
+ // If the user does not have an account, then ask the user to create an account.
+ ReturnUrl = returnUrl;
+ LoginProvider = info.LoginProvider;
+ if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
+ {
+ Input = new InputModel
+ {
+ Email = info.Principal.FindFirstValue(ClaimTypes.Email)
+ };
+ }
+ return Page();
+ }
+ }
+
+ public async Task OnPostConfirmationAsync(string returnUrl = null)
+ {
+ returnUrl = returnUrl ?? Url.Content("~/");
+ // Get the information about the user from the external login provider
+ var info = await _signInManager.GetExternalLoginInfoAsync();
+ if (info == null)
+ {
+ ErrorMessage = "Error loading external login information during confirmation.";
+ return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
+ }
+
+ if (ModelState.IsValid)
+ {
+ var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
+ var result = await _userManager.CreateAsync(user);
+ if (result.Succeeded)
+ {
+ result = await _userManager.AddLoginAsync(user, info);
+ if (result.Succeeded)
+ {
+ await _signInManager.SignInAsync(user, isPersistent: false);
+ _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
+ await UpdateClaims(info, user, "hasUsersGroup");
+ return LocalRedirect(returnUrl);
+ }
+ }
+ foreach (var error in result.Errors)
+ {
+ ModelState.AddModelError(string.Empty, error.Description);
+ }
+ }
+
+ LoginProvider = info.LoginProvider;
+ ReturnUrl = returnUrl;
+ return Page();
+ }
+
+ private async Task ProcessWindowsLoginAsync(string returnUrl)
+ {
+ var result = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
+ if (result?.Principal is WindowsPrincipal wp)
+ {
+ var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
+
+ var props = _signInManager.ConfigureExternalAuthenticationProperties(IISDefaults.AuthenticationScheme, redirectUrl);
+ props.Items["scheme"] = IISDefaults.AuthenticationScheme;
+
+ var id = new ClaimsIdentity(IISDefaults.AuthenticationScheme);
+ id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
+ id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
+ id.AddClaim(new Claim(ClaimTypes.NameIdentifier, wp.Identity.Name));
+
+ var wi = wp.Identity as WindowsIdentity;
+ var groups = wi.Groups.Translate(typeof(NTAccount));
+ var hasUsersGroup = groups.Any(i => i.Value.Contains(@"BUILTIN\Users", StringComparison.OrdinalIgnoreCase));
+
+ id.AddClaim(new Claim("hasUsersGroup", hasUsersGroup.ToString()));
+
+ await HttpContext.SignInAsync(IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), props);
+
+ return Redirect(props.RedirectUri);
+ }
+
+ return Challenge(IISDefaults.AuthenticationScheme);
+ }
+
+ private async Task UpdateClaims(ExternalLoginInfo info, ApplicationUser user, params string[] claimTypes)
+ {
+ if (claimTypes == null)
+ {
+ return;
+ }
+
+ var claimTypesHash = new HashSet(claimTypes);
+
+ var claims = (await _userManager.GetClaimsAsync(user)).Where(c => claimTypesHash.Contains(c.Type)).ToList();
+
+ await _userManager.RemoveClaimsAsync(user, claims);
+
+ foreach (var claimType in claimTypes)
+ {
+ if (info.Principal.HasClaim(c => c.Type == claimType))
+ {
+ claims = info.Principal.FindAll(claimType).ToList();
+ await _userManager.AddClaimsAsync(user, claims);
+ }
+ }
+ }
+
+
+ }
+}
diff --git a/src/Areas/Identity/Pages/Account/Login.cshtml b/src/Areas/Identity/Pages/Account/Login.cshtml
new file mode 100644
index 0000000..e9963ad
--- /dev/null
+++ b/src/Areas/Identity/Pages/Account/Login.cshtml
@@ -0,0 +1,82 @@
+@page
+@model LoginModel
+
+@{
+ ViewData["Title"] = "Log in";
+}
+
+
+ There are no external authentication services configured. See this article
+ for details on setting up this ASP.NET application to support logging in via external services.
+
+
+ }
+ else
+ {
+
+ }
+ }
+
+
+
+
+@section Scripts {
+
+}
diff --git a/src/Areas/Identity/Pages/Account/Login.cshtml.cs b/src/Areas/Identity/Pages/Account/Login.cshtml.cs
new file mode 100644
index 0000000..be22cac
--- /dev/null
+++ b/src/Areas/Identity/Pages/Account/Login.cshtml.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using IdentityServer4WebApp.Models;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Server.IISIntegration;
+using Microsoft.Extensions.Logging;
+
+namespace IdentityServer4WebApp.Areas.Identity.Pages.Account
+{
+ [Authorize(AuthenticationSchemes = "Windows")]
+ public class LoginModel : PageModel
+ {
+ private readonly SignInManager _signInManager;
+ private readonly ILogger _logger;
+
+ private readonly IAuthenticationSchemeProvider _schemeProvider;
+
+ public LoginModel(SignInManager signInManager, ILogger logger, IAuthenticationSchemeProvider schemeProvider)
+ {
+ _signInManager = signInManager;
+ _logger = logger;
+ _schemeProvider = schemeProvider;
+ }
+
+ [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 _schemeProvider.GetAllSchemesAsync()).Where(x => x.DisplayName != null || (x.Name.Equals(IISDefaults.AuthenticationScheme, StringComparison.OrdinalIgnoreCase))).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/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs b/src/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
new file mode 100644
index 0000000..c8a84bf
--- /dev/null
+++ b/src/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
@@ -0,0 +1,35 @@
+using System;
+using Microsoft.AspNetCore.Mvc.Rendering;
+
+namespace IdentityServer4WebApp.Areas.Identity.Pages.Account.Manage
+{
+ public static class ManageNavPages
+ {
+ public static string Index => "Index";
+
+ public static string ChangePassword => "ChangePassword";
+
+ public static string ExternalLogins => "ExternalLogins";
+
+ public static string PersonalData => "PersonalData";
+
+ public static string TwoFactorAuthentication => "TwoFactorAuthentication";
+
+ public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
+
+ public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
+
+ public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
+
+ public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
+
+ public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
+
+ private static string PageNavClass(ViewContext viewContext, string page)
+ {
+ var activePage = viewContext.ViewData["ActivePage"] as string
+ ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
+ return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml b/src/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml
new file mode 100644
index 0000000..677b43e
--- /dev/null
+++ b/src/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml
@@ -0,0 +1,14 @@
+@inject SignInManager SignInManager
+@{
+ var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
+}
+
+ Use this space to summarize your privacy and cookie use policy. Learn More.
+
+
+
+}
\ No newline at end of file
diff --git a/src/Pages/_ViewStart.cshtml b/src/Pages/_ViewStart.cshtml
new file mode 100644
index 0000000..e25d5d0
--- /dev/null
+++ b/src/Pages/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "_Layout";
+ }
diff --git a/src/ScaffoldingReadme.txt b/src/ScaffoldingReadme.txt
new file mode 100644
index 0000000..da8a278
--- /dev/null
+++ b/src/ScaffoldingReadme.txt
@@ -0,0 +1,18 @@
+Support for ASP.NET Core Identity was added to your project
+- The code for adding Identity to your project was generated under Areas/Identity.
+
+Configuration of the Identity related services can be found in the Areas/Identity/IdentityHostingStartup.cs file.
+
+
+The generated UI requires support for static files. To add static files to your app:
+1. Call app.UseStaticFiles() from your Configure method
+
+To use ASP.NET Core Identity you also need to enable authentication. To authentication to your app:
+1. Call app.UseAuthentication() from your Configure method (after static files)
+
+The generated UI requires MVC. To add MVC to your app:
+1. Call services.AddMvc() from your ConfigureServices method
+2. Call app.UseMvc() from your Configure method (after authentication)
+
+Apps that use ASP.NET Core Identity should also use HTTPS. To enable HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
+
diff --git a/src/Startup.cs b/src/Startup.cs
index 98eced8..f4dfae9 100644
--- a/src/Startup.cs
+++ b/src/Startup.cs
@@ -2,8 +2,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using IdentityServer4.Models;
using IdentityServer4WebApp.Data;
using IdentityServer4WebApp.Models;
+using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -35,7 +37,21 @@ public void ConfigureServices(IServiceCollection services)
services.AddDefaultIdentity()
.AddEntityFrameworkStores();
services.AddIdentityServer()
- .AddApiAuthorization();
+ .AddApiAuthorization(options =>
+ {
+ var apiResource = options.ApiResources.First();
+ apiResource.UserClaims = new[] { "hasUsersGroup" };
+
+ var identityResource = new IdentityResource
+ {
+ Name = "customprofile",
+ DisplayName = "Custom profile",
+ UserClaims = new[] { "hasUsersGroup" },
+ };
+ identityResource.Properties.Add(ApplicationProfilesPropertyNames.Clients, "*");
+ options.IdentityResources.Add(identityResource);
+ }
+ );
services.AddAuthentication()
.AddOpenIdConnect("Google", "Google",
@@ -59,6 +75,12 @@ public void ConfigureServices(IServiceCollection services)
services.AddControllers();
services.AddRazorPages();
+
+ services.AddAuthorization(options =>
+ {
+ options.AddPolicy("ShouldHasUsersGroup", policy => { policy.RequireClaim("hasUsersGroup"); });
+ });
+
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
diff --git a/src/data.db b/src/data.db
index 1ada2a5..6f5ab7f 100644
Binary files a/src/data.db and b/src/data.db differ