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"; +} + +

@ViewData["Title"]

+
+
+
+
+

Use a local account to log in.

+
+
+
+ + + +
+
+ + + +
+
+
+ +
+
+
+ +
+ +
+
+
+
+
+

Use another service to log in.

+
+ @{ + if ((Model.ExternalLogins?.Count ?? 0) == 0) + { +
+

+ 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 + { +
+
+

+ @foreach (var provider in Model.ExternalLogins) + { + + } +

+
+
+ } + } +
+
+
+ +@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(); +} + diff --git a/src/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml b/src/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml new file mode 100644 index 0000000..d75b855 --- /dev/null +++ b/src/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml @@ -0,0 +1 @@ +@using IdentityServer4WebApp.Areas.Identity.Pages.Account.Manage diff --git a/src/Areas/Identity/Pages/Account/_ViewImports.cshtml b/src/Areas/Identity/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000..ae330dd --- /dev/null +++ b/src/Areas/Identity/Pages/Account/_ViewImports.cshtml @@ -0,0 +1 @@ +@using IdentityServer4WebApp.Areas.Identity.Pages.Account \ No newline at end of file diff --git a/src/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml b/src/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml new file mode 100644 index 0000000..9e26f3b --- /dev/null +++ b/src/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/src/Areas/Identity/Pages/_ViewImports.cshtml b/src/Areas/Identity/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..636114b --- /dev/null +++ b/src/Areas/Identity/Pages/_ViewImports.cshtml @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Identity +@using IdentityServer4WebApp.Areas.Identity +@using IdentityServer4WebApp.Models +@namespace IdentityServer4WebApp.Areas.Identity.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/Areas/Identity/Pages/_ViewStart.cshtml b/src/Areas/Identity/Pages/_ViewStart.cshtml new file mode 100644 index 0000000..2909abd --- /dev/null +++ b/src/Areas/Identity/Pages/_ViewStart.cshtml @@ -0,0 +1,4 @@ + +@{ + Layout = "/Pages/Shared/_Layout.cshtml"; +} diff --git a/src/ClientApp/src/app/api-authorization/authorize-windows-group-guard.guard.ts b/src/ClientApp/src/app/api-authorization/authorize-windows-group-guard.guard.ts new file mode 100644 index 0000000..57e54ee --- /dev/null +++ b/src/ClientApp/src/app/api-authorization/authorize-windows-group-guard.guard.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router'; +import { AuthorizeService } from "./authorize.service"; +import { ApplicationPaths, QueryParameterNames } from './api-authorization.constants'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthorizeWindowsGroupGuardGuard implements CanActivate { + constructor(private authorize: AuthorizeService, private router: Router) { } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | + Promise | + boolean | + UrlTree { + return this.authorize.getUser().pipe(map((u: any) => !!u && !!u.hasUsersGroup)).pipe(tap((isAuthorized: boolean) => this.handleAuthorization(isAuthorized, state)));; + } + + private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) { + if (!isAuthenticated) { + window.location.href = "/Identity/Account/Login?" + QueryParameterNames.ReturnUrl + "=/"; + } + } +} diff --git a/src/ClientApp/src/app/app-routing.module.ts b/src/ClientApp/src/app/app-routing.module.ts index dc7bc08..4760d6d 100644 --- a/src/ClientApp/src/app/app-routing.module.ts +++ b/src/ClientApp/src/app/app-routing.module.ts @@ -3,11 +3,14 @@ import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from "./home/home.component"; import { DataComponent } from "./data/data.component"; import { AuthorizeGuard } from "./api-authorization/authorize.guard"; +import { InternalDataComponent } from "./internal-data/internal-data.component"; +import { AuthorizeWindowsGroupGuardGuard } from "./api-authorization/authorize-windows-group-guard.guard"; const routes: Routes = [ { path: '', component: HomeComponent, pathMatch: 'full' }, - { path: 'data', component: DataComponent, canActivate: [AuthorizeGuard] } + { path: 'data', component: DataComponent, canActivate: [AuthorizeGuard] }, + { path: 'internaldata', component: InternalDataComponent, canActivate: [AuthorizeWindowsGroupGuardGuard] } ]; diff --git a/src/ClientApp/src/app/app.component.html b/src/ClientApp/src/app/app.component.html index c05b075..e854fd1 100644 --- a/src/ClientApp/src/app/app.component.html +++ b/src/ClientApp/src/app/app.component.html @@ -10,7 +10,10 @@ - + + diff --git a/src/ClientApp/src/app/app.module.ts b/src/ClientApp/src/app/app.module.ts index bccc12d..221643b 100644 --- a/src/ClientApp/src/app/app.module.ts +++ b/src/ClientApp/src/app/app.module.ts @@ -9,13 +9,15 @@ import { DataComponent } from './data/data.component'; import { ApiAuthorizationModule } from "./api-authorization/api-authorization.module"; import { ExchangeRateComponent } from './exchange-rate/exchange-rate.component'; import { AuthorizeInterceptor } from "./api-authorization/authorize.interceptor"; +import { InternalDataComponent } from './internal-data/internal-data.component'; @NgModule({ declarations: [ AppComponent, HomeComponent, DataComponent, - ExchangeRateComponent + ExchangeRateComponent, + InternalDataComponent ], imports: [ BrowserModule, diff --git a/src/ClientApp/src/app/internal-data/internal-data.component.ts b/src/ClientApp/src/app/internal-data/internal-data.component.ts new file mode 100644 index 0000000..1a03e11 --- /dev/null +++ b/src/ClientApp/src/app/internal-data/internal-data.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-internal-data', + template: ``, + styles: [] +}) +export class InternalDataComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/Controller/ExchangeRateController.cs b/src/Controller/ExchangeRateController.cs index d93bf99..930a853 100644 --- a/src/Controller/ExchangeRateController.cs +++ b/src/Controller/ExchangeRateController.cs @@ -28,5 +28,13 @@ public IEnumerable Get() }) .ToArray(); } + + [Authorize(Policy = "ShouldHasUsersGroup")] + [HttpGet("api/internalrates")] + public IEnumerable GetInternalRates() + { + return Get().Select(i => { i.Value = Math.Round(i.Value - 0.02, 2); return i; }); + } + } } diff --git a/src/IdentityServer4WebApp.csproj b/src/IdentityServer4WebApp.csproj index d4cbc74..2314b3d 100644 --- a/src/IdentityServer4WebApp.csproj +++ b/src/IdentityServer4WebApp.csproj @@ -33,9 +33,11 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all + \ No newline at end of file diff --git a/src/Pages/Shared/_CookieConsentPartial.cshtml b/src/Pages/Shared/_CookieConsentPartial.cshtml new file mode 100644 index 0000000..d7a5718 --- /dev/null +++ b/src/Pages/Shared/_CookieConsentPartial.cshtml @@ -0,0 +1,25 @@ +@using Microsoft.AspNetCore.Http.Features + +@{ + var consentFeature = Context.Features.Get(); + var showBanner = !consentFeature?.CanTrack ?? false; + var cookieString = consentFeature?.CreateConsentCookie(); +} + +@if (showBanner) +{ + + +} \ 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