Skip to content

Commit

Permalink
Adds sp-initiated endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
gislikonrad committed Aug 23, 2023
1 parent 806215f commit 626cb76
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 19 deletions.
6 changes: 6 additions & 0 deletions src/AspNetCore.IdpSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using Solid.Identity.Protocols.Saml2p;
using Solid.Identity.Protocols.Saml2p.Abstractions;
Expand All @@ -26,6 +27,11 @@ public class Startup
{
private static readonly string SigningCertificateBase64 = "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";

static Startup()
{
IdentityModelEventSource.ShowPII = true;
}

public Startup(IConfiguration configuration)
{
Configuration = configuration;
Expand Down
8 changes: 8 additions & 0 deletions src/AspNetCore.SpSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using Solid.Identity.Protocols.Saml2p;
using Solid.Identity.Protocols.Saml2p.Authentication;
Expand All @@ -26,6 +27,10 @@ namespace AspNetCore.SpSample
{
public class Startup
{
static Startup()
{
IdentityModelEventSource.ShowPII = true;
}
public static readonly string SigningCertificateBase64 = "MIIDPDCCAiSgAwIBAgIQNl7j8AGK7J1B4E/BX+vSLzANBgkqhkiG9w0BAQUFADAgMR4wHAYDVQQDDBV0b2tlbi5zaWduYXR1cmUudGVzdHMwHhcNMjAwOTIzMTU1NTIwWhcNMzAwOTI1MTU1NTIwWjAgMR4wHAYDVQQDDBV0b2tlbi5zaWduYXR1cmUudGVzdHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+zSiOz1UGpHEUgd23nZ63TmX7EnbgRBqeJjcujZdyjCJvV5u/uIbSR3LBNlbS8Rv4uqYWRxotUaTSCdif/jryzHUwPORU2lt7XbeIGEK9aNv9LcxpuEu1sUo/7Ei34uJtMdoZh6cvlzGoGMcTxapQBxrmQyE6LOHkni/sA8zI+mKHbPrRUyeiL38A54Dnc4wAWWy8euQtu9bJge0qcnT0ezp41A1z/BQ6yRKioQ9jHiOgIKnBDdAhWTFPKH4Roq4lIMt8PpIy5F2VYP5rz95obFExnSwvd+8XHaHP5rjZ7yLhhSD9yZtYzLf9nw3ea6KgAAHBbg2iFJIswb1opzJlAgMBAAGjcjBwMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwIAYDVR0RBBkwF4IVdG9rZW4uc2lnbmF0dXJlLnRlc3RzMB0GA1UdDgQWBBQa8UJxJKZCMuFEbsUqLJtj1TMJODANBgkqhkiG9w0BAQUFAAOCAQEAguPDY/RnMhipwJS+6gsthlQ1lY55KMCkxEcyAJjz5pZpgfd4oG0gfmP7S2V1bxQ8hLAdYMRyF4yfUnog7YwCwecSIG0aADaksHWbQU+k51rk4d1VetZnwmRfktzs560dmprQKL9rseYZQhFbYYXe8yyFwe3fPgOJhZkIgq7eUzQRO6kXOEwRxxYmWE3XhiiALLGUA9Yb6yyLg3sQ4Myequk+W4Fxw3n9j0jCRTjye+JlycwLM+ST4Z5lFuZVLHWZqqreUYcRvYpJ9lIq7C5b/bQnJQ873rSF6jjx17E+/YrQFpJbjSJrl8cSx3QephdUWUC2Op4n051O91tM32Lrpg==";
public Startup(IConfiguration configuration)
{
Expand All @@ -48,6 +53,8 @@ public void ConfigureServices(IServiceCollection services)
.AddSaml2p(options =>
{
options.DefaultIssuer = "https://localhost:5003/saml";
options.StartPath = "/saml2p/start";
options.FinishPath = "/saml2p/finish";
options.AddIdentityProvider("https://localhost:5001/saml", idp =>
{
idp.BaseUrl = new Uri("https://localhost:5001");
Expand Down Expand Up @@ -106,6 +113,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseCookiePolicy();
app.UseEndpoints(endpoints =>
{
endpoints.MapSaml2pServiceProvider("/sso");
endpoints.MapRazorPages();
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,7 @@ protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync
if (!result.IsSuccessful)
throw new SamlResponseException(result.PartnerId, result.Status, result.SubStatus);

var properties = new AuthenticationProperties
{
IssuedUtc = result.SecurityToken.ValidFrom,
ExpiresUtc = result.SecurityToken.ValidTo
};
var token = new AuthenticationToken
{
Name = "saml2",
Value = result.Token
};
properties.StoreTokens(new []{ token });
var ticket = new AuthenticationTicket(result.Subject, properties, Scheme.Name);
var ticket = new AuthenticationTicket(result.Subject, result.Properties, Scheme.Name);
return HandleRequestResult.Success(ticket);
}
catch(Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ public static IApplicationBuilder UseSaml2pIdentityProvider(this IApplicationBui
;
}

/// <summary>
/// Maps the SP endpoints to <paramref name="path"/>.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/> to map the endpoints to.</param>
/// <param name="path">The base path to map the endpoints to.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance so that additional calls can be chained.</returns>
public static IApplicationBuilder UseSaml2pServiceProvider(this IApplicationBuilder builder, PathString path)
{
var options = builder.ApplicationServices.GetRequiredService<IOptions<Saml2pOptions>>().Value;

return builder
.Map(path.Add(options.StartPath), b => b.UseStartSsoEndpoint(path))
.Map(path.Add(options.FinishPath), b => b.UseFinishSsoEndpoint(path))
;
}

internal static IApplicationBuilder UseStartSsoEndpoint(this IApplicationBuilder builder, PathString path)
=> builder.UsePathBase(path).UseMiddleware<StartSsoEndpointMiddleware>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,23 @@ public static IEndpointRouteBuilder MapSaml2pIdentityProvider(this IEndpointRout
var options = builder.ServiceProvider.GetRequiredService<IOptions<Saml2pOptions>>().Value;

builder.Map(path.Add(options.AcceptPath), builder.CreateApplicationBuilder().UseAcceptSsoEndpoint(path).Build()).WithMetadata(new HttpMethodMetadata(new[] { HttpMethods.Get, HttpMethods.Post }));
builder.Map(path.Add(options.InitiatePath), builder.CreateApplicationBuilder().UseInitiateSsoEndpoint(path).Build()).WithMetadata(new HttpMethodMetadata(new[] { HttpMethods.Get, HttpMethods.Post })); ;
builder.Map(path.Add(options.CompletePath), builder.CreateApplicationBuilder().UseCompleteSsoEndpoint(path).Build()).WithMetadata(new HttpMethodMetadata(new[] { HttpMethods.Get, HttpMethods.Post })); ;
builder.Map(path.Add(options.InitiatePath), builder.CreateApplicationBuilder().UseInitiateSsoEndpoint(path).Build()).WithMetadata(new HttpMethodMetadata(new[] { HttpMethods.Get, HttpMethods.Post }));
builder.Map(path.Add(options.CompletePath), builder.CreateApplicationBuilder().UseCompleteSsoEndpoint(path).Build()).WithMetadata(new HttpMethodMetadata(new[] { HttpMethods.Get, HttpMethods.Post }));
return builder;
}

/// <summary>
/// Maps the SP endpoints to <paramref name="path"/>.
/// </summary>
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to map the endpoints to.</param>
/// <param name="path">The base path to map the endpoints to.</param>
/// <returns>The <see cref="IEndpointRouteBuilder"/> instance so that additional calls can be chained.</returns>
public static IEndpointRouteBuilder MapSaml2pServiceProvider(this IEndpointRouteBuilder builder, PathString path)
{
var options = builder.ServiceProvider.GetRequiredService<IOptions<Saml2pOptions>>().Value;

builder.Map(path.Add(options.StartPath), builder.CreateApplicationBuilder().UseStartSsoEndpoint(path).Build()).WithMetadata(new HttpMethodMetadata(new[] { HttpMethods.Get, HttpMethods.Post }));
builder.Map(path.Add(options.FinishPath), builder.CreateApplicationBuilder().UseFinishSsoEndpoint(path).Build()).WithMetadata(new HttpMethodMetadata(new[] { HttpMethods.Get, HttpMethods.Post }));
return builder;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,15 @@ public override async Task InvokeAsync(HttpContext context)
{
try
{
_ = await FinishSsoAsync(context);
var result = await FinishSsoAsync(context);
if (!result.IsSuccessful)
{
context.Response.StatusCode = 401;
return;
}

await context.SignInAsync(result.Subject, result.Properties);
context.Response.Redirect(Options.DefaultRedirectPath);
}
catch(InvalidOperationException)
{
Expand Down Expand Up @@ -158,7 +166,21 @@ public async Task<FinishSsoResult> FinishSsoAsync(HttpContext context)

context.User = validateContext.Subject;

return FinishSsoResult.Success(partner.Id, validateContext.Response.XmlSecurityToken, validateContext.SecurityToken, validateContext.Subject);
var properties = new AuthenticationProperties();
if (validateContext.Subject != null)
{
properties.IssuedUtc = validateContext.SecurityToken!.ValidFrom;
properties.ExpiresUtc = validateContext.SecurityToken!.ValidTo;

var authn = new AuthenticationToken
{
Name = "saml2",
Value = validateContext.Response.XmlSecurityToken
};
properties.StoreTokens(new[] { authn });
}

return FinishSsoResult.Success(partner.Id, validateContext.Response.XmlSecurityToken, validateContext.SecurityToken, validateContext.Subject, properties);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class ValidateTokenContext
public TokenValidationParameters TokenValidationParameters { get; internal set; }

/// <summary>
/// The <see cref="Saml2SecurityTokenHandler"/> used to validatethe incoming security token.
/// The <see cref="Saml2SecurityTokenHandler"/> used to validate the incoming security token.
/// </summary>
public Saml2SecurityTokenHandler Handler { get; internal set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication;

namespace Solid.Identity.Protocols.Saml2p.Models.Results
{
Expand All @@ -21,16 +22,18 @@ private FinishSsoResult() { }
/// <param name="token">The XML representation of the received Saml2 token.</param>
/// <param name="securityToken">The <see cref="Saml2SecurityToken"/> from a <see cref="SamlResponse"/>.</param>
/// <param name="subject">The <see cref="ClaimsPrincipal"/> that was created from <paramref name="securityToken"/>.</param>
/// <param name="properties">The <see cref="AuthenticationProperties"/> instance used when signing in.</param>
/// <returns>A success result.</returns>
public static FinishSsoResult Success(string partnerId, string token, Saml2SecurityToken securityToken, ClaimsPrincipal subject)
public static FinishSsoResult Success(string partnerId, string token, Saml2SecurityToken securityToken, ClaimsPrincipal subject, AuthenticationProperties properties)
{
return new FinishSsoResult
{
Status = Saml2pConstants.Statuses.Success,
PartnerId = partnerId,
Token = token,
SecurityToken = securityToken,
Subject = subject
Subject = subject,
Properties = properties
};
}

Expand Down Expand Up @@ -81,6 +84,11 @@ public static FinishSsoResult Fail(string partnerId, Uri status, Uri subStatus)
/// The <see cref="ClaimsPrincipal"/> that was created from <see cref="SecurityToken"/>.
/// </summary>
public ClaimsPrincipal Subject { get; private set; }

/// <summary>
/// The <see cref="AuthenticationProperties"/> instance used when signing in.
/// </summary>
public AuthenticationProperties Properties { get; private set; }

/// <summary>
/// The status of the SSO response.
Expand Down
5 changes: 5 additions & 0 deletions src/Solid.Identity.Protocols.Saml2p/Options/Saml2pOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ public class Saml2pOptions
/// </summary>
public PathString FinishPath { get; set; } = "/finish";

/// <summary>
/// The relative path used by default after SSO has finished (SP flow).
/// </summary>
public PathString DefaultRedirectPath { get; set; } = "/";

/// <summary>
/// Events object that contains delegates to be run during SSO (IDP flow).
/// </summary>
Expand Down

0 comments on commit 626cb76

Please sign in to comment.