Skip to content

Commit ef29002

Browse files
authored
Merge pull request #90 from damienbod/Update-IdentityServer4-V4
Update identity server4 v4
2 parents 909bd6b + 10b875a commit ef29002

41 files changed

Lines changed: 857 additions & 2967 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
[Readme](https://github.com/damienbod/IdentityServer4AspNetCoreIdentityTemplate/blob/master/README.md)
44

5+
2020-06-26 5.0.0
6+
- Updated to IdentityServer4 V4, updated packages
7+
58
2020-05-06 4.0.6
69
- Updated FIDO2 to 1.1.0
710
- Updating Nuget packages, npm packages

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ dotnet new -i IdentityServer4AspNetCoreIdentityTemplate
5353
Locally built nupkg:
5454

5555
```
56-
dotnet new -i IdentityServer4AspNetCoreIdentityTemplate.4.0.6.nupkg
56+
dotnet new -i IdentityServer4AspNetCoreIdentityTemplate.5.0.0.nupkg
5757
```
5858

5959
Local folder:

content/IdentityServer4AspNetCoreIdentityTemplate.nuspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
33
<metadata>
44
<id>IdentityServer4AspNetCoreIdentityTemplate</id>
5-
<version>4.0.6</version>
5+
<version>5.0.0</version>
66
<title>IdentityServer4.Identity.Template</title>
77
<license type="file">LICENSE</license>
88
<description>
@@ -17,7 +17,7 @@
1717
<requireLicenseAcceptance>false</requireLicenseAcceptance>
1818
<copyright>2020 damienbod</copyright>
1919
<summary>This template provides a simle getting started for IdentityServer4 with Identity. Identity is Localized and the UI uses Bootstrap 4, Remove AllowAnonymous from the logout</summary>
20-
<releaseNotes>Same Site fix for Anti forgery cookie, Language Cookie add Same Site + Secure, Init language selection fix</releaseNotes>
20+
<releaseNotes>Updated to IdentityServer4 V4, updated packages</releaseNotes>
2121
<repository type="git" url="https://github.com/damienbod/IdentityServer4AspNetCoreIdentityTemplate" />
2222
<packageTypes>
2323
<packageType name="Template" />

content/StsServerIdentity/Config.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,34 @@ public static IEnumerable<IdentityResource> GetIdentityResources()
1919
};
2020
}
2121

22+
public static IEnumerable<ApiScope> GetApiScopes()
23+
{
24+
return new List<ApiScope>
25+
{
26+
//new ApiScope("dataEventRecords", "Scope for the dataEventRecords ApiResource"),
27+
//new ApiScope("securedFiles", "Scope for the securedFiles ApiResource")
28+
};
29+
}
30+
2231
public static IEnumerable<ApiResource> GetApiResources()
2332
{
2433
return new List<ApiResource>
2534
{
26-
// example code
27-
//new ApiResource("dataEventRecords")
35+
//new ApiResource("dataEventRecordsApi")
2836
//{
2937
// ApiSecrets =
3038
// {
3139
// new Secret("dataEventRecordsSecret".Sha256())
3240
// },
33-
// Scopes =
41+
// Scopes = new List<string> { "dataEventRecords" }
42+
//},
43+
//new ApiResource("securedFilesApi")
44+
//{
45+
// ApiSecrets =
3446
// {
35-
// new Scope
36-
// {
37-
// Name = "dataeventrecords",
38-
// DisplayName = "Scope for the dataEventRecords ApiResource"
39-
// }
47+
// new Secret("securedFilesSecret".Sha256())
4048
// },
41-
// UserClaims = { "role", "admin", "user", "dataEventRecords", "dataEventRecords.admin", "dataEventRecords.user" }
49+
// Scopes = new List<string> { "securedFiles" }
4250
//}
4351
};
4452
}

content/StsServerIdentity/Controllers/AccountController.cs

Lines changed: 152 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@
1111
using StsServerIdentity.Models;
1212
using IdentityServer4.Services;
1313
using IdentityServer4.Stores;
14-
using IdentityServer4.Models;
1514
using IdentityModel;
1615
using IdentityServer4;
1716
using IdentityServer4.Extensions;
18-
using System.Globalization;
1917
using StsServerIdentity.Services;
2018
using Microsoft.Extensions.Localization;
2119
using StsServerIdentity.Resources;
2220
using System.Reflection;
21+
using Microsoft.AspNetCore.Authentication;
2322

