Skip to content
This repository was archived by the owner on Apr 10, 2025. It is now read-only.

Commit d5c60ae

Browse files
committed
Adds documentation and fleshes out examples
1 parent f8d7ab9 commit d5c60ae

File tree

16 files changed

+290
-18
lines changed

16 files changed

+290
-18
lines changed

README.md

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,194 @@
11
# Solid.Identity.Protocols.Saml2p
2-
2+
![badge](https://action-badges.now.sh/SOLIDSoftworks/Solid.Identity.Protocols.Saml2p)
3+
4+
A simple SAML2p protocol library for aspnetcore.
5+
6+
## Features
7+
- SSO
8+
- Multiple partner IDPs
9+
- Multiple parnter SPs
10+
- Global event handlers
11+
- Per partner event handlers
12+
- In-memory partner store
13+
- Optional custom partner store
14+
- Microsoft.AspNetCore.Authentication integration
15+
16+
### Upcoming features
17+
- SLO
18+
- Encrypted tokens
19+
- Signed requests
20+
- Signed responses
21+
- Federation metadata endpoint
22+
- Federation metadata import
23+
- Validate incoming authentication context
24+
- Allow accept and initiate events to manually provide a status
25+
26+
## Usage
27+
28+
### Service provider (SP)
29+
Using this package as an SP is pretty easy with the *Microsoft.AspNetCore.Authentication* integration.
30+
```csharp
31+
32+
public void ConfigureServices(IServiceCollection services)
33+
{
34+
services
35+
.AddSaml2p(options =>
36+
{
37+
// this will be the issuer of the AuthnRequest
38+
// unless overridden in the parnter IDP configuration.
39+
options.DefaultIssuer = "https://myhost/saml";
40+
41+
// The following values should come from your partner IDP.
42+
// If these values differ between environments, you can implement
43+
// IConfigureOptions<Saml2pOptions> and add as a singleton to the
44+
// service collection.
45+
options.AddIdentityProvider("https://identityproviderhost/saml", idp =>
46+
{
47+
idp.Name = "My partner identity provider";
48+
idp.BaseUrl = new Uri("https://identityproviderhost");
49+
idp.AcceptSsoEndpoint = "/saml/sso";
50+
idp.CanInitiateSso = true;
51+
// Multiple signing keys can be added for secondary
52+
// and tertiary key purposes.
53+
idp.AssertionSigningKeys.Add(new X509SecurityKey(new X509Certificate2(Convert.FromBase64String(SigningCertificateBase64))));
54+
});
55+
})
56+
;
57+
58+
services
59+
.AddAuthentication(options =>
60+
{
61+
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
62+
options.DefaultChallengeScheme = Saml2pAuthenticationDefaults.AuthenticationScheme;
63+
})
64+
.AddCookie()
65+
.AddSaml2p("https://identityproviderhost/saml")
66+
;
67+
}
68+
69+
```
70+
71+
#### Razor or MVC
72+
If you are using Razor or MVC, you can simply put an ```[Authorize]``` attribute on your PageModel/Controller/Method and this will just work.
73+
74+
#### Manual use
75+
If you're not using Razor og MVC, you can call the extension methods for *HttpContext* included in aspnetcore.
76+
77+
```csharp
78+
var result = await context.AuthenticateAsync();
79+
if(!result.Succeeded)
80+
await context.ChallengeAsync();
81+
```
82+
83+
### Identity provider (IDP)
84+
Using this package as an IDP is also pretty easy with the *Microsoft.AspNetCore.Authentication* integration.
85+
```csharp
86+
public class Startup
87+
{
88+
public void ConfigureServices(IServiceCollection services)
89+
{
90+
services
91+
.AddSaml2p(options =>
92+
{
93+
// this will be the issuer of the Response
94+
// unless overridden in the parnter SP configuration.
95+
options.DefaultIssuer = "https://myhost/saml";
96+
97+
// The following values should come from your partner SP.
98+
// If these values differ between environments, you can implement
99+
// IConfigureOptions<Saml2pOptions> and add as a singleton to the
100+
// service collection.
101+
options.AddServiceProvider("https://serviceproviderhost/saml", sp =>
102+
{
103+
sp.BaseUrl = new Uri("https://serviceproviderhost");
104+
sp.MaxClockSkew = TimeSpan.FromMinutes(2);
105+
sp.AssertionConsumerServiceEndpoint = "/finish";
106+
sp.AssertionSigningKey = new X509SecurityKey(new X509Certificate2(Convert.FromBase64String(SigningCertificateBase64)));
107+
108+
sp.SupportedBindings.Clear();
109+
sp.SupportedBindings.Add(BindingType.Post);
110+
});
111+
})
112+
;
113+
114+
// You can implement your authentication any way you like.
115+
// You can even integrate with IdentityServer4 or Duende IdentityServer.
116+
services
117+
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
118+
.AddCookie(o =>
119+
{
120+
o.LoginPath = "/login";
121+
})
122+
;
123+
}
124+
125+
public void Configure(IApplicationBuilder app)
126+
{
127+
// other middleware/endpoints added at your discretion.
128+
app.UseRouting();
129+
app.UseAuthentication();
130+
app.UseAuthorization();
131+
132+
app.UseEndpoints(endpoints =>
133+
{
134+
endpoints.MapSaml2pIdentityProvider("/saml/sso");
135+
});
136+
}
137+
}
138+
```
139+
#### Responding to RequestedAuthnContext
140+
According to the spec, if a specific authentication context is requested, it's at the chosen at the discretion of the responder. This can be done with more authentication handlers on the IDP side and
141+
142+
```csharp
143+
public void ConfigureServices(IServiceCollection services)
144+
{
145+
services.Configure<CookiePolicyOptions>(options =>
146+
{
147+
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
148+
options.CheckConsentNeeded = context => true;
149+
options.MinimumSameSitePolicy = SameSiteMode.None;
150+
});
151+
152+
services
153+
.AddSaml2p(options =>
154+
{
155+
options.DefaultIssuer = "https://myhost/saml";
156+
157+
options.IdentityProviderEvents.OnAcceptSso = (services, context) =>
158+
{
159+
if (context.Request.RequestedAuthnContext?.AuthnContextClassRef == Saml2pConstants.Classes.Kerberos)
160+
{
161+
// The SAML2p AcceptSsoEndpointMiddleware will challenge using this scheme.
162+
context.AuthenticationScheme = "FauxKerberos";
163+
}
164+
return new ValueTask();
165+
};
166+
options.AddServiceProvider("https://serviceproviderhost/saml", sp =>
167+
{
168+
sp.BaseUrl = new Uri("https://serviceproviderhost");
169+
sp.MaxClockSkew = TimeSpan.FromMinutes(2);
170+
sp.AssertionConsumerServiceEndpoint = "/finish";
171+
sp.AssertionSigningKey = new X509SecurityKey(new X509Certificate2(Convert.FromBase64String(SigningCertificateBase64)));
172+
173+
sp.SupportedBindings.Clear();
174+
sp.SupportedBindings.Add(BindingType.Post);
175+
});
176+
})
177+
;
178+
179+
services
180+
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
181+
.AddCookie(o =>
182+
{
183+
o.LoginPath = "/login";
184+
o.Cookie.Name = "Cookie.Idp";
185+
})
186+
.AddCookie("FauxKerberos", o =>
187+
{
188+
o.LoginPath = "/fauxkerberos";
189+
})
190+
;
191+
192+
//...
193+
}
194+
```

src/AspNetCore.IdpSample/AspNetCore.IdpSample.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
66
</PropertyGroup>
77

8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.4" />
10+
</ItemGroup>
11+
812
<ItemGroup>
913
<ProjectReference Include="..\Solid.Identity.Protocols.Saml2p\Solid.Identity.Protocols.Saml2p.csproj" />
1014
</ItemGroup>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@page
2+
@model AspNetCore.IdpSample.Pages.FauxKerberosModel
3+
@{
4+
ViewData["Title"] = "FauxKerberos";
5+
Layout = "~/Pages/Shared/_Layout.cshtml";
6+
}
7+
8+
<h1>FauxKerberos</h1>
9+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Security.Claims;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Authentication;
7+
using Microsoft.AspNetCore.Mvc;
8+
using Microsoft.AspNetCore.Mvc.RazorPages;
9+
using Microsoft.IdentityModel.Tokens.Saml;
10+
using Solid.Identity.Protocols.Saml2p;
11+
12+
namespace AspNetCore.IdpSample.Pages
13+
{
14+
public class FauxKerberosModel : PageModel
15+
{
16+
public async Task OnGet(string returnUrl)
17+
{
18+
var userName = "KerberosUser";
19+
var claims = new List<Claim>();
20+
claims.Add(new Claim(ClaimTypes.NameIdentifier, userName));
21+
claims.Add(new Claim(ClaimTypes.AuthenticationInstant, DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")));
22+
claims.Add(new Claim(ClaimTypes.AuthenticationMethod, Saml2pConstants.Classes.KerberosString));
23+
claims.Add(new Claim(ClaimTypes.Name, userName));
24+
25+
var identity = new ClaimsIdentity(claims, "Kerberos", ClaimTypes.NameIdentifier, ClaimTypes.Role);
26+
var subject = new ClaimsPrincipal(identity);
27+
await HttpContext.SignInAsync(subject);
28+
HttpContext.Response.Redirect(returnUrl);
29+
}
30+
}
31+
}

src/AspNetCore.IdpSample/Pages/Login.cshtml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public async Task OnPost(string returnUrl)
2828

2929
var identity = new ClaimsIdentity(claims, "Password", ClaimTypes.NameIdentifier, ClaimTypes.Role);
3030
var subject = new ClaimsPrincipal(identity);
31-
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, subject);
31+
await HttpContext.SignInAsync(subject);
3232
HttpContext.Response.Redirect(returnUrl);
3333
}
3434
}

