Skip to content

Commit

Permalink
Merge pull request #580 from akunzai/owin-logout
Browse files Browse the repository at this point in the history
Support CAS logout for OWIN
  • Loading branch information
akunzai authored Apr 14, 2024
2 parents 49a9892 + 12fe902 commit 3df4896
Show file tree
Hide file tree
Showing 16 changed files with 299 additions and 211 deletions.
15 changes: 13 additions & 2 deletions samples/AspNetCoreReactSample/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,20 @@ public IActionResult Login(string? scheme)

[AllowAnonymous]
[HttpGet("/account/logout")]
public IActionResult Logout()
public async Task Logout(string? redirectUrl)
{
return SignOut();
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = "/";
}

var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
await HttpContext.SignOutAsync(properties);
var authScheme = User.Claims.FirstOrDefault(x => string.Equals(x.Type, "auth_scheme"))?.Value;
if (!string.IsNullOrWhiteSpace(authScheme))
{
await HttpContext.SignOutAsync(authScheme, properties);
}
}
}

Expand Down
52 changes: 11 additions & 41 deletions samples/AspNetCoreReactSample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Security.Claims;
using GSS.Authentication.CAS;
using GSS.Authentication.CAS.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
Expand All @@ -16,45 +15,7 @@
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Events.OnSigningOut = async context =>
{
var authService = context.HttpContext.RequestServices.GetRequiredService<IAuthenticationService>();
var result = await authService.AuthenticateAsync(context.HttpContext, null);
string? authScheme = null;
if (result.Properties != null || result.Properties!.Items.TryGetValue(".AuthScheme", out authScheme) &&
string.IsNullOrWhiteSpace(authScheme))
{
if (string.Equals(authScheme, CasDefaults.AuthenticationType))
{
options.CookieManager.DeleteCookie(context.HttpContext, options.Cookie.Name!,
context.CookieOptions);
// redirecting to the identity provider to sign out
await context.HttpContext.SignOutAsync(authScheme);
return;
}

if (string.Equals(authScheme, OpenIdConnectDefaults.AuthenticationScheme) &&
builder.Configuration.GetValue("OIDC:SaveTokens", false))
{
options.CookieManager.DeleteCookie(context.HttpContext, options.Cookie.Name!,
context.CookieOptions);
// redirecting to the identity provider to sign out
await context.HttpContext.SignOutAsync(authScheme);
return;
}
}

await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenticationOptions>(
context.HttpContext,
context.Scheme,
context.Options,
context.Properties,
"/"
));
};
})
.AddCookie()
.AddCAS(options =>
{
options.CasServerUrlBase = builder.Configuration["CAS:ServerUrlBase"]!;
Expand All @@ -65,6 +26,7 @@ await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenti
return Task.CompletedTask;
// Map claims from assertion
var assertion = context.Assertion;
context.Identity.AddClaim(new Claim("auth_scheme", CasDefaults.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, assertion.PrincipalName));
if (assertion.Attributes.TryGetValue("display_name", out var displayName) &&
!string.IsNullOrWhiteSpace(displayName))
Expand Down Expand Up @@ -92,14 +54,22 @@ await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenti
options.ClientSecret = builder.Configuration["OIDC:ClientSecret"];
options.Authority = builder.Configuration["OIDC:Authority"];
options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();
// required for single logout
options.SaveTokens = builder.Configuration.GetValue("OIDC:SaveTokens", false);
options.ResponseType = OpenIdConnectResponseType.Code;
var scope = builder.Configuration["OIDC:Scope"];
if (!string.IsNullOrWhiteSpace(scope))
{
scope.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList().ForEach(s => options.Scope.Add(s));
}

options.Events.OnTokenValidated = context =>
{
if (context.Principal?.Identity is ClaimsIdentity claimIdentity)
{
claimIdentity.AddClaim(new Claim("auth_scheme", OpenIdConnectDefaults.AuthenticationScheme));
}
return Task.CompletedTask;
};
options.TokenValidationParameters.NameClaimType = builder.Configuration.GetValue("OIDC:NameClaimType", "name");
});

Expand Down
17 changes: 14 additions & 3 deletions samples/AspNetCoreSample/Pages/Account/Logout.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AspNetCoreSample.Pages.Account;

