From 4d27fbffd813a19cc7fdb25c0389a613557e1734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 1 Mar 2018 10:34:25 +0300 Subject: [PATCH 01/17] Refactored login page. --- .../MicroservicesAuthServerModule.cs | 17 +- .../AbpAccountWebIdentityServerModule.cs | 8 +- .../Pages/Account/Login.cshtml | 61 +++++ .../Pages/Account/Login.cshtml.cs | 228 ++++++++++++++++++ .../Pages/Account/_ViewImports.cshtml | 2 + ...Volo.Abp.Account.Web.IdentityServer.csproj | 6 +- src/Volo.Abp.Account.Web/AbpAccountOptions.cs | 13 + .../AbpAccountWebModule.cs | 4 +- .../Pages/Account/_ViewImports.cshtml | 2 + .../Volo.Abp.Account.Web.csproj | 8 + 10 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml create mode 100644 src/Volo.Abp.Account.Web/AbpAccountOptions.cs create mode 100644 src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs index 28837b8d08..1fe43dc686 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Builder; +using System.IO; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -6,12 +7,15 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Modularity; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.Autofac; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.IdentityServer.EntityFrameworkCore; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace MicroserviceDemo.AuthServer { @@ -43,6 +47,17 @@ namespace MicroserviceDemo.AuthServer }); }); + if (hostingEnvironment.IsDevelopment()) + { + services.Configure(options => + { + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.AspNetCore.Mvc.UI")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.Account.Web")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.Account.Web.IdentityServer")); + }); + } + services.AddAssemblyOf(); } diff --git a/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs b/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs index 14e5100045..21ec67aa81 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs @@ -1,20 +1,22 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.IdentityServer; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Account.Web { [DependsOn(typeof(AbpAccountWebModule))] + [DependsOn(typeof(AbpIdentityServerDomainModule))] public class AbpAccountWebIdentityServerModule : AbpModule { public override void ConfigureServices(IServiceCollection services) { - services.AddAssemblyOf(); - services.Configure(options => { - options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); + options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); }); + + services.AddAssemblyOf(); } } } diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml new file mode 100644 index 0000000000..75cef4ee5e --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml @@ -0,0 +1,61 @@ +@page +@model Volo.Abp.Account.Web.Pages.Account.IdsLoginModel +
+ + @if (Model.EnableLocalLogin) + { +
+
+
+ + +
+ + + +
+
+ + + +
+
+ +
+ + +
+ +
+ Register +
+
+
+ } + + @if (Model.VisibleExternalProviders.Any()) + { +
+

Use another service to log in.

+
+
+ @foreach (var provider in Model.VisibleExternalProviders) + { + + } +
+
+
+ } + + @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) + { +
+ Invalid login request + There are no login schemes configured for this client. +
+ } +
\ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs new file mode 100644 index 0000000000..e71030e3b8 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.Identity; +using Volo.Abp.Ui; +using Volo.Abp.Uow; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public class IdsLoginModel : AccountModelBase + { + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [BindProperty] + public LoginInputModel LoginInput { get; set; } + + public bool EnableLocalLogin { get; set; } + + //TODO: Why there is an ExternalProviders if only the VisibleExternalProviders is used. + public IEnumerable ExternalProviders { get; set; } + public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); + + public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; + public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; + + private readonly SignInManager _signInManager; + private readonly IdentityUserManager _userManager; + private readonly IIdentityServerInteractionService _interaction; + private readonly IAuthenticationSchemeProvider _schemeProvider; + private readonly AbpAccountOptions _accountOptions; + private readonly IClientStore _clientStore; + + public IdsLoginModel( + SignInManager signInManager, + IdentityUserManager userManager, + IIdentityServerInteractionService interaction, + IAuthenticationSchemeProvider schemeProvider, + IOptions accountOptions, + IClientStore clientStore) + { + _signInManager = signInManager; + _userManager = userManager; + _interaction = interaction; + _schemeProvider = schemeProvider; + _clientStore = clientStore; + _accountOptions = accountOptions.Value; + } + + public async Task OnGetAsync() + { + LoginInput = new LoginInputModel(); + + var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl); + LoginInput.UserNameOrEmailAddress = context?.LoginHint; + + if (context?.IdP != null) + { + LoginInput.UserNameOrEmailAddress = context.LoginHint; + ExternalProviders = new[] {new ExternalProviderModel {AuthenticationScheme = context.IdP}}; + return; + } + + var schemes = await _schemeProvider.GetAllSchemesAsync(); + + var providers = schemes + .Where(x => x.DisplayName != null || x.Name.Equals(_accountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) + .Select(x => new ExternalProviderModel + { + DisplayName = x.DisplayName, + AuthenticationScheme = x.Name + }) + .ToList(); + + EnableLocalLogin = true; //TODO: We can get default from a setting? + if (context?.ClientId != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); + if (client != null) + { + EnableLocalLogin = client.EnableLocalLogin; + + if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) + { + providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); + } + } + } + + ExternalProviders = providers.ToArray(); + + if (IsExternalLoginOnly) + { + //return await ExternalLogin(vm.ExternalLoginScheme, returnUrl); + throw new NotImplementedException(); + } + } + + [UnitOfWork] //TODO: Will be removed when we implement action filter + public virtual async Task OnPostAsync() + { + ValidateModel(); + + var result = await _signInManager.PasswordSignInAsync( + LoginInput.UserNameOrEmailAddress, + LoginInput.Password, + LoginInput.RememberMe, + true + ); + + if (!result.Succeeded) + { + throw new UserFriendlyException("Login failed!"); //TODO: Handle other cases, do not throw exception + } + + return RedirectSafely(ReturnUrl, ReturnUrlHash); + } + + [UnitOfWork] + public virtual IActionResult OnPostExternalLogin(string provider, string returnUrl = "", string returnUrlHash = "") + { + var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { returnUrl, returnUrlHash }); + + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + + return new ChallengeResult(provider, properties); + } + + [UnitOfWork] + public virtual async Task OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) + { + if (remoteError != null) + { + Logger.LogWarning($"External login callback error: {remoteError}"); + return RedirectToPage("./Login"); + } + + var loginInfo = await _signInManager.GetExternalLoginInfoAsync(); + if (loginInfo == null) + { + Logger.LogWarning("External login info is not available"); + return RedirectToPage("./Login"); + } + + var result = await _signInManager.ExternalLoginSignInAsync( + loginInfo.LoginProvider, + loginInfo.ProviderKey, + isPersistent: false, + bypassTwoFactor: true + ); + + if (result.IsLockedOut) + { + throw new UserFriendlyException("Cannot proceed because user is locked out!"); + } + + //TODO: Handle other cases + + if (result.Succeeded) + { + return RedirectSafely(returnUrl, returnUrlHash); + } + + // Get the information about the user from the external login provider + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + throw new ApplicationException("Error loading external login information during confirmation."); + } + + var user = await CreateExternalUserAsync(info); + + await _signInManager.SignInAsync(user, false); + return RedirectSafely(returnUrl, returnUrlHash); + } + + private async Task CreateExternalUserAsync(ExternalLoginInfo info) + { + var emailAddress = info.Principal.FindFirstValue(ClaimTypes.Email); + + var user = new IdentityUser(GuidGenerator.Create(), emailAddress, CurrentTenant.Id); + + CheckIdentityErrors(await _userManager.CreateAsync(user)); + CheckIdentityErrors(await _userManager.SetEmailAsync(user, emailAddress)); + CheckIdentityErrors(await _userManager.AddLoginAsync(user, info)); + + return user; + } + + public class LoginInputModel + { + [Required] + [StringLength(255)] + [Display(Name = "UserNameOrEmailAddress")] + public string UserNameOrEmailAddress { get; set; } + + [Required] + [StringLength(32)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Display(Name = "RememberMe")] + public bool RememberMe { get; set; } + } + + public class ExternalProviderModel + { + public string DisplayName { get; set; } + public string AuthenticationScheme { get; set; } + } + } +} diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000000..d1ac64721f --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 5bedcf9979..fa6ac31d6d 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -19,13 +19,17 @@ + + + + - + diff --git a/src/Volo.Abp.Account.Web/AbpAccountOptions.cs b/src/Volo.Abp.Account.Web/AbpAccountOptions.cs new file mode 100644 index 0000000000..e814ee44f5 --- /dev/null +++ b/src/Volo.Abp.Account.Web/AbpAccountOptions.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.Account.Web +{ + public class AbpAccountOptions + { + public string WindowsAuthenticationSchemeName { get; set; } + + public AbpAccountOptions() + { + //TODO: This makes us depend on the Microsoft.AspNetCore.Server.IISIntegration package. + WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; + } + } +} diff --git a/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs index a0df66f747..e35f0050ed 100644 --- a/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs +++ b/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -12,12 +12,12 @@ namespace Volo.Abp.Account.Web { public override void ConfigureServices(IServiceCollection services) { - services.AddAssemblyOf(); - services.Configure(options => { options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); }); + + services.AddAssemblyOf(); } } } diff --git a/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml b/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000000..d1ac64721f --- /dev/null +++ b/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index 6073769fc4..e844ba9f73 100644 --- a/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -19,6 +19,14 @@ + + + + + + + + From b3de11f8d320777315eb7ee465c7c75c33807c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 1 Mar 2018 11:00:31 +0300 Subject: [PATCH 02/17] Login/logout is now working for the microservice auth server demo. --- .../MicroserviceDemo.AuthServer.csproj | 1 + .../MicroservicesAuthServerModule.cs | 16 ++++++++++++++- .../Pages/Index.cshtml | 20 +++++++++++++++++++ .../Pages/Index.cshtml.cs | 17 ++++++++++++++++ .../MicroservicesDemoWebModule.cs | 11 +++++++++- .../Pages/Account/Login.cshtml | 2 +- .../Pages/Account/Login.cshtml.cs | 17 +++++++++++++++- 7 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml create mode 100644 src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml.cs diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroserviceDemo.AuthServer.csproj b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroserviceDemo.AuthServer.csproj index 3661de16dd..2ffe2ec9c0 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroserviceDemo.AuthServer.csproj +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroserviceDemo.AuthServer.csproj @@ -17,6 +17,7 @@ + diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs index 1fe43dc686..21e54e30f6 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs @@ -58,6 +58,8 @@ namespace MicroserviceDemo.AuthServer }); } + services.AddAuthorization(); + services.AddAssemblyOf(); } @@ -73,7 +75,19 @@ namespace MicroserviceDemo.AuthServer app.UseStaticFiles(); app.UseVirtualFiles(); - app.UseMvcWithDefaultRoute(); + app.UseAuthentication(); + + app.UseMvc(routes => + { + //TODO: Can we make an extension method for adding these two routes inside the framework? + routes.MapRoute( + name: "defaultWithArea", + template: "{area}/{controller=Home}/{action=Index}/{id?}"); + + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } private static IConfigurationRoot BuildConfiguration(IHostingEnvironment env) diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml new file mode 100644 index 0000000000..0712e36914 --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml @@ -0,0 +1,20 @@ +@page +@model MicroserviceDemo.AuthServer.Pages.IndexModel + +

Welcome to the Authentication Server!

+ + + + + Login + + @if (User.Identity.IsAuthenticated) + { + foreach (var claim in User.Claims) + { +
@claim.Type = @claim.Value
+ } + } + +
+
\ No newline at end of file diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml.cs new file mode 100644 index 0000000000..0771c699c0 --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace MicroserviceDemo.AuthServer.Pages +{ + public class IndexModel : PageModel + { + public void OnGet() + { + + } + } +} \ No newline at end of file diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs index 7b91f3de5a..e2cc2c8ca3 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs @@ -109,7 +109,16 @@ namespace MicroserviceDemo.Web app.UseAuthentication(); - app.UseMvcWithDefaultRoute(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "defaultWithArea", + template: "{area}/{controller=Home}/{action=Index}/{id?}"); + + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } private static IConfigurationRoot BuildConfiguration(IHostingEnvironment env) diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml index 75cef4ee5e..7e45ba5176 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml @@ -26,7 +26,7 @@ - + @* TODO: Only show if identity server is used *@
diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs index e71030e3b8..e280fcd7ce 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; +using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Stores; using Microsoft.AspNetCore.Authentication; @@ -112,8 +113,20 @@ namespace Volo.Abp.Account.Web.Pages.Account } [UnitOfWork] //TODO: Will be removed when we implement action filter - public virtual async Task OnPostAsync() + public virtual async Task OnPostAsync(string action) { + if (action == "Cancel") + { + var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl); + if (context == null) + { + return RedirectSafely("~/"); + } + + await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); + return RedirectSafely(ReturnUrl); + } + ValidateModel(); var result = await _signInManager.PasswordSignInAsync( @@ -128,6 +141,8 @@ namespace Volo.Abp.Account.Web.Pages.Account throw new UserFriendlyException("Login failed!"); //TODO: Handle other cases, do not throw exception } + //var user = _userManager.FindByIdAsync(result.) + return RedirectSafely(ReturnUrl, ReturnUrlHash); } From e2754dff87fec36cf520802e1abc0222df8ff91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 1 Mar 2018 11:22:01 +0300 Subject: [PATCH 03/17] Created SignInResultExtensions --- .../Auth/SignInResultExtensions.cs | 33 +++++++++++++++++++ .../Pages/Account/Login.cshtml.cs | 23 +++++++++++-- ...Volo.Abp.Account.Web.IdentityServer.csproj | 8 ----- 3 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Auth/SignInResultExtensions.cs diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Auth/SignInResultExtensions.cs b/src/Volo.Abp.Account.Web.IdentityServer/Auth/SignInResultExtensions.cs new file mode 100644 index 0000000000..ac229fbf52 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Auth/SignInResultExtensions.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Identity; + +namespace Volo.Abp.Account.Web.Auth +{ + //TODO: Move to the framework..? + public static class SignInResultExtensions + { + public static string GetResultAsString(this SignInResult signInResult) + { + if (signInResult.Succeeded) + { + return "Succeeded"; + } + + if (signInResult.IsLockedOut) + { + return "IsLockedOut"; + } + + if (signInResult.IsNotAllowed) + { + return "IsNotAllowed"; + } + + if (signInResult.RequiresTwoFactor) + { + return "RequiresTwoFactor"; + } + + return "Unknown"; + } + } +} diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs index e280fcd7ce..ecc1abfbcf 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Diagnostics; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; +using IdentityServer4.Events; using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Stores; @@ -12,6 +14,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Volo.Abp.Account.Web.Auth; using Volo.Abp.Identity; using Volo.Abp.Ui; using Volo.Abp.Uow; @@ -46,6 +49,7 @@ namespace Volo.Abp.Account.Web.Pages.Account private readonly IAuthenticationSchemeProvider _schemeProvider; private readonly AbpAccountOptions _accountOptions; private readonly IClientStore _clientStore; + private readonly IEventService _identityServerEvents; public IdsLoginModel( SignInManager signInManager, @@ -53,13 +57,15 @@ namespace Volo.Abp.Account.Web.Pages.Account IIdentityServerInteractionService interaction, IAuthenticationSchemeProvider schemeProvider, IOptions accountOptions, - IClientStore clientStore) + IClientStore clientStore, + IEventService identityServerEvents) { _signInManager = signInManager; _userManager = userManager; _interaction = interaction; _schemeProvider = schemeProvider; _clientStore = clientStore; + _identityServerEvents = identityServerEvents; _accountOptions = accountOptions.Value; } @@ -120,7 +126,7 @@ namespace Volo.Abp.Account.Web.Pages.Account var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl); if (context == null) { - return RedirectSafely("~/"); + return Redirect("~/"); } await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); @@ -136,12 +142,23 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); + //TODO: Find a way of getting user's id from the logged in user and do not query it again like that! + var user = await _userManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ?? + await _userManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress); + if (!result.Succeeded) { + await _identityServerEvents.RaiseAsync(new UserLoginFailureEvent(LoginInput.UserNameOrEmailAddress, "Login failed: " + result.GetResultAsString())); throw new UserFriendlyException("Login failed!"); //TODO: Handle other cases, do not throw exception } - //var user = _userManager.FindByIdAsync(result.) + Debug.Assert(user != null, nameof(user) + " != null"); + await _identityServerEvents.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName)); //TODO: Use user's name once implemented + + if (!_interaction.IsValidReturnUrl(ReturnUrl)) + { + return Redirect("~/"); + } return RedirectSafely(ReturnUrl, ReturnUrlHash); } diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index fa6ac31d6d..f5b527a464 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -19,14 +19,6 @@ - - - - - - - - From eb8020f29e8520ed8ac1e5590c1f99bdf6b875ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 1 Mar 2018 11:42:36 +0300 Subject: [PATCH 04/17] Added IDS to pipeline --- .../MicroservicesAuthServerModule.cs | 13 +++++++++++-- .../MicroserviceDemo.AuthServer/appsettings.json | 7 +++++++ .../IdentityServer/AbpIdentityServerDomainModule.cs | 2 -- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs index 21e54e30f6..7a9e6b2290 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs @@ -13,6 +13,7 @@ using Volo.Abp.Autofac; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.IdentityServer; using Volo.Abp.IdentityServer.EntityFrameworkCore; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -58,7 +59,15 @@ namespace MicroserviceDemo.AuthServer }); } - services.AddAuthorization(); + services.AddAuthentication() + .AddFacebook(options => + { + options.AppId = configuration["Authentication:Facebook:AppId"]; + options.AppSecret = configuration["Authentication:Facebook:AppSecret"]; + + options.Scope.Add("email"); + options.Scope.Add("public_profile"); + }); services.AddAssemblyOf(); } @@ -75,7 +84,7 @@ namespace MicroserviceDemo.AuthServer app.UseStaticFiles(); app.UseVirtualFiles(); - app.UseAuthentication(); + app.UseIdentityServer(); //This internally adds .UseAuthentication() (we should be carefully about that) app.UseMvc(routes => { diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/appsettings.json b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/appsettings.json index a3a7ec3fb7..2d740d39f2 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/appsettings.json +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/appsettings.json @@ -1,5 +1,12 @@ { "ConnectionStrings": { "Default": "Server=localhost;Database=MicroservicesDemo.Web;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "Authentication": { + "Facebook": { + "IsEnabled": "true", + "AppId": "911417875702990", + "AppSecret": "adea0bff222ae340d8fb0ce3e6275d6b" + } } } \ No newline at end of file diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs index 3e49a826e6..fe033da5a9 100644 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs @@ -33,8 +33,6 @@ namespace Volo.Abp.IdentityServer identityServerBuilder .AddDeveloperSigningCredential() - //.AddInMemoryApiResources(IdentityServerConfig.GetApiResources()) - //.AddInMemoryClients(IdentityServerConfig.GetClients()) .AddAbpIdentityServer(); services.ExecutePreConfiguredActions(identityServerBuilder); From 0cc656f62771c94d2ea6fff9999b8f7ebbab9384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 1 Mar 2018 14:06:53 +0300 Subject: [PATCH 05/17] Handle Windows external login. --- .../Pages/Account/Login.cshtml | 16 +++--- .../Pages/Account/Login.cshtml.cs | 55 ++++++++++++++++++- src/Volo.Abp.Account.Web/AbpAccountOptions.cs | 3 + 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml index 7e45ba5176..61364f038d 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml @@ -1,7 +1,7 @@ @page @model Volo.Abp.Account.Web.Pages.Account.IdsLoginModel
- + @if (Model.EnableLocalLogin) {
@@ -41,16 +41,16 @@

Use another service to log in.

-
- @foreach (var provider in Model.VisibleExternalProviders) - { - - } -
+ + + @foreach (var provider in Model.VisibleExternalProviders) + { + + }
} - + @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) {
diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs index ecc1abfbcf..03b67acf27 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -4,7 +4,9 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Security.Claims; +using System.Security.Principal; using System.Threading.Tasks; +using IdentityModel; using IdentityServer4.Events; using IdentityServer4.Models; using IdentityServer4.Services; @@ -164,13 +166,18 @@ namespace Volo.Abp.Account.Web.Pages.Account } [UnitOfWork] - public virtual IActionResult OnPostExternalLogin(string provider, string returnUrl = "", string returnUrlHash = "") + public virtual async Task OnPostExternalLogin(string provider) { - var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { returnUrl, returnUrlHash }); + if (_accountOptions.WindowsAuthenticationSchemeName == provider) + { + return await ProcessWindowsLoginAsync(); + } + var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }); var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + properties.Items["scheme"] = provider; - return new ChallengeResult(provider, properties); + return Challenge(properties, provider); } [UnitOfWork] @@ -234,6 +241,48 @@ namespace Volo.Abp.Account.Web.Pages.Account return user; } + private async Task ProcessWindowsLoginAsync() + { + var result = await HttpContext.AuthenticateAsync(_accountOptions.WindowsAuthenticationSchemeName); + if (!(result?.Principal is WindowsPrincipal windowsPrincipal)) + { + return Challenge(_accountOptions.WindowsAuthenticationSchemeName); + } + + var props = new AuthenticationProperties + { + RedirectUri = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }), + Items = + { + {"scheme", _accountOptions.WindowsAuthenticationSchemeName}, + } + }; + + var identity = new ClaimsIdentity(_accountOptions.WindowsAuthenticationSchemeName); + identity.AddClaim(new Claim(JwtClaimTypes.Subject, windowsPrincipal.Identity.Name)); + identity.AddClaim(new Claim(JwtClaimTypes.Name, windowsPrincipal.Identity.Name)); + + //TODO: Consider to add Windows groups the the identity + //if (_accountOptions.IncludeWindowsGroups) + //{ + // var windowsIdentity = windowsPrincipal.Identity as WindowsIdentity; + // if (windowsIdentity != null) + // { + // var groups = windowsIdentity.Groups?.Translate(typeof(NTAccount)); + // var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value)); + // identity.AddClaims(roles); + // } + //} + + await HttpContext.SignInAsync( + IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme, + new ClaimsPrincipal(identity), + props + ); + + return RedirectSafely(props.RedirectUri); + } + public class LoginInputModel { [Required] diff --git a/src/Volo.Abp.Account.Web/AbpAccountOptions.cs b/src/Volo.Abp.Account.Web/AbpAccountOptions.cs index e814ee44f5..21e72155cc 100644 --- a/src/Volo.Abp.Account.Web/AbpAccountOptions.cs +++ b/src/Volo.Abp.Account.Web/AbpAccountOptions.cs @@ -2,6 +2,9 @@ { public class AbpAccountOptions { + /// + /// Default value: . + /// public string WindowsAuthenticationSchemeName { get; set; } public AbpAccountOptions() From 1d1fd3c92ce34adf5e6dc5e6e284ab1ebbb1aafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 1 Mar 2018 14:33:52 +0300 Subject: [PATCH 06/17] Added notes. --- .../Pages/Account/Login.cshtml.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs index 03b67acf27..990e168229 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -183,6 +183,11 @@ namespace Volo.Abp.Account.Web.Pages.Account [UnitOfWork] public virtual async Task OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) { + //TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample) + /* Also did not implement these: + * - Logout(string logoutId) + */ + if (remoteError != null) { Logger.LogWarning($"External login callback error: {remoteError}"); @@ -208,13 +213,13 @@ namespace Volo.Abp.Account.Web.Pages.Account throw new UserFriendlyException("Cannot proceed because user is locked out!"); } - //TODO: Handle other cases - if (result.Succeeded) { return RedirectSafely(returnUrl, returnUrlHash); } + //TODO: Handle other cases for result! + // Get the information about the user from the external login provider var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) From ff65d1520e68300a6612d3322868b716c2ad8355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 1 Mar 2018 15:46:47 +0300 Subject: [PATCH 07/17] Aded consent page for IDS. --- Volo.Abp.sln | 4 +- .../Pages/Account/Login.cshtml.cs | 1 + .../Pages/Account/_ScopeListItem.cshtml | 38 +++ .../Pages/Consent.cshtml | 76 ++++++ .../Pages/Consent.cshtml.cs | 218 ++++++++++++++++++ .../Pages/_ViewImports.cshtml | 3 + ...Volo.Abp.Account.Web.IdentityServer.csproj | 4 + 7 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/_ViewImports.cshtml diff --git a/Volo.Abp.sln b/Volo.Abp.sln index ee8cece3d9..f414740370 100644 --- a/Volo.Abp.sln +++ b/Volo.Abp.sln @@ -296,9 +296,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroserviceDemo.TenancySer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MultiTenancy.HttpApi.Client", "src\Volo.Abp.MultiTenancy.HttpApi.Client\Volo.Abp.MultiTenancy.HttpApi.Client.csproj", "{76D24E2C-8DB0-48B7-9FC4-02231B8B9F39}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroserviceDemo.AuthServer", "src\MicroserviceDemo\MicroserviceDemo.AuthServer\MicroserviceDemo.AuthServer.csproj", "{A177B8B7-ACAD-4F75-B6D4-B72F7BC2E40A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroserviceDemo.AuthServer", "src\MicroserviceDemo\MicroserviceDemo.AuthServer\MicroserviceDemo.AuthServer.csproj", "{A177B8B7-ACAD-4F75-B6D4-B72F7BC2E40A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Account.Web.IdentityServer", "src\Volo.Abp.Account.Web.IdentityServer\Volo.Abp.Account.Web.IdentityServer.csproj", "{E4AB8A4F-BB59-4BDB-B915-877CE97D8113}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Account.Web.IdentityServer", "src\Volo.Abp.Account.Web.IdentityServer\Volo.Abp.Account.Web.IdentityServer.csproj", "{E4AB8A4F-BB59-4BDB-B915-877CE97D8113}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs index 990e168229..d7c95c98e5 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -23,6 +23,7 @@ using Volo.Abp.Uow; namespace Volo.Abp.Account.Web.Pages.Account { + //TODO: Inherit from LoginModel of Account.Web project. We should design it as extensible. public class IdsLoginModel : AccountModelBase { [HiddenInput] diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml new file mode 100644 index 0000000000..12d6ae8c01 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml @@ -0,0 +1,38 @@ +@using Volo.Abp.Account.Web.Pages +@using Volo.Abp.Account.Web.Pages.Account +@model Volo.Abp.Account.Web.Pages.ConsentModel.ScopeViewModel + +@* TODO: Should re-format this, just made copy/paste *@ + +
  • + + @if (Model.Required) + { + (required) + } + @if (Model.Description != null) + { + + } +
  • \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml new file mode 100644 index 0000000000..fe23d661f8 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml @@ -0,0 +1,76 @@ +@page +@using Volo.Abp.Account.Web.Pages +@using Volo.Abp.Account.Web.Pages.Account +@model ConsentModel + + +
    +
    +

    + @if (Model.ClientLogoUrl != null) + { + + } + @Model.ClientName + is requesting your permission +

    +
    +
    +
    + +
    + + + +
    Uncheck the permissions you do not wish to grant.
    + + @if (Model.IdentityScopes.Any()) + { +

    Personal Information

    + +
      + @foreach (var scope in Model.IdentityScopes) + { + @Html.Partial("Account/_ScopeListItem", scope) + } +
    + } + + @if (Model.ResourceScopes.Any()) + { +

    Application Access

    + +
      + @foreach (var scope in Model.ResourceScopes) + { + @Html.Partial("Account/_ScopeListItem", scope) + } +
    + } + + @if (Model.AllowRememberConsent) + { +
    + +
    + } + +
    + + + @if (Model.ClientUrl != null) + { + + @Model.ClientName + + } +
    + +
    + +
    +
    +
    \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs new file mode 100644 index 0000000000..bd6c18b096 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.RazorPages; + +namespace Volo.Abp.Account.Web.Pages +{ + //TODO: Move this into the Account folder!!! + public class ConsentModel : AbpPageModel + { + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [BindProperty] + public ConsentInputModel ConsentInput { get; set; } + + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public bool AllowRememberConsent { get; set; } + + public List IdentityScopes { get; set; } + + public List ResourceScopes { get; set; } + + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IResourceStore _resourceStore; + + public ConsentModel( + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IResourceStore resourceStore) + { + _interaction = interaction; + _clientStore = clientStore; + _resourceStore = resourceStore; + } + + public async Task OnGet() + { + var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); + if (request == null) + { + throw new ApplicationException($"No consent request matching request: {ReturnUrl}"); + } + + var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); + if (client == null) + { + throw new ApplicationException($"Invalid client id: {request.ClientId}"); + } + + var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); + if (resources == null || (!resources.IdentityResources.Any() && !resources.ApiResources.Any())) + { + throw new ApplicationException($"No scopes matching: {request.ScopesRequested.Aggregate((x, y) => x + ", " + y)}"); + } + + ConsentInput = new ConsentInputModel + { + RememberConsent = true, + ScopesConsented = new List() + }; + + ClientName = client.ClientId; //TODO: Consider to create a ClientInfoModel + ClientUrl = client.ClientUri; + ClientLogoUrl = client.LogoUri; + AllowRememberConsent = client.AllowRememberConsent; + + IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, true)).ToList(); + ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, true)).ToList(); + + if (resources.OfflineAccess) + { + ResourceScopes = ResourceScopes.Union(new[] {GetOfflineAccessScope(true)}).ToList(); + } + } + + public async Task OnPost(string userDecision) + { + var result = await ProcessConsentAsync(); + + if (result.IsRedirect) + { + return Redirect(result.RedirectUri); + } + + if (result.HasValidationError) + { + ModelState.AddModelError("", result.ValidationError); + } + + throw new ApplicationException("Error: "); + } + + private async Task ProcessConsentAsync() + { + var result = new ProcessConsentResult(); + + ConsentResponse grantedConsent = null; + + if (ConsentInput.UserDecision == "no") + { + grantedConsent = ConsentResponse.Denied; + } + else + { + if (ConsentInput.ScopesConsented != null && ConsentInput.ScopesConsented.Any()) + { + var scopes = ConsentInput.ScopesConsented; + + grantedConsent = new ConsentResponse + { + RememberConsent = ConsentInput.RememberConsent, + ScopesConsented = scopes.ToArray() + }; + } + else + { + result.ValidationError = "You must pick at least one permission"; + } + } + + if (grantedConsent != null) + { + // validate return url is still valid + var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); + if (request == null) return result; + + // communicate outcome of consent back to identityserver + await _interaction.GrantConsentAsync(request, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = ReturnUrl; //TODO: ReturnUrlHash? + } + + return result; + } + + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Name = identity.Name, + DisplayName = identity.DisplayName, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(Scope scope, bool check) + { + return new ScopeViewModel + { + Name = scope.Name, + DisplayName = scope.DisplayName, + Description = scope.Description, + Emphasize = scope.Emphasize, + Required = scope.Required, + Checked = check || scope.Required + }; + } + + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = "Offline Access", //TODO: Localize + Description = "Access to your applications and resources, even when you are offline", + Emphasize = true, + Checked = check + }; + } + + public class ConsentInputModel + { + public string UserDecision { get; set; } + + public List ScopesConsented { get; set; } + + public bool RememberConsent { get; set; } + } + + public class ScopeViewModel + { + public string Name { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public bool Emphasize { get; set; } + public bool Required { get; set; } + public bool Checked { get; set; } + } + + public class ProcessConsentResult + { + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/_ViewImports.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..05e1baf060 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index f5b527a464..26958e48e0 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -19,6 +19,10 @@ + + + + From 81ceed974fca8527fd2d1b197871dab61610eced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 2 Mar 2018 10:47:00 +0300 Subject: [PATCH 08/17] Added grants page. --- .../Pages/Account/Grants.cshtml | 82 +++++++++++++++++++ .../Pages/Account/Grants.cshtml.cs | 75 +++++++++++++++++ ...Volo.Abp.Account.Web.IdentityServer.csproj | 6 +- 3 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml.cs diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml new file mode 100644 index 0000000000..069301af3a --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml @@ -0,0 +1,82 @@ +@page +@using Volo.Abp.Account.Web.Pages.Account +@model GrantsModel + +@* TODO: Should work on the page HTML, copy/pasted from IDS4 samples *@ + +
    + + @if (Model.Grants.Any() == false) + { +
    +
    +
    + You have not given access to any applications +
    +
    +
    + } + else + { + foreach (var grant in Model.Grants) + { +
    +
    + @if (grant.ClientLogoUrl != null) + { + + } +
    +
    +
    @grant.ClientName
    +
    + Created: @grant.Created.ToString("yyyy-MM-dd") +
    + @if (grant.Expires.HasValue) + { +
    + Expires: @grant.Expires.Value.ToString("yyyy-MM-dd") +
    + } + @if (grant.IdentityGrantNames.Any()) + { +
    +
    Identity Grants
    +
      + @foreach (var name in grant.IdentityGrantNames) + { +
    • @name
    • + } +
    +
    + } + @if (grant.ApiGrantNames.Any()) + { +
    +
    API Grants
    +
      + @foreach (var name in grant.ApiGrantNames) + { +
    • @name
    • + } +
    +
    + } +
    +
    +
    + + +
    +
    +
    + } + } +
    \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml.cs new file mode 100644 index 0000000000..eeb83453a6 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.RazorPages; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public class GrantsModel : AbpPageModel + { + public List Grants { get; set; } + + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clients; + private readonly IResourceStore _resources; + + public GrantsModel(IIdentityServerInteractionService interaction, + IClientStore clients, + IResourceStore resources) + { + _interaction = interaction; + _clients = clients; + _resources = resources; + } + + public async Task OnGet() + { + Grants = new List(); + + foreach (var consent in await _interaction.GetAllUserConsentsAsync()) + { + var client = await _clients.FindClientByIdAsync(consent.ClientId); + if (client != null) + { + var resources = await _resources.FindResourcesByScopeAsync(consent.Scopes); + + var item = new GrantViewModel + { + ClientId = client.ClientId, + ClientName = client.ClientName ?? client.ClientId, + ClientLogoUrl = client.LogoUri, + ClientUrl = client.ClientUri, + Created = consent.CreationTime, + Expires = consent.Expiration, + IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), + ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() + }; + + Grants.Add(item); + } + } + } + + public async Task OnPostRevokeAsync(string clientId) + { + await _interaction.RevokeUserConsentAsync(clientId); + return Redirect("/"); //TODO: ..? + } + + public class GrantViewModel + { + public string ClientId { get; set; } + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public DateTime Created { get; set; } + public DateTime? Expires { get; set; } + public IEnumerable IdentityGrantNames { get; set; } + public IEnumerable ApiGrantNames { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 26958e48e0..32143716e5 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -18,11 +18,7 @@ - - - - - + From e104b10dc7d49fd17be570f51241886c2d74d055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 2 Mar 2018 10:49:18 +0300 Subject: [PATCH 09/17] Fix form url for grants page. --- .../Pages/Account/Grants.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml index 069301af3a..1f02ca8888 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml @@ -71,7 +71,7 @@ }
    -
    +
    From a53f9796c0cb5d220d6c0d690278bd96c4880bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 2 Mar 2018 11:09:12 +0300 Subject: [PATCH 10/17] Created a console client for the microservice demo --- .gitignore | 3 +- Volo.Abp.sln | 7 +++ .../MicroserviceDemo.ConsoleClient.csproj | 12 +++++ .../MicroserviceDemo.ConsoleClient/Program.cs | 54 +++++++++++++++++++ .../MicroservicesDemoWebModule.cs | 24 ++++++++- 5 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/MicroserviceDemo.ConsoleClient.csproj create mode 100644 src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/Program.cs diff --git a/.gitignore b/.gitignore index cf6f59024d..a20b3f93d9 100644 --- a/.gitignore +++ b/.gitignore @@ -257,4 +257,5 @@ src/AbpDesk/AbpDesk.Web.Mvc/Logs /src/Volo.Abp.Identity.HttpApi.Host/Logs src/MicroserviceDemo/MicroserviceDemo.Web/Logs/*.txt src/MicroserviceDemo/MicroserviceDemo.TenantService/Logs/*.txt -src/MicroserviceDemo/MicroserviceDemo.AuthServer/Logs/*.txt \ No newline at end of file +src/MicroserviceDemo/MicroserviceDemo.AuthServer/Logs/*.txt +src/MicroserviceDemo/MicroserviceDemo.TenancyService/Logs/*.txt diff --git a/Volo.Abp.sln b/Volo.Abp.sln index f414740370..f31a1fa152 100644 --- a/Volo.Abp.sln +++ b/Volo.Abp.sln @@ -300,6 +300,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroserviceDemo.AuthServer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Account.Web.IdentityServer", "src\Volo.Abp.Account.Web.IdentityServer\Volo.Abp.Account.Web.IdentityServer.csproj", "{E4AB8A4F-BB59-4BDB-B915-877CE97D8113}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroserviceDemo.ConsoleClient", "src\MicroserviceDemo\MicroserviceDemo.ConsoleClient\MicroserviceDemo.ConsoleClient.csproj", "{CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -790,6 +792,10 @@ Global {E4AB8A4F-BB59-4BDB-B915-877CE97D8113}.Debug|Any CPU.Build.0 = Debug|Any CPU {E4AB8A4F-BB59-4BDB-B915-877CE97D8113}.Release|Any CPU.ActiveCfg = Release|Any CPU {E4AB8A4F-BB59-4BDB-B915-877CE97D8113}.Release|Any CPU.Build.0 = Release|Any CPU + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -936,6 +942,7 @@ Global {76D24E2C-8DB0-48B7-9FC4-02231B8B9F39} = {CA154803-3589-47B3-B7CB-B18F94FE1EB6} {A177B8B7-ACAD-4F75-B6D4-B72F7BC2E40A} = {3510E248-DC9F-4A07-8134-02E7F5CC5783} {E4AB8A4F-BB59-4BDB-B915-877CE97D8113} = {DB012309-74FD-4D5A-B843-DD77BF053BF4} + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39} = {3510E248-DC9F-4A07-8134-02E7F5CC5783} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/MicroserviceDemo.ConsoleClient.csproj b/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/MicroserviceDemo.ConsoleClient.csproj new file mode 100644 index 0000000000..3d9798a90e --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/MicroserviceDemo.ConsoleClient.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.0 + + + + + + + diff --git a/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/Program.cs b/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/Program.cs new file mode 100644 index 0000000000..d2611feda0 --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/Program.cs @@ -0,0 +1,54 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using IdentityModel.Client; + +namespace MicroserviceDemo.ConsoleClient +{ + class Program + { + static void Main(string[] args) + { + RunDemo().Wait(); + Console.ReadLine(); + } + + private static async Task RunDemo() + { + // discover endpoints from metadata + var disco = await DiscoveryClient.GetAsync("http://localhost:54307"); + if (disco.IsError) + { + Console.WriteLine(disco.Error); + return; + } + + // request token + var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret"); + var tokenResponse = await tokenClient.RequestClientCredentialsAsync("multi-tenancy-api"); + + if (tokenResponse.IsError) + { + Console.WriteLine(tokenResponse.Error); + return; + } + + Console.WriteLine(tokenResponse.Json); + + // call api + var client = new HttpClient(); + client.SetBearerToken(tokenResponse.AccessToken); + + var response = await client.GetAsync("http://localhost:63877/api/multi-tenancy/tenant?SkipCount=0&MaxResultCount=100"); + if (!response.IsSuccessStatusCode) + { + Console.WriteLine(response.StatusCode); + } + else + { + var content = await response.Content.ReadAsStringAsync(); + Console.WriteLine(content); + } + } + } +} diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs index e2cc2c8ca3..670ec04226 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs @@ -75,7 +75,29 @@ namespace MicroserviceDemo.Web }); }); - services.AddAuthentication(); + services.AddAuthentication(options => + { + options.DefaultScheme = "Cookies"; + options.DefaultChallengeScheme = "oidc"; + }) + .AddCookie("Cookies") + .AddOpenIdConnect("oidc", options => + { + options.SignInScheme = "Cookies"; + + options.Authority = "http://localhost:54307"; + options.RequireHttpsMetadata = false; + + options.ClientId = "mvc"; + options.ClientSecret = "secret"; + options.ResponseType = "code id_token"; + + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + + options.Scope.Add("api1"); + options.Scope.Add("offline_access"); + }); services.Configure(configuration); From c84c5b76c1af7cf10a7f3c7d23ce48d218c3757f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 5 Mar 2018 09:44:13 +0300 Subject: [PATCH 11/17] Working on identity server integration. --- .../MicroservicesAuthServerModule.cs | 7 +- .../MicroservicesDemoWebModule.cs | 46 +++++-- .../MicroserviceDemo.Web/Pages/Index.cshtml | 13 +- .../Pages/Account/Login.cshtml.cs | 4 +- .../Pages/Account/_ScopeListItem.cshtml | 36 ++---- .../Pages/Consent.cshtml | 65 +++++++--- .../Pages/Consent.cshtml.cs | 114 +++++++++++------- .../Pages/Account/Login.cshtml.cs | 3 +- .../AbpIdentityServiceCollectionExtensions.cs | 3 +- ... => AbpIdentityServerBuilderExtensions.cs} | 2 +- .../AbpIdentityServerDomainModule.cs | 15 ++- .../AspNetIdentity/AbpProfileService.cs | 23 +++- .../Clients/ClientAutoMapperProfile.cs | 5 + 13 files changed, 228 insertions(+), 108 deletions(-) rename src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/{AbpZeroIdentityServerBuilderExtensions.cs => AbpIdentityServerBuilderExtensions.cs} (96%) diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs index 7a9e6b2290..dd0a0ccc12 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs @@ -13,7 +13,6 @@ using Volo.Abp.Autofac; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore; -using Volo.Abp.IdentityServer; using Volo.Abp.IdentityServer.EntityFrameworkCore; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -48,6 +47,12 @@ namespace MicroserviceDemo.AuthServer }); }); + services.Configure(iis => + { + iis.AuthenticationDisplayName = "Windows"; + iis.AutomaticAuthentication = false; + }); + if (hostingEnvironment.IsDevelopment()) { services.Configure(options => diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs index 670ec04226..ba35973aff 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs @@ -1,9 +1,16 @@ using System; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; using Volo.Abp.Account.Web; @@ -21,6 +28,7 @@ using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy.Web; using Volo.Abp.Permissions; using Volo.Abp.Permissions.EntityFrameworkCore; +using Volo.Abp.Security.Claims; namespace MicroserviceDemo.Web { @@ -75,28 +83,41 @@ namespace MicroserviceDemo.Web }); }); + var xxx = JwtSecurityTokenHandler.DefaultInboundClaimTypeMap; + + //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //TODO: Try to understand if this is really needed? + services.AddAuthentication(options => { - options.DefaultScheme = "Cookies"; - options.DefaultChallengeScheme = "oidc"; + //options.DefaultScheme = IdentityConstants.ApplicationScheme; + //options.DefaultChallengeScheme = "oidc"; }) - .AddCookie("Cookies") + //.AddCookie(IdentityConstants.ApplicationScheme) .AddOpenIdConnect("oidc", options => { - options.SignInScheme = "Cookies"; + //options.SignInScheme = IdentityConstants.ApplicationScheme; options.Authority = "http://localhost:54307"; options.RequireHttpsMetadata = false; - options.ClientId = "mvc"; + options.ClientId = "client"; options.ClientSecret = "secret"; - options.ResponseType = "code id_token"; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; - options.Scope.Add("api1"); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("email"); + options.Scope.Add("phone"); + options.Scope.Add("multi-tenancy-api"); options.Scope.Add("offline_access"); + + options.ClaimActions.MapUniqueJsonKey(AbpClaimTypes.UserName, "name"); + options.ClaimActions.MapUniqueJsonKey(AbpClaimTypes.Email, "email"); + + options.SecurityTokenValidator = new MyJwtSecurityTokenHandler(); }); services.Configure(configuration); @@ -153,4 +174,15 @@ namespace MicroserviceDemo.Web return builder.Build(); } } + + public class MyJwtSecurityTokenHandler : JwtSecurityTokenHandler + { + protected override ClaimsIdentity CreateClaimsIdentity(JwtSecurityToken jwt, string issuer, + TokenValidationParameters validationParameters) + { + var xxx = base.CreateClaimsIdentity(jwt, issuer, validationParameters); + + return xxx; + } + } } \ No newline at end of file diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/Pages/Index.cshtml b/src/MicroserviceDemo/MicroserviceDemo.Web/Pages/Index.cshtml index f61b9abf2f..c92bb117dd 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/Pages/Index.cshtml +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/Pages/Index.cshtml @@ -1,11 +1,14 @@ @page +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Http @model MicroserviceDemo.Web.Pages.IndexModel +@inject IHttpContextAccessor HttpContextAccessor

    Login - + @if (User.Identity.IsAuthenticated) { foreach (var claim in User.Claims) @@ -14,6 +17,14 @@ } } +


    + + @{ + var accessToken = await HttpContextAccessor.HttpContext.GetTokenAsync("access_token"); + } + + @accessToken
    +

    \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs index d7c95c98e5..a1db7969f1 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -133,7 +133,7 @@ namespace Volo.Abp.Account.Web.Pages.Account } await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); - return RedirectSafely(ReturnUrl); + return Redirect(ReturnUrl); } ValidateModel(); @@ -163,7 +163,7 @@ namespace Volo.Abp.Account.Web.Pages.Account return Redirect("~/"); } - return RedirectSafely(ReturnUrl, ReturnUrlHash); + return Redirect(ReturnUrl); //ReturnUrlHash? } [UnitOfWork] diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml index 12d6ae8c01..95fee3a858 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml @@ -1,30 +1,20 @@ @using Volo.Abp.Account.Web.Pages @using Volo.Abp.Account.Web.Pages.Account -@model Volo.Abp.Account.Web.Pages.ConsentModel.ScopeViewModel - +@model ConsentModel.ScopeViewModel @* TODO: Should re-format this, just made copy/paste *@ -
  • - +
    + +
    @if (Model.Required) { (required) diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml index fe23d661f8..fa3efda621 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml @@ -7,48 +7,85 @@

    - @if (Model.ClientLogoUrl != null) + @if (Model.ClientInfo.ClientLogoUrl != null) { - + } - @Model.ClientName + + @Model.ClientInfo.ClientName is requesting your permission

    -
    +
    Uncheck the permissions you do not wish to grant.
    - @if (Model.IdentityScopes.Any()) + @if (Model.ConsentInput.IdentityScopes.Any()) {

    Personal Information

      - @foreach (var scope in Model.IdentityScopes) + @for (var i = 0; i < Model.ConsentInput.IdentityScopes.Count; i++) { - @Html.Partial("Account/_ScopeListItem", scope) +
    • +
      + +
      + @* TODO: Use attributes on the view model instead of using hidden here *@ + @if (Model.ConsentInput.IdentityScopes[i].Description != null) + { + + } +
    • }
    } - @if (Model.ResourceScopes.Any()) + @if (Model.ConsentInput.ApiScopes.Any()) {

    Application Access

      - @foreach (var scope in Model.ResourceScopes) + @for (var i = 0; i < Model.ConsentInput.ApiScopes.Count; i++) { - @Html.Partial("Account/_ScopeListItem", scope) +
    • +
      + +
      + @* TODO: Use attributes on the view model instead of using hidden here *@ + @if (Model.ConsentInput.ApiScopes[i].Description != null) + { + + } +
    • }
    } - @if (Model.AllowRememberConsent) + @if (Model.ClientInfo.AllowRememberConsent) {