Merge pull request #2306 from DuendeSoftware/mb/templates

Update templates to .NET 10 and latest IdentityServer 7.4.x
This commit is contained in:
Damian Hickey 2025-12-12 11:29:53 +01:00 committed by GitHub
commit 91d2565cb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
102 changed files with 302 additions and 397 deletions

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>IdentityServerTemplate</RootNamespace>
@ -12,13 +12,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Duende.IdentityServer.EntityFramework" version="7.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.6" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Duende.IdentityServer.EntityFramework" version="7.4.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6">
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -12,11 +12,11 @@
Admin Dashboard
</h1>
<span class="text-muted">
Get a high-level overview of your Duende IdentityServer instance.
Click <a href="/.well-known/openid-configuration" target="_blank">here</a> to access the Discovery Document.
Get a high-level overview of your Duende IdentityServer instance.<br/>
Click <a href="/.well-known/openid-configuration" target="_blank">here</a> to access the Discovery Document.<br/>
<a asp-page-handler="Diagnostics">Download diagnostics data</a>
</span>
</header>
<div class="row">
<div class="col-lg-8">
<h2 class="my-3">System Information</h2>
@ -55,10 +55,8 @@
</div>
</div>
<div class="col-lg-4">
<h2 class="my-3">License Usage</h2>
<vc:license-summary></vc:license-summary>
</div>
</div>
</div>

View file

@ -1,17 +1,23 @@
using System.Reflection;
using Duende.IdentityServer;
using Duende.IdentityServer.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerTemplate.Pages.Admin;
[Authorize(Config.Policies.Admin)]
public class Index(IdentityServerLicense? license = null) : PageModel
public class Index(DiagnosticDataService? diagnosticDataService = null) : PageModel
{
public string Version => typeof(Duende.IdentityServer.Hosting.IdentityServerMiddleware).Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion.Split('+').First()
?? "unavailable";
public async Task<IActionResult> OnGetDiagnostics()
{
if (diagnosticDataService == null)
{
return NotFound();
}
public IdentityServerLicense? License { get; } = license;
var diagnosticsJson = await diagnosticDataService.GetJsonStringAsync();
Response.Headers.ContentDisposition = "attachment; filename=diagnostics.json";
return Content(diagnosticsJson, "application/json");
}
}

View file