public class LogoutModel : PageModel
{
public IActionResult OnGet()
public async Task OnGet(string? redirectUrl)
{
return SignOut();
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = "/";
}

var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
await HttpContext.SignOutAsync(properties);
var authScheme = User.Claims.FirstOrDefault(x => string.Equals(x.Type, "auth_scheme"))?.Value;
if (!string.IsNullOrWhiteSpace(authScheme))
{
await HttpContext.SignOutAsync(authScheme, properties);
}
}
}
67 changes: 18 additions & 49 deletions samples/AspNetCoreSample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Security.Cryptography.X509Certificates;
using GSS.Authentication.CAS;
using GSS.Authentication.CAS.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
Expand Down Expand Up @@ -34,53 +33,7 @@
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Events.OnSigningOut = async context =>
{
var authService = context.HttpContext.RequestServices.GetRequiredService<IAuthenticationService>();
var result = await authService.AuthenticateAsync(context.HttpContext, null);
string? authScheme = null;
if (result.Properties != null || result.Properties!.Items.TryGetValue(".AuthScheme", out authScheme) &&
string.IsNullOrWhiteSpace(authScheme))
{
if (string.Equals(authScheme, CasDefaults.AuthenticationType))
{
options.CookieManager.DeleteCookie(context.HttpContext, options.Cookie.Name!,
context.CookieOptions);
// redirecting to the identity provider to sign out
await context.HttpContext.SignOutAsync(authScheme);
return;
}

if (string.Equals(authScheme, OpenIdConnectDefaults.AuthenticationScheme) &&
builder.Configuration.GetValue("OIDC:SaveTokens", false))
{
options.CookieManager.DeleteCookie(context.HttpContext, options.Cookie.Name!,
context.CookieOptions);
// redirecting to the identity provider to sign out
await context.HttpContext.SignOutAsync(authScheme);
return;
}
}

var saml2SessionIndex = context.HttpContext.User.FindFirst(Saml2ClaimTypes.SessionIndex);
if (saml2SessionIndex != null)
{
// redirecting to the identity provider to sign out
await context.HttpContext.SignOutAsync(Saml2Defaults.Scheme);
return;
}

await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenticationOptions>(
context.HttpContext,
context.Scheme,
context.Options,
context.Properties,
"/"
));
};
})
.AddCookie()
.AddCAS(options =>
{
options.CasServerUrlBase = builder.Configuration["CAS:ServerUrlBase"]!;
Expand All @@ -92,6 +45,7 @@ await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenti
return Task.CompletedTask;
// Map claims from assertion
var assertion = context.Assertion;
context.Identity.AddClaim(new Claim("auth_scheme", CasDefaults.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, assertion.PrincipalName));
if (assertion.Attributes.TryGetValue("display_name", out var displayName) &&
!string.IsNullOrWhiteSpace(displayName))
Expand Down Expand Up @@ -133,16 +87,24 @@ await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenti
options.ClientSecret = builder.Configuration["OIDC:ClientSecret"];
options.Authority = builder.Configuration["OIDC:Authority"];
options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();
// required for single logout
options.SaveTokens = builder.Configuration.GetValue("OIDC:SaveTokens", false);
options.ResponseType = OpenIdConnectResponseType.Code;
var scope = builder.Configuration["OIDC:Scope"];
if (!string.IsNullOrWhiteSpace(scope))
{
scope.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList().ForEach(s => options.Scope.Add(s));
}

options.TokenValidationParameters.NameClaimType =
builder.Configuration.GetValue("OIDC:NameClaimType", "name");
options.Events.OnTokenValidated = context =>
{
if (context.Principal?.Identity is ClaimsIdentity claimIdentity)
{
claimIdentity.AddClaim(new Claim("auth_scheme", OpenIdConnectDefaults.AuthenticationScheme));
}
return Task.CompletedTask;
};
options.Events.OnRemoteFailure = context =>
{
var failure = context.Failure;
Expand Down Expand Up @@ -182,6 +144,13 @@ await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenti
{
MetadataLocation = builder.Configuration["SAML2:IdP:MetadataLocation"],
});
options.Notifications.AcsCommandResultCreated = (result,_) =>
{
if (result.Principal?.Identity is ClaimsIdentity claimIdentity)
{
claimIdentity.AddClaim(new Claim("auth_scheme", Saml2Defaults.Scheme));
}
};
options.Notifications.MetadataCreated = (metadata, _) =>
{
var ssoDescriptor = metadata.RoleDescriptors.OfType<SpSsoDescriptor>().First();
Expand Down
17 changes: 14 additions & 3 deletions samples/BlazorSample/Pages/Account/Logout.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace BlazorSample.Pages.Account;

public class Logout : PageModel
{
public IActionResult OnGet()
public async Task OnGet(string? redirectUrl)
{
return SignOut();
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = "/";
}

var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
await HttpContext.SignOutAsync(properties);
var authScheme = User.Claims.FirstOrDefault(x => string.Equals(x.Type, "auth_scheme"))?.Value;
if (!string.IsNullOrWhiteSpace(authScheme))
{
await HttpContext.SignOutAsync(authScheme, properties);
}
}
}
52 changes: 11 additions & 41 deletions samples/BlazorSample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using BlazorSample.Components;
using GSS.Authentication.CAS;
using GSS.Authentication.CAS.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
Expand All @@ -19,45 +18,7 @@
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Events.OnSigningOut = async context =>
{
var authService = context.HttpContext.RequestServices.GetRequiredService<IAuthenticationService>();
var result = await authService.AuthenticateAsync(context.HttpContext, null);
string? authScheme = null;
if (result.Properties != null || result.Properties!.Items.TryGetValue(".AuthScheme", out authScheme) &&
string.IsNullOrWhiteSpace(authScheme))
{
if (string.Equals(authScheme, CasDefaults.AuthenticationType))
{
options.CookieManager.DeleteCookie(context.HttpContext, options.Cookie.Name!,
context.CookieOptions);
// redirecting to the identity provider to sign out
await context.HttpContext.SignOutAsync(authScheme);
return;
}

if (string.Equals(authScheme, OpenIdConnectDefaults.AuthenticationScheme) &&
builder.Configuration.GetValue("OIDC:SaveTokens", false))
{
options.CookieManager.DeleteCookie(context.HttpContext, options.Cookie.Name!,
context.CookieOptions);
// redirecting to the identity provider to sign out
await context.HttpContext.SignOutAsync(authScheme);
return;
}
}

await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenticationOptions>(
context.HttpContext,
context.Scheme,
context.Options,
context.Properties,
"/"
));
};
})
.AddCookie()
.AddCAS(options =>
{
options.CasServerUrlBase = builder.Configuration["CAS:ServerUrlBase"]!;
Expand All @@ -68,6 +29,7 @@ await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenti
return Task.CompletedTask;
// Map claims from assertion
var assertion = context.Assertion;
context.Identity.AddClaim(new Claim("auth_scheme", CasDefaults.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, assertion.PrincipalName));
if (assertion.Attributes.TryGetValue("display_name", out var displayName) &&
!string.IsNullOrWhiteSpace(displayName))
Expand Down Expand Up @@ -95,14 +57,22 @@ await context.Options.Events.RedirectToLogout(new RedirectContext<CookieAuthenti
options.ClientSecret = builder.Configuration["OIDC:ClientSecret"];
options.Authority = builder.Configuration["OIDC:Authority"];
options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();
// required for single logout
options.SaveTokens = builder.Configuration.GetValue("OIDC:SaveTokens", false);
options.ResponseType = OpenIdConnectResponseType.Code;
var scope = builder.Configuration["OIDC:Scope"];
if (!string.IsNullOrWhiteSpace(scope))
{
scope.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList().ForEach(s => options.Scope.Add(s));
}

options.Events.OnTokenValidated = context =>
{
if (context.Principal?.Identity is ClaimsIdentity claimIdentity)
{
claimIdentity.AddClaim(new Claim("auth_scheme", OpenIdConnectDefaults.AuthenticationScheme));
}
return Task.CompletedTask;
};
options.TokenValidationParameters.NameClaimType = builder.Configuration.GetValue("OIDC:NameClaimType", "name");
});

Expand Down
16 changes: 14 additions & 2 deletions samples/OwinSample/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Web;
using System.Web.Mvc;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;

namespace OwinSample.Controllers
{
Expand All @@ -22,9 +23,20 @@ public ActionResult Login(string scheme)

// GET /Account/Logout
[HttpGet]
public void Logout()
public void Logout(string redirectUrl)
{
Request.GetOwinContext().Authentication.SignOut();
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = "/";
}
var owinContext = Request.GetOwinContext();
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
owinContext.Authentication.SignOut(properties, CookieAuthenticationDefaults.AuthenticationType);
var authScheme = owinContext.Authentication.User.FindFirst("auth_scheme")?.Value;
if (!string.IsNullOrWhiteSpace(authScheme))
{
owinContext.Authentication.SignOut(properties, authScheme);
}
}
}
}
Loading

0 comments on commit 3df4896

Please sign in to comment.