2423
namespace StsServerIdentity.Controllers
2524
{
@@ -35,6 +34,8 @@ public class AccountController : Controller
3534
private readonly IClientStore _clientStore;
3635
private readonly IPersistedGrantService _persistedGrantService;
3736
private readonly IStringLocalizer _sharedLocalizer;
37+
private readonly IAuthenticationSchemeProvider _schemeProvider;
38+
private readonly IEventService _events;
3839

3940
public AccountController(
4041
UserManager<ApplicationUser> userManager,
@@ -45,7 +46,10 @@ public AccountController(
4546
IIdentityServerInteractionService interaction,
4647
IClientStore clientStore,
4748
IStringLocalizerFactory factory,
48-
Fido2Storage fido2Storage)
49+
Fido2Storage fido2Storage,
50+
IAuthenticationSchemeProvider schemeProvider,
51+
IEventService events)
52+
4953
{
5054
_fido2Storage = fido2Storage;
5155
_userManager = userManager;
@@ -55,30 +59,24 @@ public AccountController(
5559
_logger = loggerFactory.CreateLogger<AccountController>();
5660
_interaction = interaction;
5761
_clientStore = clientStore;
62+
_schemeProvider = schemeProvider;
63+
_events = events;
5864

5965
var type = typeof(SharedResource);
6066
var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
6167
_sharedLocalizer = factory.Create("SharedResource", assemblyName.Name);
6268
}
6369

64-
//
65-
// GET: /Account/Login
6670
[HttpGet]
6771
[AllowAnonymous]
68-
public async Task<IActionResult> Login(string returnUrl = null)
72+
public async Task<IActionResult> Login(string returnUrl)
6973
{
70-
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
71-
if (context?.IdP != null)
72-
{
73-
// if IdP is passed, then bypass showing the login screen
74-
return ExternalLogin(context.IdP, returnUrl);
75-
}
74+
// build a model so we know what to show on the login page
75+
var vm = await BuildLoginViewModelAsync(returnUrl);
7676

77-
var vm = await BuildLoginViewModelAsync(returnUrl, context);
78-
79-
if (vm.EnableLocalLogin == false && vm.ExternalProviders.Count() == 1)
77+
if (vm.IsExternalLoginOnly)
8078
{
81-
// only one option for logging in
79+
// we only have one option for logging in and it's an external provider
8280
return ExternalLogin(vm.ExternalProviders.First().AuthenticationScheme, returnUrl);
8381
}
8482

@@ -168,76 +166,19 @@ public IActionResult ErrorEnable2FA()
168166
return View();
169167
}
170168

171-
private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context)
172-
{
173-
var loginProviders = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
174-
var providers = loginProviders
175-
.Where(x => x.DisplayName != null)
176-
.Select(x => new ExternalProvider
177-
{
178-
DisplayName = x.DisplayName,
179-
AuthenticationScheme = x.Name
180-
});
181-
182-
var allowLocal = true;
183-
if (context?.ClientId != null)
184-
{
185-
var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId);
186-
if (client != null)
187-
{
188-
allowLocal = client.EnableLocalLogin;
189-
190-
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
191-
{
192-
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme));
193-
}
194-
}
195-
}
196-
197-
return new LoginViewModel
198-
{
199-
EnableLocalLogin = allowLocal,
200-
ReturnUrl = returnUrl,
201-
Email = context?.LoginHint,
202-
ExternalProviders = providers.ToArray()
203-
};
204-
}
205-
206-
private async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model)
207-
{
208-
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
209-
var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context);
210-
vm.Email = model.Email;
211-
vm.RememberLogin = model.RememberLogin;
212-
return vm;
213-
}
214-
215-
/// <summary>
216-
/// Show logout page
217-
/// </summary>
218169
[HttpGet]
219170
public async Task<IActionResult> Logout(string logoutId)
220171
{
221-
if (User.Identity.IsAuthenticated == false)
222-
{
223-
// if the user is not authenticated, then just show logged out page
224-
return await Logout(new LogoutViewModel { LogoutId = logoutId });
225-
}
172+
// build a model so the logout page knows what to display
173+
var vm = await BuildLogoutViewModelAsync(logoutId);
226174

227-
var context = await _interaction.GetLogoutContextAsync(logoutId);
228-
if (context?.ShowSignoutPrompt == false)
175+
if (vm.ShowLogoutPrompt == false)
229176
{
230-
// it's safe to automatically sign-out
231-
return await Logout(new LogoutViewModel { LogoutId = logoutId });
177+
// if the request for logout was properly authenticated from IdentityServer, then
178+
// we don't need to show the prompt and can just log the user out directly.
179+
return await Logout(vm);
232180
}
233181

234-
// show the logout prompt. this prevents attacks where the user
235-
// is automatically signed out by another malicious web page.
236-
var vm = new LogoutViewModel
237-
{
238-
LogoutId = logoutId
239-
};
240-
241182
return View(vm);
242183
}
243184