@ -1,6 +1,6 @@
using System.Buffers.Text;
using System.Text;
using System.Text.Json;
using Duende.IdentityModel;
using Microsoft.AspNetCore.Authentication;
namespace IdentityServerTemplate.Pages.Diagnostics;
@ -15,7 +15,7 @@ public class ViewModel
{
if (encoded != null)
{
var bytes = Base64Url.Decode(encoded);
var bytes = Base64Url.DecodeFromChars(encoded);
var value = Encoding.UTF8.GetString(bytes);
Clients = JsonSerializer.Deserialize<string[]>(value) ?? Enumerable.Empty<string>();
return;

View file

@ -1,6 +1,6 @@
using Duende.IdentityServer.Models;
namespace IdentityServerAspNetIdentity;
namespace IdentityServerHost;
public static class Config
{

View file

@ -8,7 +8,7 @@ using Microsoft.IdentityModel.Tokens;
using Serilog;
using Serilog.Filters;
namespace IdentityServerAspNetIdentity;
namespace IdentityServerHost;
internal static class HostingExtensions
{

View file

@ -1,19 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>IdentityServerHost</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" version="7.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" version="7.4.3" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Login.Index
@model IdentityServerHost.Pages.Account.Login.Index
<div class="login-page">
<div class="lead">
@ -86,4 +86,4 @@
</div>
}
</div>
</div>
</div>

View file

@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
[SecurityHeaders]
[AllowAnonymous]

View file

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public class InputModel
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public static class LoginOptions
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public class ViewModel
{

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Logout.Index
@model IdentityServerHost.Pages.Account.Logout.Index
<div class="logout-page">
<div class="lead">
@ -14,4 +14,4 @@
<button class="btn btn-primary">Yes</button>
</div>
</form>
</div>
</div>

View file

@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
[SecurityHeaders]
[AllowAnonymous]

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Logout.LoggedOut
@model IdentityServerHost.Pages.Account.Logout.LoggedOut
<div class="logged-out-page">
<h1>
@ -27,4 +27,4 @@
{
<script src="~/js/signout-redirect.js"></script>
}
}
}

View file

@ -2,7 +2,7 @@ using Duende.IdentityServer.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
[SecurityHeaders]
[AllowAnonymous]

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
public class LoggedOutViewModel
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
public static class LogoutOptions
{

View file

@ -1,6 +1,6 @@
using System.Buffers.Text;
using System.Text;
using System.Text.Json;
using Duende.IdentityModel;
using Microsoft.AspNetCore.Authentication;
namespace IdentityServerHost.Pages.Diagnostics;
@ -15,7 +15,7 @@ public class ViewModel
{
if (encoded != null)
{
var bytes = Base64Url.Decode(encoded);
var bytes = Base64Url.DecodeFromChars(encoded);
var value = Encoding.UTF8.GetString(bytes);
Clients = JsonSerializer.Deserialize<string[]>(value) ?? Enumerable.Empty<string>();
return;

View file

@ -21,7 +21,7 @@ public class Index : PageModel
public async Task OnGet(string? errorId)
{
// retrieve error details from identityserver
// retrieve error details from IdentityServer
var message = await _interaction.GetErrorContextAsync(errorId);
if (message != null)
{

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Home.Index
@model IdentityServerHost.Pages.Index
<div class="welcome-page">
<h1>

View file

@ -3,7 +3,7 @@ using Duende.IdentityServer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Home;
namespace IdentityServerHost.Pages;
[AllowAnonymous]
public class Index : PageModel

View file

@ -1,2 +1 @@
@using IdentityServerHost.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View file

@ -1,7 +1,7 @@
using System.Globalization;
using System.Text;
using Duende.IdentityServer.Licensing;
using IdentityServerAspNetIdentity;
using IdentityServerHost;
using Serilog;
Log.Logger = new LoggerConfiguration()

View file

@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Serilog;
namespace IdentityServerAspNetIdentity;
namespace IdentityServerHost;
public class SeedData
{

View file

@ -21,5 +21,15 @@
{
"path": "IdentityServerEmpty.csproj"
}
]
],
"symbols": {
"RenameCommonNamespace": {
"datatype": "string",
"displayName": "Fix common host namespace.",
"replaces": "IdentityServerHost",
"type": "derived",
"valueSource": "name",
"valueTransform": "safe_namespace"
}
}
}

View file

@ -1,6 +1,6 @@
using Duende.IdentityServer.Models;
namespace IdentityServerEmpty;
namespace IdentityServerHost;
public static class Config
{

View file

@ -2,7 +2,7 @@ using System.Globalization;
using Serilog;
using Serilog.Filters;
namespace IdentityServerEmpty;
namespace IdentityServerHost;
internal static class HostingExtensions
{

View file

@ -1,14 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>IdentityServerHost</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Duende.IdentityServer" version="7.3.0" />
<PackageReference Include="Duende.IdentityServer" version="7.4.3" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
</ItemGroup>
</Project>

View file

@ -1,7 +1,7 @@
using System.Globalization;
using System.Text;
using Duende.IdentityServer.Licensing;
using IdentityServerEmpty;
using IdentityServerHost;
using Serilog;
Log.Logger = new LoggerConfiguration()

View file

@ -1,6 +1,6 @@
using Duende.IdentityServer.Models;
namespace IdentityServerEntityFramework;
namespace IdentityServerHost;
public static class Config
{

View file

@ -1,6 +1,6 @@
using System.Globalization;
using Duende.IdentityServer;
using IdentityServerHost;
using IdentityServerHost.Pages;
using IdentityServerHost.Pages.Admin.ApiScopes;
using IdentityServerHost.Pages.Admin.Clients;
using IdentityServerHost.Pages.Admin.IdentityScopes;
@ -10,7 +10,7 @@ using Microsoft.IdentityModel.Tokens;
using Serilog;
using Serilog.Filters;
namespace IdentityServerEntityFramework;
namespace IdentityServerHost;
internal static class HostingExtensions
{

View file

@ -1,19 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>IdentityServerHost</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Duende.IdentityServer.EntityFramework" version="7.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Duende.IdentityServer.EntityFramework" version="7.4.3" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore"
Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>

View file

@ -1,14 +1,13 @@
// <auto-generated />
using System;
#nullable disable
using Duende.IdentityServer.EntityFramework.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace IdentityServerEntityFramework.Migrations.ConfigurationDb
namespace IdentityServerHost.Migrations.ConfigurationDb
{
[DbContext(typeof(ConfigurationDbContext))]
[Migration("20240123193245_Configuration")]

View file

@ -1,8 +1,8 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IdentityServerEntityFramework.Migrations.ConfigurationDb;
using Microsoft.EntityFrameworkCore.Migrations;
namespace IdentityServerHost.Migrations.ConfigurationDb;
/// <inheritdoc />
public partial class Configuration : Migration

View file

@ -1,14 +1,13 @@
// <auto-generated />
using System;
#nullable disable
using Duende.IdentityServer.EntityFramework.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace IdentityServerEntityFramework.Migrations.PersistedGrantDb
namespace IdentityServerHost.Migrations.PersistedGrantDb
{
[DbContext(typeof(PersistedGrantDbContext))]
[Migration("20240123193240_Grants")]

View file

@ -1,8 +1,8 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IdentityServerEntityFramework.Migrations.PersistedGrantDb;
using Microsoft.EntityFrameworkCore.Migrations;
namespace IdentityServerHost.Migrations.PersistedGrantDb;
/// <inheritdoc />
public partial class Grants : Migration

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Create.Index
@model IdentityServerHost.Pages.Account.Create.Index
<div class="login-page">
<div class="lead">
@ -37,4 +37,4 @@
</div>
</div>
</div>
</div>

View file

@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Create;
namespace IdentityServerHost.Pages.Account.Create;
[SecurityHeaders]
[AllowAnonymous]

View file

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace IdentityServerHost.Pages.Create;
namespace IdentityServerHost.Pages.Account.Create;
public class InputModel
{

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Login.Index
@model IdentityServerHost.Pages.Account.Login.Index
<div class="login-page">
<div class="lead">
@ -86,4 +86,4 @@
</div>
}
</div>
</div>
</div>

View file

@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
[SecurityHeaders]
[AllowAnonymous]

View file

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public class InputModel
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public static class LoginOptions
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public class ViewModel
{

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Logout.Index
@model IdentityServerHost.Pages.Account.Logout.Index
<div class="logout-page">
<div class="lead">
@ -14,4 +14,4 @@
<button class="btn btn-primary">Yes</button>
</div>
</form>
</div>
</div>

View file

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
[SecurityHeaders]
[AllowAnonymous]

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Logout.LoggedOut
@model IdentityServerHost.Pages.Account.Logout.LoggedOut
<div class="logged-out-page">
<h1>
@ -27,4 +27,4 @@
{
<script src="~/js/signout-redirect.js"></script>
}
}
}

View file

@ -2,7 +2,7 @@ using Duende.IdentityServer.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
[SecurityHeaders]
[AllowAnonymous]

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
public class LoggedOutViewModel
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
public static class LogoutOptions
{

View file

@ -1,6 +1,6 @@
using System.Buffers.Text;
using System.Text;
using System.Text.Json;
using Duende.IdentityModel;
using Microsoft.AspNetCore.Authentication;
namespace IdentityServerHost.Pages.Diagnostics;
@ -11,11 +11,11 @@ public class ViewModel
{
AuthenticateResult = result;
if (result?.Properties?.Items.TryGetValue("client_list", out var encoded) == true)
if (result.Properties?.Items.TryGetValue("client_list", out var encoded) == true)
{
if (encoded != null)
{
var bytes = Base64Url.Decode(encoded);
var bytes = Base64Url.DecodeFromChars(encoded);
var value = Encoding.UTF8.GetString(bytes);
Clients = JsonSerializer.Deserialize<string[]>(value) ?? Enumerable.Empty<string>();
return;
@ -25,5 +25,6 @@ public class ViewModel
}
public AuthenticateResult AuthenticateResult { get; }
public IEnumerable<string> Clients { get; }
}

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Home.Index
@model IdentityServerHost.Pages.Index
<div class="welcome-page">
<h1>

View file

@ -3,7 +3,7 @@ using Duende.IdentityServer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Home;
namespace IdentityServerHost.Pages;
[AllowAnonymous]
public class Index : PageModel

View file

@ -4,7 +4,7 @@ using Duende.IdentityModel;
using Duende.IdentityServer;
using Duende.IdentityServer.Test;
namespace IdentityServerHost;
namespace IdentityServerHost.Pages;
public static class TestUsers
{

View file

@ -1,2 +1 @@
@using IdentityServerHost.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View file

@ -1,7 +1,7 @@
using System.Globalization;
using System.Text;
using Duende.IdentityServer.Licensing;
using IdentityServerEntityFramework;
using IdentityServerHost;
using Serilog;
Log.Logger = new LoggerConfiguration()

View file

@ -4,7 +4,7 @@ using Duende.IdentityServer.Models;
using Microsoft.EntityFrameworkCore;
using Serilog;
namespace IdentityServerEntityFramework;
namespace IdentityServerHost;
public class SeedData
{

View file

@ -1,6 +1,6 @@
using Duende.IdentityServer.Models;
namespace IdentityServerInMem;
namespace IdentityServerHost;
public static class Config
{

View file

@ -1,11 +1,11 @@
using System.Globalization;
using Duende.IdentityServer;
using IdentityServerHost;
using IdentityServerHost.Pages;
using Microsoft.IdentityModel.Tokens;
using Serilog;
using Serilog.Filters;
namespace IdentityServerInMem;
namespace IdentityServerHost;
internal static class HostingExtensions
{

View file

@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>IdentityServerHost</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Duende.IdentityServer" version="7.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Duende.IdentityServer" version="7.4.3" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
</ItemGroup>
</Project>

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Create.Index
@model IdentityServerHost.Pages.Account.Create.Index
<div class="login-page">
<div class="lead">
@ -37,4 +37,4 @@
</div>
</div>
</div>
</div>

View file

@ -6,27 +6,21 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Create;
namespace IdentityServerHost.Pages.Account.Create;
[SecurityHeaders]
[AllowAnonymous]
public class Index : PageModel
public class Index(
IIdentityServerInteractionService interaction,
TestUserStore? users = null)
: PageModel
{
private readonly TestUserStore _users;
private readonly IIdentityServerInteractionService _interaction;
private readonly TestUserStore _users = users ?? throw new InvalidOperationException("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController.");
[BindProperty]
public InputModel Input { get; set; } = default!;
public Index(
IIdentityServerInteractionService interaction,
TestUserStore? users = null)
{
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
_users = users ?? throw new InvalidOperationException("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController.");
_interaction = interaction;
}
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
public IActionResult OnGet(string? returnUrl)
{
@ -37,7 +31,7 @@ public class Index : PageModel
public async Task<IActionResult> OnPost()
{
// check if we are in the context of an authorization request
var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
var context = await interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
// the user clicked the "cancel" button
if (Input.Button != "create")
@ -47,7 +41,7 @@ public class Index : PageModel
// if the user cancels, send a result back into IdentityServer as if they
// denied the consent (even if this client does not require consent).
// this will send back an access denied OIDC error response to the client.
await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
await interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
if (context.IsNativeClient())

View file

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace IdentityServerHost.Pages.Create;
namespace IdentityServerHost.Pages.Account.Create;
public class InputModel
{

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Login.Index
@model IdentityServerHost.Pages.Account.Login.Index
<div class="login-page">
<div class="lead">
@ -86,4 +86,4 @@
</div>
}
</div>
</div>
</div>

View file

@ -9,38 +9,26 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
[SecurityHeaders]
[AllowAnonymous]
public class Index : PageModel
public class Index(
IIdentityServerInteractionService interaction,
IAuthenticationSchemeProvider schemeProvider,
IIdentityProviderStore identityProviderStore,
IEventService events,
TestUserStore? users = null)
: PageModel
{
private readonly TestUserStore _users;
private readonly IIdentityServerInteractionService _interaction;
private readonly IEventService _events;
private readonly IAuthenticationSchemeProvider _schemeProvider;
private readonly IIdentityProviderStore _identityProviderStore;
private readonly TestUserStore _users = users ?? throw new InvalidOperationException("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController.");
public ViewModel View { get; set; } = default!;
[BindProperty]
public InputModel Input { get; set; } = default!;
public Index(
IIdentityServerInteractionService interaction,
IAuthenticationSchemeProvider schemeProvider,
IIdentityProviderStore identityProviderStore,
IEventService events,
TestUserStore? users = null)
{
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
_users = users ?? throw new InvalidOperationException("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController.");
_interaction = interaction;
_schemeProvider = schemeProvider;
_identityProviderStore = identityProviderStore;
_events = events;
}
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
public async Task<IActionResult> OnGet(string? returnUrl)
{
@ -58,7 +46,7 @@ public class Index : PageModel
public async Task<IActionResult> OnPost()
{
// check if we are in the context of an authorization request
var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
var context = await interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
// the user clicked the "cancel" button
if (Input.Button != "login")
@ -71,7 +59,7 @@ public class Index : PageModel
// if the user cancels, send a result back into IdentityServer as if they
// denied the consent (even if this client does not require consent).
// this will send back an access denied OIDC error response to the client.
await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
await interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
if (context.IsNativeClient())
@ -96,7 +84,7 @@ public class Index : PageModel
if (_users.ValidateCredentials(Input.Username, Input.Password))
{
var user = _users.FindByUsername(Input.Username);
await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId));
await events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId));
Telemetry.Metrics.UserLogin(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider);
// only set explicit expiration here if user chooses "remember me".
@ -149,7 +137,7 @@ public class Index : PageModel
}
const string error = "invalid credentials";
await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, error, clientId: context?.Client.ClientId));
await events.RaiseAsync(new UserLoginFailureEvent(Input.Username, error, clientId: context?.Client.ClientId));
Telemetry.Metrics.UserLoginFailure(context?.Client.ClientId, IdentityServerConstants.LocalIdentityProvider, error);
ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage);
}
@ -166,10 +154,10 @@ public class Index : PageModel
ReturnUrl = returnUrl
};
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
var context = await interaction.GetAuthorizationContextAsync(returnUrl);
if (context?.IdP != null)
{
var scheme = await _schemeProvider.GetSchemeAsync(context.IdP);
var scheme = await schemeProvider.GetSchemeAsync(context.IdP);
if (scheme != null)
{
var local = context.IdP == Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider;
@ -191,7 +179,7 @@ public class Index : PageModel
return;
}
var schemes = await _schemeProvider.GetAllSchemesAsync();
var schemes = await schemeProvider.GetAllSchemesAsync();
var providers = schemes
.Where(x => x.DisplayName != null)
@ -201,7 +189,7 @@ public class Index : PageModel
displayName: x.DisplayName ?? x.Name
)).ToList();
var dynamicSchemes = (await _identityProviderStore.GetAllSchemeNamesAsync())
var dynamicSchemes = (await identityProviderStore.GetAllSchemeNamesAsync())
.Where(x => x.Enabled)
.Select(x => new ViewModel.ExternalProvider
(

View file

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public class InputModel
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public static class LoginOptions
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Login;
namespace IdentityServerHost.Pages.Account.Login;
public class ViewModel
{
@ -11,15 +11,9 @@ public class ViewModel
public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1;
public string? ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null;
public class ExternalProvider
public class ExternalProvider(string authenticationScheme, string? displayName = null)
{
public ExternalProvider(string authenticationScheme, string? displayName = null)
{
AuthenticationScheme = authenticationScheme;
DisplayName = displayName;
}
public string? DisplayName { get; set; }
public string AuthenticationScheme { get; set; }
public string? DisplayName { get; set; } = displayName;
public string AuthenticationScheme { get; set; } = authenticationScheme;
}
}

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Logout.Index
@model IdentityServerHost.Pages.Account.Logout.Index
<div class="logout-page">
<div class="lead">
@ -14,4 +14,4 @@
<button class="btn btn-primary">Yes</button>
</div>
</form>
</div>
</div>

View file

@ -7,24 +7,16 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
[SecurityHeaders]
[AllowAnonymous]
public class Index : PageModel
public class Index(IIdentityServerInteractionService interaction, IEventService events)
: PageModel
{
private readonly IIdentityServerInteractionService _interaction;
private readonly IEventService _events;
[BindProperty]
public string? LogoutId { get; set; }
public Index(IIdentityServerInteractionService interaction, IEventService events)
{
_interaction = interaction;
_events = events;
}
public async Task<IActionResult> OnGet(string? logoutId)
{
LogoutId = logoutId;
@ -38,7 +30,7 @@ public class Index : PageModel
}
else
{
var context = await _interaction.GetLogoutContextAsync(LogoutId);
var context = await interaction.GetLogoutContextAsync(LogoutId);
if (context?.ShowSignoutPrompt == false)
{
// it's safe to automatically sign-out
@ -63,7 +55,7 @@ public class Index : PageModel
// if there's no current logout context, we need to create one
// this captures necessary info from the current logged in user
// this can still return null if there is no context needed
LogoutId ??= await _interaction.CreateLogoutContextAsync();
LogoutId ??= await interaction.CreateLogoutContextAsync();
// delete local authentication cookie
await HttpContext.SignOutAsync();
@ -72,7 +64,7 @@ public class Index : PageModel
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
await events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
Telemetry.Metrics.UserLogout(idp);
// if it's a local login we can ignore this workflow

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Logout.LoggedOut
@model IdentityServerHost.Pages.Account.Logout.LoggedOut
<div class="logged-out-page">
<h1>
@ -27,4 +27,4 @@
{
<script src="~/js/signout-redirect.js"></script>
}
}
}

View file

@ -2,22 +2,18 @@ using Duende.IdentityServer.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
[SecurityHeaders]
[AllowAnonymous]
public class LoggedOut : PageModel
public class LoggedOut(IIdentityServerInteractionService interactionService) : PageModel
{
private readonly IIdentityServerInteractionService _interactionService;
public LoggedOutViewModel View { get; set; } = default!;
public LoggedOut(IIdentityServerInteractionService interactionService) => _interactionService = interactionService;
public async Task OnGet(string? logoutId)
{
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interactionService.GetLogoutContextAsync(logoutId);
var logout = await interactionService.GetLogoutContextAsync(logoutId);
View = new LoggedOutViewModel
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
public class LoggedOutViewModel
{

View file

@ -1,4 +1,4 @@
namespace IdentityServerHost.Pages.Logout;
namespace IdentityServerHost.Pages.Account.Logout;
public static class LogoutOptions
{

View file

@ -7,13 +7,10 @@ namespace IdentityServerHost.Pages.Ciba;
[SecurityHeaders]
[Authorize]
public class AllModel : PageModel
public class AllModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService)
: PageModel
{
public IEnumerable<BackchannelUserLoginRequest> Logins { get; set; } = default!;
private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction;
public AllModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService) => _backchannelAuthenticationInteraction = backchannelAuthenticationInteractionService;
public async Task OnGet() => Logins = await _backchannelAuthenticationInteraction.GetPendingLoginRequestsForCurrentUserAsync();
public async Task OnGet() => Logins = await backchannelAuthenticationInteractionService.GetPendingLoginRequestsForCurrentUserAsync();
}

View file

@ -11,22 +11,12 @@ namespace IdentityServerHost.Pages.Ciba;
[Authorize]
[SecurityHeaders]
public class Consent : PageModel
public class Consent(
IBackchannelAuthenticationInteractionService interaction,
IEventService events,
ILogger<Consent> logger)
: PageModel
{
private readonly IBackchannelAuthenticationInteractionService _interaction;
private readonly IEventService _events;
private readonly ILogger<Consent> _logger;
public Consent(
IBackchannelAuthenticationInteractionService interaction,
IEventService events,
ILogger<Consent> logger)
{
_interaction = interaction;
_events = events;
_logger = logger;
}
public ViewModel View { get; set; } = default!;
[BindProperty]
@ -50,10 +40,10 @@ public class Consent : PageModel
public async Task<IActionResult> OnPost()
{
// validate return url is still valid
var request = await _interaction.GetLoginRequestByInternalIdAsync(Input.Id ?? throw new ArgumentNullException(nameof(Input.Id)));
var request = await interaction.GetLoginRequestByInternalIdAsync(Input.Id ?? throw new ArgumentNullException(nameof(Input.Id)));
if (request == null || request.Subject.GetSubjectId() != User.GetSubjectId())
{
_logger.InvalidId(Input.Id);
logger.InvalidId(Input.Id);
return RedirectToPage("/Home/Error/Index");
}
@ -65,7 +55,7 @@ public class Consent : PageModel
result = new CompleteBackchannelLoginRequest(Input.Id);
// emit event
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
await events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName));
}
// user clicked 'yes' - validate the data
@ -87,7 +77,7 @@ public class Consent : PageModel
};
// emit event
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false));
await events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false));
Telemetry.Metrics.ConsentGranted(request.Client.ClientId, result.ScopesValuesConsented, false);
var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented);
Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied);
@ -105,7 +95,7 @@ public class Consent : PageModel
if (result != null)
{
// communicate outcome of consent back to identityserver
await _interaction.CompleteLoginRequestAsync(result);
await interaction.CompleteLoginRequestAsync(result);
return RedirectToPage("/Ciba/All");
}
@ -122,7 +112,7 @@ public class Consent : PageModel
{
ArgumentNullException.ThrowIfNull(id);
var request = await _interaction.GetLoginRequestByInternalIdAsync(id);
var request = await interaction.GetLoginRequestByInternalIdAsync(id);
if (request != null && request.Subject.GetSubjectId() == User.GetSubjectId())
{
View = CreateConsentViewModel(request);
@ -130,7 +120,7 @@ public class Consent : PageModel
}
else
{
_logger.NoMatchingBackchannelLoginRequest(id);
logger.NoMatchingBackchannelLoginRequest(id);
return false;
}
}

View file

@ -8,25 +8,19 @@ namespace IdentityServerHost.Pages.Ciba;
[AllowAnonymous]
[SecurityHeaders]
public class IndexModel : PageModel
public class IndexModel(
IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService,
ILogger<IndexModel> logger)
: PageModel
{
public BackchannelUserLoginRequest LoginRequest { get; set; } = default!;
private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction;
private readonly ILogger<IndexModel> _logger;
public IndexModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService, ILogger<IndexModel> logger)
{
_backchannelAuthenticationInteraction = backchannelAuthenticationInteractionService;
_logger = logger;
}
public async Task<IActionResult> OnGet(string id)
{
var result = await _backchannelAuthenticationInteraction.GetLoginRequestByInternalIdAsync(id);
var result = await backchannelAuthenticationInteractionService.GetLoginRequestByInternalIdAsync(id);
if (result == null)
{
_logger.InvalidBackchannelLoginId(id);
logger.InvalidBackchannelLoginId(id);
return RedirectToPage("/Home/Error/Index");
}
else

View file

@ -12,22 +12,12 @@ namespace IdentityServerHost.Pages.Consent;
[Authorize]
[SecurityHeaders]
public class Index : PageModel
public class Index(
IIdentityServerInteractionService interaction,
IEventService events,
ILogger<Index> logger)
: PageModel
{
private readonly IIdentityServerInteractionService _interaction;
private readonly IEventService _events;
private readonly ILogger<Index> _logger;
public Index(
IIdentityServerInteractionService interaction,
IEventService events,
ILogger<Index> logger)
{
_interaction = interaction;
_events = events;
_logger = logger;
}
public ViewModel View { get; set; } = default!;
[BindProperty]
@ -51,7 +41,7 @@ public class Index : PageModel
public async Task<IActionResult> OnPost()
{
// validate return url is still valid
var request = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
var request = await interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
if (request == null)
{
return RedirectToPage("/Home/Error/Index");
@ -65,7 +55,7 @@ public class Index : PageModel
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied };
// emit event
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
await events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName));
}
// user clicked 'yes' - validate the data
@ -88,7 +78,7 @@ public class Index : PageModel
};
// emit event
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent));
await events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent));
Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent);
var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented);
Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied);
@ -108,7 +98,7 @@ public class Index : PageModel
ArgumentNullException.ThrowIfNull(Input.ReturnUrl, nameof(Input.ReturnUrl));
// communicate outcome of consent back to identityserver
await _interaction.GrantConsentAsync(request, grantedConsent);
await interaction.GrantConsentAsync(request, grantedConsent);
// redirect back to authorization endpoint
if (request.IsNativeClient() == true)
@ -133,7 +123,7 @@ public class Index : PageModel
{
ArgumentNullException.ThrowIfNull(returnUrl);
var request = await _interaction.GetAuthorizationContextAsync(returnUrl);
var request = await interaction.GetAuthorizationContextAsync(returnUrl);
if (request != null)
{
View = CreateConsentViewModel(request);
@ -141,7 +131,7 @@ public class Index : PageModel
}
else
{
_logger.NoConsentMatchingRequest(returnUrl);
logger.NoConsentMatchingRequest(returnUrl);
return false;
}
}

View file

@ -14,21 +14,13 @@ namespace IdentityServerHost.Pages.Device;
[SecurityHeaders]
[Authorize]
public class Index : PageModel
public class Index(
IDeviceFlowInteractionService interaction,
IEventService eventService,
IOptions<IdentityServerOptions> options)
: PageModel
{
private readonly IDeviceFlowInteractionService _interaction;
private readonly IEventService _events;
private readonly IOptions<IdentityServerOptions> _options;
public Index(
IDeviceFlowInteractionService interaction,
IEventService eventService,
IOptions<IdentityServerOptions> options)
{
_interaction = interaction;
_events = eventService;
_options = options;
}
private readonly IOptions<IdentityServerOptions> _options = options;
public ViewModel View { get; set; } = default!;
@ -58,7 +50,7 @@ public class Index : PageModel
public async Task<IActionResult> OnPost()
{
var request = await _interaction.GetAuthorizationContextAsync(Input.UserCode ?? throw new ArgumentNullException(nameof(Input.UserCode)));
var request = await interaction.GetAuthorizationContextAsync(Input.UserCode ?? throw new ArgumentNullException(nameof(Input.UserCode)));
if (request == null)
{
return RedirectToPage("/Home/Error/Index");
@ -75,7 +67,7 @@ public class Index : PageModel
};
// emit event
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
await eventService.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName));
}
// user clicked 'yes' - validate the data
@ -98,7 +90,7 @@ public class Index : PageModel
};
// emit event
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent));
await eventService.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent));
Telemetry.Metrics.ConsentGranted(request.Client.ClientId, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent);
var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(grantedConsent.ScopesValuesConsented);
Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied);
@ -116,7 +108,7 @@ public class Index : PageModel
if (grantedConsent != null)
{
// communicate outcome of consent back to identityserver
await _interaction.HandleRequestAsync(Input.UserCode, grantedConsent);
await interaction.HandleRequestAsync(Input.UserCode, grantedConsent);
// indicate that's it ok to redirect back to authorization endpoint
return RedirectToPage("/Device/Success");
@ -133,7 +125,7 @@ public class Index : PageModel
private async Task<bool> SetViewModelAsync(string userCode)
{
var request = await _interaction.GetAuthorizationContextAsync(userCode);
var request = await interaction.GetAuthorizationContextAsync(userCode);
if (request != null)
{
View = CreateConsentViewModel(request);

View file

@ -1,6 +1,6 @@
using System.Buffers.Text;
using System.Text;
using System.Text.Json;
using Duende.IdentityModel;
using Microsoft.AspNetCore.Authentication;
namespace IdentityServerHost.Pages.Diagnostics;
@ -15,7 +15,7 @@ public class ViewModel
{
if (encoded != null)
{
var bytes = Base64Url.Decode(encoded);
var bytes = Base64Url.DecodeFromChars(encoded);
var value = Encoding.UTF8.GetString(bytes);
Clients = JsonSerializer.Deserialize<string[]>(value) ?? Enumerable.Empty<string>();
return;

View file

@ -6,28 +6,20 @@ namespace IdentityServerHost.Pages.Error;
[AllowAnonymous]
[SecurityHeaders]
public class Index : PageModel
public class Index(IIdentityServerInteractionService interaction, IWebHostEnvironment environment)
: PageModel
{
private readonly IIdentityServerInteractionService _interaction;
private readonly IWebHostEnvironment _environment;
public ViewModel View { get; set; } = new();
public Index(IIdentityServerInteractionService interaction, IWebHostEnvironment environment)
{
_interaction = interaction;
_environment = environment;
}
public async Task OnGet(string? errorId)
{
// retrieve error details from identityserver
var message = await _interaction.GetErrorContextAsync(errorId);
// retrieve error details from IdentityServer
var message = await interaction.GetErrorContextAsync(errorId);
if (message != null)
{
View.Error = message;
if (!_environment.IsDevelopment())
if (!environment.IsDevelopment())
{
// only show in development
message.ErrorDescription = null;

View file

@ -13,26 +13,16 @@ namespace IdentityServerHost.Pages.ExternalLogin;
[AllowAnonymous]
[SecurityHeaders]
public class Callback : PageModel
public class Callback(
IIdentityServerInteractionService interaction,
IEventService events,
ILogger<Callback> logger,
TestUserStore? users = null)
: PageModel
{
private readonly TestUserStore _users;
private readonly IIdentityServerInteractionService _interaction;
private readonly ILogger<Callback> _logger;
private readonly IEventService _events;
private readonly TestUserStore _users = users ?? throw new InvalidOperationException("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController.");
public Callback(
IIdentityServerInteractionService interaction,
IEventService events,
ILogger<Callback> logger,
TestUserStore? users = null)
{
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
_users = users ?? throw new InvalidOperationException("Please call 'AddTestUsers(TestUsers.Users)' on the IIdentityServerBuilder in Startup or remove the TestUserStore from the AccountController.");
_interaction = interaction;
_logger = logger;
_events = events;
}
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
public async Task<IActionResult> OnGet()
{
@ -46,10 +36,10 @@ public class Callback : PageModel
var externalUser = result.Principal ??
throw new InvalidOperationException("External authentication produced a null Principal");
if (_logger.IsEnabled(LogLevel.Debug))
if (logger.IsEnabled(LogLevel.Debug))
{
var externalClaims = externalUser.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.ExternalClaims(externalClaims);
logger.ExternalClaims(externalClaims);
}
// lookup our user and external provider info
@ -101,8 +91,8 @@ public class Callback : PageModel
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// check if external login is in the context of an OIDC request
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId));
var context = await interaction.GetAuthorizationContextAsync(returnUrl);
await events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId));
Telemetry.Metrics.UserLogin(context?.Client.ClientId, provider!);
if (context != null)

View file

@ -8,12 +8,8 @@ namespace IdentityServerHost.Pages.ExternalLogin;
[AllowAnonymous]
[SecurityHeaders]
public class Challenge : PageModel
public class Challenge(IIdentityServerInteractionService interactionService) : PageModel
{
private readonly IIdentityServerInteractionService _interactionService;
public Challenge(IIdentityServerInteractionService interactionService) => _interactionService = interactionService;
public IActionResult OnGet(string scheme, string? returnUrl)
{
if (string.IsNullOrEmpty(returnUrl))
@ -22,7 +18,7 @@ public class Challenge : PageModel
}
// Abort on incorrect returnUrl - it is neither a local url nor a valid OIDC url.
if (Url.IsLocalUrl(returnUrl) == false && _interactionService.IsValidReturnUrl(returnUrl) == false)
if (Url.IsLocalUrl(returnUrl) == false && interactionService.IsValidReturnUrl(returnUrl) == false)
{
// user might have clicked on a malicious link - should be logged
throw new ArgumentException("invalid return URL");

View file

@ -10,37 +10,26 @@ namespace IdentityServerHost.Pages.Grants;
[SecurityHeaders]
[Authorize]
public class Index : PageModel
public class Index(
IIdentityServerInteractionService interaction,
IClientStore clients,
IResourceStore resources,
IEventService events)
: PageModel
{
private readonly IIdentityServerInteractionService _interaction;
private readonly IClientStore _clients;
private readonly IResourceStore _resources;
private readonly IEventService _events;
public Index(IIdentityServerInteractionService interaction,
IClientStore clients,
IResourceStore resources,
IEventService events)
{
_interaction = interaction;
_clients = clients;
_resources = resources;
_events = events;
}
public ViewModel View { get; set; } = default!;
public async Task OnGet()
{
var grants = await _interaction.GetAllUserGrantsAsync();
var grants = await interaction.GetAllUserGrantsAsync();
var list = new List<GrantViewModel>();
foreach (var grant in grants)
{
var client = await _clients.FindClientByIdAsync(grant.ClientId);
var client = await clients.FindClientByIdAsync(grant.ClientId);
if (client != null)
{
var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes);
var resources1 = await resources.FindResourcesByScopeAsync(grant.Scopes);
var item = new GrantViewModel()
{
@ -51,8 +40,8 @@ public class Index : PageModel
Description = grant.Description,
Created = grant.CreationTime,
Expires = grant.Expiration,
IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(),
ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray()
IdentityGrantNames = resources1.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(),
ApiGrantNames = resources1.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray()
};
list.Add(item);
@ -70,8 +59,8 @@ public class Index : PageModel
public async Task<IActionResult> OnPost()
{
await _interaction.RevokeUserConsentAsync(ClientId);
await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId));
await interaction.RevokeUserConsentAsync(ClientId);
await events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId));
Telemetry.Metrics.GrantsRevoked(ClientId);
return RedirectToPage("/Grants/Index");

View file

@ -1,5 +1,5 @@
@page
@model IdentityServerHost.Pages.Home.Index
@model IdentityServerHost.Pages.Index
<div class="welcome-page">
<h1>

View file

@ -3,16 +3,14 @@ using Duende.IdentityServer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.Home;
namespace IdentityServerHost.Pages;
[AllowAnonymous]
public class Index : PageModel
public class Index(IdentityServerLicense? license = null) : PageModel
{
public Index(IdentityServerLicense? license = null) => License = license;
public string Version => typeof(Duende.IdentityServer.Hosting.IdentityServerMiddleware).Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion.Split('+').First()
?? "unavailable";
public IdentityServerLicense? License { get; }
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion.Split('+').First()
?? "unavailable";
public IdentityServerLicense? License { get; } = license;
}

View file

@ -6,12 +6,8 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
namespace IdentityServerHost.Pages.ServerSideSessions;
public class IndexModel : PageModel
public class IndexModel(ISessionManagementService? sessionManagementService = null) : PageModel
{
private readonly ISessionManagementService? _sessionManagementService;
public IndexModel(ISessionManagementService? sessionManagementService = null) => _sessionManagementService = sessionManagementService;
public QueryResult<UserSession>? UserSessions { get; set; }
[BindProperty(SupportsGet = true)]
@ -37,9 +33,9 @@ public class IndexModel : PageModel
return NotFound();
}
if (_sessionManagementService != null)
if (sessionManagementService != null)
{
UserSessions = await _sessionManagementService.QuerySessionsAsync(new SessionQuery
UserSessions = await sessionManagementService.QuerySessionsAsync(new SessionQuery
{
ResultsToken = Token,
RequestPriorResults = Prev == "true",
@ -63,9 +59,9 @@ public class IndexModel : PageModel
return NotFound();
}
ArgumentNullException.ThrowIfNull(_sessionManagementService);
ArgumentNullException.ThrowIfNull(sessionManagementService);
await _sessionManagementService.RemoveSessionsAsync(new RemoveSessionsContext
await sessionManagementService.RemoveSessionsAsync(new RemoveSessionsContext
{
SessionId = SessionId,
});

View file

@ -4,7 +4,7 @@ using Duende.IdentityModel;
using Duende.IdentityServer;
using Duende.IdentityServer.Test;
namespace IdentityServerHost;
namespace IdentityServerHost.Pages;
public static class TestUsers
{

View file

@ -1,2 +1 @@
@using IdentityServerHost.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View file

@ -1,7 +1,7 @@
using System.Globalization;
using System.Text;
using Duende.IdentityServer.Licensing;
using IdentityServerInMem;
using IdentityServerHost;
using Serilog;
Log.Logger = new LoggerConfiguration()

View file

@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Duende.AccessTokenManagement.OpenIdConnect" Version="3.2.0" />
<PackageReference Include="Duende.IdentityModel" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.6" />
<PackageReference Include="Duende.AccessTokenManagement.OpenIdConnect" Version="4.1.0" />
<PackageReference Include="Duende.IdentityModel" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.1" />
</ItemGroup>
</Project>

View file

@ -82,10 +82,10 @@ void CopyFile(DirectoryInfo directoryInfo, FileInfo fileInfo)
bool TryFindFile(string fileName, [NotNullWhen(true)] out FileInfo? found)
{
var executingAssemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
?? throw new InvalidOperationException("Failed to find executing assembly location");
var currentDir = new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
?? throw new InvalidOperationException("Failed to find directory for current assembly"));
var currentDir = new DirectoryInfo(executingAssemblyLocation);
while (currentDir != null && currentDir.Exists)
{

Some files were not shown because too many files have changed in this diff Show more