mirror of
https://github.com/DuendeSoftware/products
synced 2026-05-24 01:18:22 +00:00
Base SAML test client
This commit is contained in:
parent
ca9e3a360f
commit
481eb372e6
18 changed files with 282 additions and 3 deletions
|
|
@ -47,6 +47,7 @@
|
|||
<ProjectReference Include="..\..\..\clients\src\MvcHybridBackChannel\MvcHybridBackChannel.csproj" />
|
||||
<ProjectReference Include="..\..\..\clients\src\MvcJarJwt\MvcJarJwt.csproj" />
|
||||
<ProjectReference Include="..\..\..\clients\src\MvcJarUriJwt\MvcJarUriJwt.csproj" />
|
||||
<ProjectReference Include="..\..\..\clients\src\MvcSaml\MvcSaml.csproj" />
|
||||
<ProjectReference Include="..\..\..\clients\src\Web\Web.csproj" />
|
||||
<ProjectReference Include="..\..\..\clients\src\WindowsConsoleSystemBrowser\WindowsConsoleSystemBrowser.csproj" />
|
||||
<ProjectReference Include="..\..\..\hosts\AspNetIdentity10\Host.AspNetIdentity10.csproj" />
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ void ConfigureWebClients()
|
|||
RegisterClientIfEnabled<Projects.MvcHybridBackChannel>("mvc-hybrid-backchannel");
|
||||
RegisterClientIfEnabled<Projects.MvcJarJwt>("mvc-jar-jwt");
|
||||
RegisterClientIfEnabled<Projects.MvcJarUriJwt>("mvc-jar-uri-jwt");
|
||||
RegisterClientIfEnabled<Projects.MvcSaml>("mvc-saml");
|
||||
RegisterClientIfEnabled<Projects.Web>("web");
|
||||
RegisterTemplateIfEnabled<Projects.IdentityServerTemplate>("template-is", 7001);
|
||||
RegisterTemplateIfEnabled<Projects.IdentityServerEmpty>("template-is-empty", 7002);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
"MvcHybridBackChannel": true,
|
||||
"MvcJarJwt": true,
|
||||
"MvcJarUriJwt": true,
|
||||
"MvcSaml": true,
|
||||
"Web": true,
|
||||
"WindowsConsoleSystemBrowser": false
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sustainsys.Saml2.AspNetCore2;
|
||||
|
||||
namespace MvcSaml.Controllers;
|
||||
|
||||
public class HomeController : Controller
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public IActionResult Index() => View();
|
||||
|
||||
public IActionResult Secure() => View();
|
||||
|
||||
public IActionResult Logout() => SignOut(
|
||||
new AuthenticationProperties { RedirectUri = "/" },
|
||||
Saml2Defaults.Scheme,
|
||||
CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
75
identity-server/clients/src/MvcSaml/HostingExtensions.cs
Normal file
75
identity-server/clients/src/MvcSaml/HostingExtensions.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Sustainsys.Saml2;
|
||||
using Sustainsys.Saml2.AspNetCore2;
|
||||
using Sustainsys.Saml2.Configuration;
|
||||
using Sustainsys.Saml2.Metadata;
|
||||
|
||||
namespace MvcSaml;
|
||||
|
||||
internal static class HostingExtensions
|
||||
{
|
||||
public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
// The IdentityServer base URL is injected by Aspire at runtime via the "is-host" environment variable.
|
||||
var idpBaseUrl = builder.Configuration["is-host"]
|
||||
?? throw new InvalidOperationException("is-host configuration is required");
|
||||
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = Saml2Defaults.Scheme;
|
||||
})
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.Cookie.Name = "mvcsaml";
|
||||
})
|
||||
.AddSaml2(options =>
|
||||
{
|
||||
// SP entity ID — must match the EntityId registered in the IdP's SamlServiceProviders config.
|
||||
// By convention, Sustainsys uses <base-url>/Saml2 as the entity ID.
|
||||
options.SPOptions.EntityId = new EntityId("https://localhost:44350/Saml2");
|
||||
|
||||
// Best practice: require the IdP to sign assertions.
|
||||
options.SPOptions.WantAssertionsSigned = true;
|
||||
|
||||
// Best practice: the SP does not sign AuthnRequests for this sample (no SP cert needed).
|
||||
// Set to Always and add a ServiceCertificate to enable signed AuthnRequests.
|
||||
options.SPOptions.AuthenticateRequestSigningBehavior = SigningBehavior.Never;
|
||||
|
||||
// Load the IdP configuration from the metadata endpoint published by IdentityServer.
|
||||
// This automatically picks up signing certificates, endpoints, and capabilities.
|
||||
options.IdentityProviders.Add(
|
||||
new IdentityProvider(new EntityId(idpBaseUrl), options.SPOptions)
|
||||
{
|
||||
MetadataLocation = $"{idpBaseUrl}/saml/metadata",
|
||||
LoadMetadata = true
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public static WebApplication ConfigurePipeline(this WebApplication app)
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapDefaultControllerRoute()
|
||||
.RequireAuthorization();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
23
identity-server/clients/src/MvcSaml/MvcSaml.csproj
Normal file
23
identity-server/clients/src/MvcSaml/MvcSaml.csproj
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<UserSecretsId>3a8b2c1d-4e5f-6a7b-8c9d-0e1f2a3b4c5d</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Sustainsys.Saml2.AspNetCore2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
|
||||
<PackageReference Include="OpenTelemetry" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" />
|
||||
|
||||
<ProjectReference Include="..\..\..\aspire\ServiceDefaults\ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\Constants\Constants.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
36
identity-server/clients/src/MvcSaml/Program.cs
Normal file
36
identity-server/clients/src/MvcSaml/Program.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using MvcSaml;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("MvcSaml", LogEventLevel.Debug)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}")
|
||||
.CreateLogger();
|
||||
|
||||
try
|
||||
{
|
||||
var builder = WebApplication
|
||||
.CreateBuilder(args);
|
||||
|
||||
builder
|
||||
.AddServiceDefaults();
|
||||
|
||||
builder
|
||||
.ConfigureServices()
|
||||
.ConfigurePipeline()
|
||||
.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, messageTemplate: "Unhandled exception");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.Information(messageTemplate: "Shut down complete");
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
12
identity-server/clients/src/MvcSaml/Properties/launchSettings.json
vendored
Normal file
12
identity-server/clients/src/MvcSaml/Properties/launchSettings.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"profiles": {
|
||||
"Host": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:44350",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
@{
|
||||
ViewData["Title"] = "Home";
|
||||
}
|
||||
|
||||
<h1>MvcSaml Sample</h1>
|
||||
<p>This sample demonstrates SAML 2.0 single sign-on via Duende IdentityServer using the <a href="https://github.com/Sustainsys/Saml2" target="_blank">Sustainsys.Saml2</a> library.</p>
|
||||
<p>
|
||||
<a class="btn" asp-controller="Home" asp-action="Secure">Sign in</a>
|
||||
</p>
|
||||
28
identity-server/clients/src/MvcSaml/Views/Home/Secure.cshtml
Normal file
28
identity-server/clients/src/MvcSaml/Views/Home/Secure.cshtml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
@{
|
||||
ViewData["Title"] = "Secure";
|
||||
}
|
||||
|
||||
<h1>Authenticated User</h1>
|
||||
<p>You are signed in via SAML 2.0. Your claims:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var claim in User.Claims)
|
||||
{
|
||||
<tr>
|
||||
<td>@claim.Type</td>
|
||||
<td>@claim.Value</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p style="margin-top:1rem">
|
||||
<a class="btn" asp-controller="Home" asp-action="Logout">Logout</a>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - MvcSaml</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin: 0; }
|
||||
nav { background: #1a1a2e; padding: 0.75rem 1.5rem; display: flex; align-items: center; gap: 1.5rem; }
|
||||
nav a { color: #eee; text-decoration: none; font-size: 0.95rem; }
|
||||
nav a:hover { color: #fff; text-decoration: underline; }
|
||||
nav .brand { font-weight: 600; font-size: 1.1rem; color: #fff; }
|
||||
main { padding: 2rem 1.5rem; max-width: 900px; margin: 0 auto; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #ddd; padding: 0.5rem 0.75rem; text-align: left; font-size: 0.875rem; }
|
||||
th { background: #f5f5f5; font-weight: 600; }
|
||||
tr:nth-child(even) td { background: #fafafa; }
|
||||
.btn { display: inline-block; padding: 0.4rem 1rem; background: #1a1a2e; color: #fff; border-radius: 4px; text-decoration: none; font-size: 0.875rem; }
|
||||
.btn:hover { background: #2d2d50; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a class="brand" asp-controller="Home" asp-action="Index">MvcSaml</a>
|
||||
<a asp-controller="Home" asp-action="Index">Home</a>
|
||||
<a asp-controller="Home" asp-action="Secure">Secure</a>
|
||||
@if (Context.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
<a asp-controller="Home" asp-action="Logout">Logout</a>
|
||||
}
|
||||
</nav>
|
||||
<main>
|
||||
@RenderBody()
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@using MvcSaml
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Duende.IdentityModel;
|
||||
using Duende.IdentityServer;
|
||||
using Duende.IdentityServer.Configuration;
|
||||
using Duende.IdentityServer.Configuration.RequestProcessing;
|
||||
using Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
using Duende.IdentityServer.Hosts.Shared.Customization;
|
||||
using Microsoft.AspNetCore.Authentication.Certificate;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
|
@ -78,7 +78,9 @@ internal static class IdentityServerExtensions
|
|||
Scope = "openid profile"
|
||||
}
|
||||
])
|
||||
.AddLicenseSummary();
|
||||
.AddLicenseSummary()
|
||||
.AddSaml()
|
||||
.AddInMemorySamlServiceProviders(SamlServiceProviders.Get());
|
||||
|
||||
builder.Services.AddIdentityServerConfiguration(opt => { })
|
||||
.AddInMemoryClientConfigurationStore();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Models;
|
||||
|
||||
namespace Duende.IdentityServer.Hosts.Shared.Configuration;
|
||||
|
||||
public static class SamlServiceProviders
|
||||
{
|
||||
public static IEnumerable<SamlServiceProvider> Get() =>
|
||||
[
|
||||
new SamlServiceProvider
|
||||
{
|
||||
EntityId = "https://localhost:44350/Saml2",
|
||||
DisplayName = "MvcSaml Sample Client",
|
||||
Enabled = true,
|
||||
// ACS URL follows the Sustainsys.Saml2 convention: <base>/Saml2/Acs
|
||||
AssertionConsumerServiceUrls = [new Uri("https://localhost:44350/Saml2/Acs")],
|
||||
// Sign the assertion (not the response envelope) — the Sustainsys default expectation
|
||||
SigningBehavior = SamlSigningBehavior.SignAssertion,
|
||||
// No RequireSignedAuthnRequests — keeps the sample self-contained without distributing an SP cert
|
||||
// No EncryptAssertions — plain HTTP is fine for local development
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ public class HostProfileService(TestUserStore users, ILogger<TestUserProfileServ
|
|||
ArgumentNullException.ThrowIfNull(context);
|
||||
await base.GetProfileDataAsync(context, ct);
|
||||
|
||||
var transaction = context.RequestedResources.ParsedScopes.FirstOrDefault(x => x.ParsedName == "transaction");
|
||||
var transaction = context.RequestedResources?.ParsedScopes.FirstOrDefault(x => x.ParsedName == "transaction");
|
||||
if (transaction?.ParsedParameter != null)
|
||||
{
|
||||
context.IssuedClaims.Add(new Claim("transaction_id", transaction.ParsedParameter));
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
"identity-server\\clients\\src\\MvcHybridBackChannel\\MvcHybridBackChannel.csproj",
|
||||
"identity-server\\clients\\src\\MvcJarJwt\\MvcJarJwt.csproj",
|
||||
"identity-server\\clients\\src\\MvcJarUriJwt\\MvcJarUriJwt.csproj",
|
||||
"identity-server\\clients\\src\\MvcSaml\\MvcSaml.csproj",
|
||||
"identity-server\\clients\\src\\Web\\Web.csproj",
|
||||
"identity-server\\clients\\src\\WindowsConsoleSystemBrowser\\WindowsConsoleSystemBrowser.csproj",
|
||||
"identity-server\\hosts\\AspNetIdentity10\\Host.AspNetIdentity10.csproj",
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@
|
|||
<Project Path="identity-server/clients/src/MvcHybridBackChannel/MvcHybridBackChannel.csproj" />
|
||||
<Project Path="identity-server/clients/src/MvcJarJwt/MvcJarJwt.csproj" />
|
||||
<Project Path="identity-server/clients/src/MvcJarUriJwt/MvcJarUriJwt.csproj" />
|
||||
<Project Path="identity-server/clients/src/MvcSaml/MvcSaml.csproj" />
|
||||
<Project Path="identity-server/clients/src/Web/Web.csproj" />
|
||||
<Project Path="identity-server/clients/src/WindowsConsoleSystemBrowser/WindowsConsoleSystemBrowser.csproj" />
|
||||
</Folder>
|
||||
|
|
|
|||
Loading…
Reference in a new issue