@@ -283,11 +224,14 @@ public async Task<IActionResult> Logout(LogoutViewModel model)
283224

284225
var vm = new LoggedOutViewModel
285226
{
227+
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
286228
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
287-
ClientName = logout?.ClientId,
288-
SignOutIframeUrl = logout?.SignOutIFrameUrl
229+
ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
230+
SignOutIframeUrl = logout?.SignOutIFrameUrl,
231+
LogoutId = model.LogoutId
289232
};
290233

234+
291235
await _persistedGrantService.RemoveAllGrantsAsync(subjectId, "angular2client");
292236

293237
return View("LoggedOut", vm);
@@ -678,6 +622,133 @@ public async Task<IActionResult> VerifyCode(VerifyCodeViewModel model)
678622
}
679623
}
680624

625+
private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
626+
{
627+
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
628+
if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
629+
{
630+
var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider;
631+
632+
// this is meant to short circuit the UI and only trigger the one external IdP
633+
var vm = new LoginViewModel
634+
{
635+
EnableLocalLogin = local,
636+
ReturnUrl = returnUrl,
637+
Email = context?.LoginHint,
638+
};
639+
640+
if (!local)
641+
{
642+
vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } };
643+
}
644+
645+
return vm;
646+
}
647+
648+
var schemes = await _schemeProvider.GetAllSchemesAsync();
649+
650+
var providers = schemes
651+
.Where(x => x.DisplayName != null)
652+
.Select(x => new ExternalProvider
653+
{
654+
DisplayName = x.DisplayName ?? x.Name,
655+
AuthenticationScheme = x.Name
656+
}).ToList();
657+
658+
var allowLocal = true;
659+
if (context?.Client.ClientId != null)
660+
{
661+
var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId);
662+
if (client != null)
663+
{
664+
allowLocal = client.EnableLocalLogin;
665+
666+
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
667+
{
668+
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
669+
}
670+
}
671+
}
672+
673+
return new LoginViewModel
674+
{
675+
AllowRememberLogin = AccountOptions.AllowRememberLogin,
676+
EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
677+
ReturnUrl = returnUrl,
678+
Email = context?.LoginHint,
679+
ExternalProviders = providers.ToArray()
680+
};
681+
}
682+
683+
private async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model)
684+
{
685+
var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
686+
vm.Email = model.Email;
687+
vm.RememberLogin = model.RememberLogin;
688+
return vm;
689+
}
690+
691+
private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
692+
{
693+
var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt };
694+
695+
if (User?.Identity.IsAuthenticated != true)
696+
{
697+
// if the user is not authenticated, then just show logged out page
698+
vm.ShowLogoutPrompt = false;
699+
return vm;
700+
}
701+
702+
var context = await _interaction.GetLogoutContextAsync(logoutId);
703+
if (context?.ShowSignoutPrompt == false)
704+
{
705+
// it's safe to automatically sign-out
706+
vm.ShowLogoutPrompt = false;
707+
return vm;
708+
}
709+
710+
// show the logout prompt. this prevents attacks where the user
711+
// is automatically signed out by another malicious web page.
712+
return vm;
713+
}
714+
715+
private async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
716+
{
717+
// get context information (client name, post logout redirect URI and iframe for federated signout)
718+
var logout = await _interaction.GetLogoutContextAsync(logoutId);
719+
720+
var vm = new LoggedOutViewModel
721+
{
722+
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
723+
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
724+
ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
725+
SignOutIframeUrl = logout?.SignOutIFrameUrl,
726+
LogoutId = logoutId
727+
};
728+
729+
if (User?.Identity.IsAuthenticated == true)
730+
{
731+
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
732+
if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
733+
{
734+
var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
735+
if (providerSupportsSignout)
736+
{
737+
if (vm.LogoutId == null)
738+
{
739+
// if there's no current logout context, we need to create one
740+
// this captures necessary info from the current logged in user
741+
// before we signout and redirect away to the external IdP for signout
742+
vm.LogoutId = await _interaction.CreateLogoutContextAsync();
743+
}
744+
745+
vm.ExternalAuthenticationScheme = idp;
746+
}
747+
}
748+
}
749+
750+
return vm;
751+
}
681752
#region Helpers
682753

683754
private void AddErrors(IdentityResult result)

0 commit comments

Comments
 (0)