src/AspNetCore.IdpSample/Startup.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Security.Claims;
56
using System.Security.Cryptography.X509Certificates;
67
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Authentication.Cookies;
@@ -14,6 +15,7 @@
1415
using Microsoft.Extensions.DependencyInjection;
1516
using Microsoft.Extensions.Hosting;
1617
using Microsoft.IdentityModel.Tokens;
18+
using Solid.Identity.Protocols.Saml2p;
1719
using Solid.Identity.Protocols.Saml2p.Models;
1820
using Solid.Identity.Protocols.Saml2p.Options;
1921

@@ -44,6 +46,15 @@ public void ConfigureServices(IServiceCollection services)
4446
.AddSaml2p(options =>
4547
{
4648
options.DefaultIssuer = "https://localhost:5001/saml";
49+
options.IdentityProviderEvents.OnAcceptSso = (services, context) =>
50+
{
51+
if (context.Request.RequestedAuthnContext?.AuthnContextClassRef == Saml2pConstants.Classes.Kerberos)
52+
{
53+
context.AuthenticationScheme = "FauxKerberos";
54+
context.AuthenticationPropertyItems.Add(ClaimTypes.AuthenticationMethod, Saml2pConstants.Classes.KerberosString);
55+
}
56+
return new ValueTask();
57+
};
4758
options.AddServiceProvider("https://localhost:5003/saml", sp =>
4859
{
4960
sp.BaseUrl = new Uri("https://localhost:5003");
@@ -53,8 +64,6 @@ public void ConfigureServices(IServiceCollection services)
5364

5465
sp.SupportedBindings.Clear();
5566
sp.SupportedBindings.Add(BindingType.Post);
56-
57-
sp.Enabled = false;
5867
});
5968
})
6069
;
@@ -66,6 +75,10 @@ public void ConfigureServices(IServiceCollection services)
6675
o.LoginPath = "/login";
6776
o.Cookie.Name = "Cookie.Idp";
6877
})
78+
.AddCookie("FauxKerberos", o =>
79+
{
80+
o.LoginPath = "/fauxkerberos";
81+
})
6982
;
7083

