I have a requirement to nest one site under another in IIS so users can go back and forth between the two without logging into both. I have done this previously with MVC sites with success by removing most of the child's web.config so it inherits the parent web.config and setting a machinekey manually in the parent config.
In the parent I set the machine key manually and verified that the child is picking it up.
I have tested two MVC 5 web apps with individual user accounts (Identity). I setup the second app as a virtual application under the main app, using the same app pool. The stock MVC apps work fine. I log into the parent, navigate to the child and it picks up the identity. I can verify because it says 'welcome [email protected]' in both parent and child sites.
But the stock ones are using the stock login/ApplicationUserManager/ApplicationSignInManager methodology, whereas our parent app has a lot of customized OWIN.
Where a stock MVC site's login method uses: "SignInManager.PasswordSignInAsync(...)"
our parent site is using: "HttpContext.GetOwinContext().Authentication.SignIn(...)"
Parent site still uses: "@User.Identity.GetUserName()" in the _loginPartial.cshtml just like the mostly stock MVC5 child site but the child NEVER picks up the Identity.User or any User Claims from the parent like the stock MVC5 parent/child sites do.
Here is some of the login:
var hash = _cryptographyService.HashPassword(model.Password);
var token = _profileService.Login(model.Email, hash);
if (token != null)
{
var userData = SerializeCustomUser(token);
var identity = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaims(new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier,model.Email),
new Claim(ClaimTypes.Name,model.Email),
new Claim("UserId", token.UserId.ToString()),
new Claim("RoleId", token.Role.ToString()),
new Claim("SchoolId", token.SchoolId.ToString()),
new Claim("CampusId", token.CampusId.ToString())
});
if (model.RememberMe)
{
Response.Cookies["UserName"].Expires = DateTime.Now.AddDays(30);
}
else
{
Response.Cookies["UserName"].Expires = DateTime.Now.AddDays(-1);
}
Response.Cookies["UserName"].Value = model.Email.Trim();
HttpContext.GetOwinContext().Authentication.SignIn(identity);
....
_ProfileService.Login goes to the Db and verifies the creds
here is the Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
// 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
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
CookieName="VerityAuthSession",
Provider = new CookieAuthenticationProvider {
OnValidateIdentity = context =>
{
var url = System.Web.HttpContext.Current?.Request.Url.ToString().ToLower();
if (url?.IndexOf("signalr") != -1 || url?.IndexOf("TwilioInfo/GetInboundCallDetails".ToLower()) != -1 || url?.IndexOf("CreateLogOffEvent".ToLower()) !=-1)
{
if(url?.IndexOf("signalr") != -1)
{
var cookie = new System.Web.HttpCookie("tmpsession", context?.Identity?.Claims?.FirstOrDefault(x => x.Type == "UserId")?.Value);
cookie.Expires = DateTime.Now.AddSeconds(10);
System.Web.HttpContext.Current?.Response.Cookies.Add(cookie);
}
context.RejectIdentity();
return Task.FromResult<int>(0);
}
DateTimeOffset now = DateTimeOffset.UtcNow;
context.OwinContext.Request.Set<double>("time.Remaining",
context.Properties.ExpiresUtc.Value.Subtract(now).TotalSeconds);
System.Web.HttpContext.Current?.Response.Cookies.Add(new System.Web.HttpCookie("lastaccess", DateTime.UtcNow.ToString("MM.dd.yyyy.HH.mm.ss")));
return Task.FromResult<object>(null);
},
OnException = context => {},
OnResponseSignIn = context =>
{
context.Properties.AllowRefresh = true;
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(double.Parse(System.Configuration.ConfigurationManager.AppSettings[EnvironmentConsts.SessiontTimeout] ?? "45"));
}
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
var signalRConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings[EnvironmentConsts.SignaRDbConnectionName].ConnectionString;
GlobalHost.DependencyResolver.UseSqlServer(signalRConnectionString); //for using SignalR with loadbalancer we need this configuration
app.MapSignalR();
}
There is some code in the startup to handle when our site ajax's to twillio so it doesn't extend their session as if they are navigating.
I found the answer.
I needed to copy over the entire customized app.UseCookieAuthentication(...
portion into the StartupAuth.cs file. The parent is giving the cookie a 'Name' property and apparently, it needs the same name in both apps.
the following needs to be in the OnApplicationStarted() method of our custom setup: AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
Our architect set this up for Ninject by having our global.asax.cs inherit form a file called 'ParentApplication.cs" in the App_Startup folder. There, the ParentApplication.cs file inherits from NinjectHttpApplication.
In this file there is a method:
protected override void OnApplicationStarted()
Hope this helps someone else.