Skip to content

Instantly share code, notes, and snippets.

@jhauge
Last active November 4, 2021 07:04
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jhauge/d154af1badc24dc11d974f5413de67d4 to your computer and use it in GitHub Desktop.
Save jhauge/d154af1badc24dc11d974f5413de67d4 to your computer and use it in GitHub Desktop.
Setting up Owin un umbraco for frontend login via adfs
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);
}
}
}
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);
}
}
}
}
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