7184
services.AddMvc();

src/AspNetCore.SpSample/Startup.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
using System.Linq;
55
using System.Security.Cryptography.X509Certificates;
66
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Authentication;
78
using Microsoft.AspNetCore.Authentication.Cookies;
89
using Microsoft.AspNetCore.Builder;
10+
using Microsoft.AspNetCore.DataProtection;
911
using Microsoft.AspNetCore.Hosting;
1012
using Microsoft.AspNetCore.Http;
1113
using Microsoft.AspNetCore.HttpsPolicy;
@@ -14,6 +16,7 @@
1416
using Microsoft.Extensions.DependencyInjection;
1517
using Microsoft.Extensions.Hosting;
1618
using Microsoft.IdentityModel.Tokens;
19+
using Solid.Identity.Protocols.Saml2p;
1720
using Solid.Identity.Protocols.Saml2p.Authentication;
1821
using Solid.Identity.Protocols.Saml2p.Models.Context;
1922
using Solid.Identity.Protocols.Saml2p.Options;
@@ -47,12 +50,12 @@ public void ConfigureServices(IServiceCollection services)
4750
options.DefaultIssuer = "https://localhost:5003/saml";
4851
options.AddIdentityProvider("https://localhost:5001/saml", idp =>
4952
{
50-
idp.BaseUrl = new Uri("https://localhost:5001");
53+
idp.BaseUrl = new Uri("https://localhost:5001");
5154
idp.AcceptSsoEndpoint = "/saml/sso";
5255
idp.CanInitiateSso = true;
56+
idp.RequestedAuthnContextClassRef = Saml2pConstants.Classes.Kerberos;
5357
idp.AssertionSigningKeys.Add(new X509SecurityKey(new X509Certificate2(Convert.FromBase64String(SigningCertificateBase64))));
5458
//idp.Events.OnGeneratingRelayState = (provider, context) => new ValueTask();
55-
//idp.Events.OnStartSso += (provider, context) => new ValueTask();
5659
//idp.Events.OnValidatingToken = ValidatingToken;
5760
//idp.Events.OnValidatingToken += (provider, context) => new ValueTask();
5861
});
@@ -62,9 +65,7 @@ public void ConfigureServices(IServiceCollection services)
6265
services
6366
.AddAuthentication(options =>
6467
{
65-
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
66-
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
67-
68+
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
6869
options.DefaultChallengeScheme = Saml2pAuthenticationDefaults.AuthenticationScheme;
6970
})
7071
.AddCookie(o =>

src/Solid.Identity.Protocols.Saml2p/Abstractions/ISaml2pIdentityProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.AspNetCore.Http;
22
using Microsoft.IdentityModel.Tokens;
33
using Solid.Identity.Protocols.Saml2p.Models.Context;
4+
using Solid.Identity.Protocols.Saml2p.Models.Protocol;
45
using Solid.Identity.Protocols.Saml2p.Options;
56
using System;
67
using System.Collections.Generic;
@@ -23,6 +24,11 @@ public interface ISaml2pIdentityProvider : ISaml2pPartner<Saml2pServiceProviderE
2324
/// The requested authentication type from the IDP partner.
2425
/// </summary>
2526
Uri RequestedAuthnContextClassRef { get; }
27+
28+
/// <summary>
29+
/// The comparison the IDP partner should use when choosing an authentication type.
30+
/// </summary>
31+
Comparison RequestedAuthnContextClassRefComparison { get; }
2632

2733
/// <summary>
2834
/// The endpoint on the IDP partner service that is used when starting SSO.

src/Solid.Identity.Protocols.Saml2p/Factories/AuthnRequestFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public async Task<AuthnRequest> CreateAuthnRequestAsync(HttpContext context, ISa
5555
},
5656
RequestedAuthnContext = new RequestedAuthnContext
5757
{
58-
AuthnContextClassRef = idp.RequestedAuthnContextClassRef
58+
AuthnContextClassRef = idp.RequestedAuthnContextClassRef,
59+
Comparison = idp.RequestedAuthnContextClassRefComparison
5960
}
6061
};
6162
var generateContext = new GenerateRelayStateContext

src/Solid.Identity.Protocols.Saml2p/Middleware/Idp/AcceptSsoEndpointMiddleware.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ public override async Task InvokeAsync(HttpContext context)
7272
context.Response.Redirect(ssoContext.ReturnUrl);
7373
}
7474
else if (ssoContext.AuthenticationScheme != null)
75-
await ChallengeAsync(context, request, ssoContext.ReturnUrl, ssoContext.AuthenticationScheme);
75+
await ChallengeAsync(context, request, ssoContext.ReturnUrl, ssoContext.AuthenticationPropertyItems, ssoContext.AuthenticationScheme);
7676
else
77-
await ChallengeAsync(context, request, ssoContext.ReturnUrl);
77+
await ChallengeAsync(context, request, ssoContext.ReturnUrl, ssoContext.AuthenticationPropertyItems);
7878
}
7979

8080
// TODO: extract this to a validator class and test it

0 commit comments

Comments
 (0)