-
-
Save jhauge/d154af1badc24dc11d974f5413de67d4 to your computer and use it in GitHub Desktop.
Setting up Owin un umbraco for frontend login via adfs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Configuration; | |
using Cphbusiness.Web; | |
using Cphbusiness.Web.Models.UmbracoIdentity; | |
using Microsoft.AspNet.Identity; | |
using Microsoft.Owin; | |
using Microsoft.Owin.Security; | |
using Microsoft.Owin.Security.Cookies; | |
using Microsoft.Owin.Security.WsFederation; | |
using Owin; | |
using UmbracoIdentity; | |
[assembly: OwinStartup(typeof(UmbracoIdentityStartup))] | |
namespace Site.Web | |
{ | |
/// <summary> | |
/// OWIN Startup class for UmbracoIdentity | |
/// </summary> | |
public class UmbracoIdentityStartup | |
{ | |
public void Configuration(IAppBuilder app) | |
{ | |
// Single method to configure the Identity user manager for use with Umbraco | |
app.ConfigureUserManagerForUmbracoMembers<UmbracoApplicationMember>(); | |
// Call into Kentor.OwinCookieSaver attempting to solve cookie deletion malfunction | |
// See here for more info: | |
// http://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser | |
app.UseKentorOwinCookieSaver(); | |
// Enable the application to use a cookie to store information for the | |
// signed in user and to use a cookie to temporarily store information | |
// about a user logging in with a third party login provider | |
// Configure the sign in cookie | |
var cookieOptions = new CookieAuthenticationOptions | |
{ | |
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, | |
#if !DEBUG | |
CookieSecure = CookieSecureOption.Always, | |
#endif | |
ExpireTimeSpan = TimeSpan.FromDays(1) | |
//Provider = new CookieAuthenticationProvider | |
//{ | |
// // Enables the application to validate the security stamp when the user | |
// // logs in. This is a security feature which is used when you | |
// // change a password or add an external login to your account. | |
// OnValidateIdentity = SecurityStampValidator | |
// .OnValidateIdentity | |
// <UmbracoMembersUserManager<UmbracoApplicationMember>, UmbracoApplicationMember, int>( | |
// TimeSpan.FromMinutes(30), | |
// (manager, user) => user.GenerateUserIdentityAsync(manager), | |
// UmbracoIdentity.IdentityExtensions.GetUserId<int> | |
// ) | |
//} | |
}; | |
app.UseCookieAuthentication(cookieOptions); | |
//Ensure owin is configured for Umbraco back office authentication - this must | |
// be configured AFTER the standard UseCookieConfiguration above. | |
app.UseUmbracoBackAuthentication(); | |
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType); | |
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); | |
// NOTE: WsFederationAuthentication needs a Name ID claim to function. (Not very well documented) | |
// See: http://stackoverflow.com/questions/25900262/unable-to-login-with-microsoft-owin-security-wsfederation-and-adfs-3 | |
// And: http://darb.io/blog/2014/06/30/WebAPI-and-ADFS-as-external-login-provider/ | |
var realm = ConfigurationManager.AppSettings["adfsRealm"]; | |
var metadataAddress = ConfigurationManager.AppSettings["adfsMetadata"]; | |
var authOptions = new WsFederationAuthenticationOptions | |
{ | |
Wtrealm = realm, | |
MetadataAddress = metadataAddress | |
}; | |
app.UseWsFederationAuthentication(authOptions); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Security.Claims; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System.Web; | |
using System.Web.Mvc; | |
using Cphbusiness.Web.Exceptions; | |
using Cphbusiness.Web.Models; | |
using Cphbusiness.Web.Models.UmbracoIdentity; | |
using Microsoft.AspNet.Identity; | |
using Microsoft.AspNet.Identity.Owin; | |
using Microsoft.Owin; | |
using Microsoft.Owin.Security; | |
using Umbraco.Core.Logging; | |
using Umbraco.Web; | |
using Umbraco.Web.Models; | |
using Umbraco.Web.Mvc; | |
using UmbracoIdentity; | |
namespace Site.Web.Controllers | |
{ | |
public class IntranetLoginSurfaceController : SurfaceController | |
{ | |
private const string XsrfKey = ""; | |
private UmbracoMembersUserManager<UmbracoApplicationMember> _userManager; | |
protected IOwinContext OwinContext | |
{ | |
get { return Request.GetOwinContext(); } | |
} | |
public UmbracoMembersUserManager<UmbracoApplicationMember> UserManager | |
{ | |
get | |
{ | |
return _userManager ?? (_userManager = OwinContext | |
.GetUserManager<UmbracoMembersUserManager<UmbracoApplicationMember>>()); | |
} | |
} | |
[AllowAnonymous] | |
public ActionResult EndSignout() | |
{ | |
// NOTE: This doesn't work since adfs server doesn't redirect when signout is done | |
return RedirectToLocal("/intra"); | |
} | |
public ActionResult ShowLogin(string wa) | |
{ | |
var model = new MemberLoginModel | |
{ | |
MessageClass = "hidden", | |
ReturnUrl = "/intra", | |
BodyText = CurrentPage.GetPropertyValue<string>("bodyText") | |
}; | |
if (!string.IsNullOrEmpty(Request["ReturnUrl"])) | |
{ | |
model.ReturnUrl = Request["ReturnUrl"]; | |
} | |
return PartialView("MemberLogin", model); | |
} | |
[AllowAnonymous] | |
[ValidateAntiForgeryToken] | |
public ActionResult StartLogin(RenderModel model, string returnUrl = null) | |
{ | |
if (string.IsNullOrEmpty(returnUrl)) | |
returnUrl = Request.RawUrl; | |
var url = Url.SurfaceAction<IntranetLoginSurfaceController>("ExternalLoginCallback", new {returnUrl}); | |
return new ChallengeResult("Federation", url); | |
} | |
[HttpGet] | |
[AllowAnonymous] | |
public async Task<ActionResult> ExternalLoginCallback(RenderModel model, string returnUrl) | |
{ | |
var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync(); | |
if (loginInfo == null || | |
!loginInfo.ExternalIdentity.HasClaim(c => c.Type == "http://schemas.xmlsoap.org/claims/Group")) | |
{ | |
//go home, invalid callback | |
LogHelper.Warn<IntranetLoginSurfaceController>("Login info was null"); | |
return RedirectToLocal(returnUrl); | |
} | |
// Read group claim and check if user is a student | |
var groupClaim = loginInfo.ExternalIdentity | |
.Claims | |
.FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/claims/Group"); | |
var groupName = groupClaim != null ? groupClaim.Value : "notallowed"; | |
//var allowedGroups = new[] {"staff", "ressource"}; | |
var disallowedGroups = new[] {"student"}; | |
if (disallowedGroups.Contains(groupName)) | |
{ | |
//go home, invalid callback | |
LogHelper.Warn<IntranetLoginSurfaceController>($"Login group not allowed : {groupName}"); | |
return RedirectToLocal(returnUrl); | |
} | |
#if DEBUG | |
LogHelper.Debug<IntranetLoginSurfaceController>("Claims recieved"); | |
var claimsStr = new StringBuilder(); | |
foreach (var claim in loginInfo.ExternalIdentity.Claims) | |
{ | |
claimsStr.AppendLine(string.Format("{0} : {1}", claim.Type, claim.Value)); | |
} | |
LogHelper.Debug<IntranetLoginSurfaceController>(claimsStr.ToString); | |
#endif | |
var userName = loginInfo.ExternalIdentity.Claims.First(c => c.Type == ClaimTypes.GivenName).Value; | |
if (loginInfo.ExternalIdentity.HasClaim(c => c.Type == ClaimTypes.Surname)) | |
{ | |
var surName = loginInfo.ExternalIdentity.Claims.First(c => c.Type == ClaimTypes.Surname).Value; | |
userName += string.Format(" {0}", surName); | |
} | |
var cn = loginInfo.Email.Substring(0, loginInfo.Email.IndexOf("@", StringComparison.InvariantCulture)); | |
// Find member for this user by Email | |
var user = await UserManager.FindByEmailAsync(loginInfo.Email); | |
if (user == null) | |
{ | |
// User has not been created as member, create a member | |
user = new UmbracoApplicationMember | |
{ | |
Name = userName, | |
UserName = cn, | |
Email = loginInfo.Email | |
}; | |
var result = await UserManager.CreateAsync(user); | |
if (!result.Succeeded) | |
{ | |
LogHelper.Error<IntranetLoginSurfaceController>("Error creating member for " + loginInfo.Email, | |
new UmbracoMemberCreationException(loginInfo.Email)); | |
return RedirectToLocal("/"); | |
// Cannot redirect to /intra - it will set off a new login leading to login loop | |
} | |
// Add user as member of intranetuser group | |
Services.MemberService.AssignRole(user.UserName, "Intranet user"); | |
} | |
// Sign in as the user we found in Umbraco member store | |
LogHelper.Debug<IntranetLoginSurfaceController>("User obtained: " + user.UserName); | |
// Passing claims from external login to internal claims | |
var claims = new List<Claim> | |
{ | |
new Claim("Group", groupName) | |
}; | |
await SignInAsync(user, claims, false); | |
return RedirectToLocal(returnUrl); | |
} | |
public ActionResult HandleLogout() | |
{ | |
if (Members.IsLoggedIn()) | |
{ | |
// Do a total logout | |
// TODO: Check how we can get the ADFS server to redirect to another location after logout | |
LogHelper.Debug<IntranetLoginSurfaceController>("OWIN logout"); | |
OwinContext.Authentication.SignOut(); | |
} | |
LogHelper.Debug<IntranetLoginSurfaceController>("Application logout"); | |
Session.Abandon(); | |
return Redirect("/intra/"); | |
} | |
private ActionResult RedirectToLocal(string returnUrl) | |
{ | |
LogHelper.Debug<IntranetLoginSurfaceController>("Redirect"); | |
if (Url.IsLocalUrl(returnUrl)) | |
{ | |
return Redirect(returnUrl); | |
} | |
return Redirect("/intra/"); | |
} | |
private async Task SignInAsync(UmbracoApplicationMember member, IEnumerable<Claim> claims, bool isPersistent) | |
{ | |
OwinContext.Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie); | |
OwinContext.Authentication.SignIn(new AuthenticationProperties {IsPersistent = isPersistent}, | |
await member.GenerateUserIdentityAsync(UserManager, claims)); | |
} | |
private class ChallengeResult : HttpUnauthorizedResult | |
{ | |
public ChallengeResult(string provider, string redirectUri) | |
{ | |
LoginProvider = provider; | |
RedirectUri = redirectUri; | |
} | |
private string LoginProvider { get; } | |
private string RedirectUri { get; } | |
public override void ExecuteResult(ControllerContext context) | |
{ | |
LogHelper.Debug<IntranetLoginSurfaceController>("Challenge"); | |
var properties = new AuthenticationProperties {RedirectUri = RedirectUri}; | |
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); | |
} | |
public override string ToString() | |
{ | |
return string.Format("Provider: {0}, Redirect: {1}", LoginProvider, RedirectUri); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Security.Claims; | |
using System.Threading.Tasks; | |
using Microsoft.AspNet.Identity; | |
using UmbracoIdentity; | |
namespace Site.Web.Models.UmbracoIdentity | |
{ | |
public class UmbracoApplicationMember : UmbracoIdentityMember | |
{ | |
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<UmbracoApplicationMember, int> manager, | |
IEnumerable<Claim> claims) | |
{ | |
// Note the authenticationType must match the one | |
// defined in CookieAuthenticationOptions.AuthenticationType | |
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); | |
// Add custom user claims here | |
userIdentity.AddClaims(claims); | |
return userIdentity; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment