.Net Claims Auth -- Unable to set current principal - c#

I am attempting to re-do a login system using Claims-Based auth.
So far, so good
Stepping through, it appears to correctly evaluate a username and password and correctly create a claims principal (including adding an authentication type in order to set IsAuthenticated to true, per this SO question.)
But then...
Somehow the identity doesn't seem to correctly get set on the wire. As a result, I'm redirected directly back to the login page.
The Code
I have the following in global.asax:
private void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var currentPrincipal = ClaimsPrincipal.Current;
var transformer = new ClaimsTransformer(); //My own custom transformer; code below.
var newPrincipal = transformer.Authenticate(string.Empty, currentPrincipal); // does the transformation
// as I understand, it is proper & recommnded to set both of these
Thread.CurrentPrincipal = newPrincipal;
HttpContext.Current.User = newPrincipal;
}
In my Login Controller, I have a simple test against a membership DB. I verified while debugging that this has newCP as a valid, authenticated identity that has the expected name.
[HttpPost]
[AllowAnonymous]
public ActionResult UserLogin(LoginViewModel viewModel)
{
var loginSuccess = Membership.ValidateUser(viewModel.UserName, viewModel.Password);
if (loginSuccess)
{
// CustomApplicationIdentity puts some identity-based logic into business domain terms and uses Claims underneath.
//Should have done it at the IPrincipal level, but instead I created the ToAuthenticatedStandardClaimsIdentity() which returns a new authenticated ClaimsIdentity.
var newIdentity = new CustomApplicationIdentity(viewModel.UserName);
var cp = new ClaimsPrincipal(newIdentity.ToAuthenticatedStandardClaimsIdentity());
var newCP = new ClaimsTransformer().Authenticate(string.Empty, cp);
System.Web.HttpContext.Current.User = newCP;
Thread.CurrentPrincipal = newCP;
if (!string.IsNullOrWhiteSpace(viewModel.ReturnUrl))
{
return Redirect(viewModel.ReturnUrl);
}
return RedirectToAction("Index", "Identity");
}
}
The Problem
When it redirects to the Action, I see it hit the Application_PostAuthenticateRequest again, which makes perfect sense.
However, despite previously setting the principal, this now appears to be an empty principal (no name, with IsAuthenticated set to false).
Where am I Going Wrong?
Some thoughts:
Is it because I haven't set up the SessionSecurityToken yet?
Am I completely missing something regarding threading or setting the context correctly?
Since the UserLogin method is in MVC, I also tried using controller context, but that didn't seem to work either.
Is it possible that something else could be messing with this in the middle?
Read: Is there an easy way to verify that some portion of the old login system isn't left over and toying with me?

After a ton of research (and wading through the excellent Pluralsight Course by Dominick Baier), the solution was the following:
The Over-arching Big Steps / Problems
I wasn't setting a session authentication cookie, so the redirect was being treated as a new request, which saw no cookie and didn't set a principal.
Later, when I used a session authentication manager, it turns out that Cassini (VS's built in debug server) wasn't loading the SessionAuthenticationManager at all
(IIS and IIS Express do so just fine).
The Full Solution
Step by step (again, much of this is credited to Dominick's video):
Step 1: Add identity services to config
Right-click on your project, and select "Add Reference..."
In the Framework section, select System.IdentityModel.Services
Add the following to your web.config:
(full outline below, insert the two sections within that outline in your web.config):
<configuration>
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configSections>
</configuration>
Step 2: Add session authentication manager
(which depends on that config setting)
In the system.webServer section of your web.config, add the following lines:
<remove name="RoleManager"/> <!--Not needed anymore in my case -->
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
Step 3: Remove the PostAuthenticate method in Global.asax
(no longer needed because of SAM, which detects the cookie; why run it on every request if you don't have to, right?)
Step 4: Set your Claims Transformation method to set the authentication cookie
Add these lines in your ClaimsAuthenticationManager (mine was called ClaimsTransformer). I put this in a separate method called "EstablishSession", which took in my principal after it was already transformed:
private void EstablishSession(ClaimsPrincipal transformedPrincipal)
{
var sessionToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
}
So now the cookie is always set whenever you transform a claim, which makes sense because you're only transforming a claim if the user was successfully authenticated.
Step 5: Tear your hair out for a bit...
...wondering why SessionAuthenticationManager is always null.
Seriously, everything seems to work, and your config is correct, but darn it if it isn't null every. single. time.
Step 6: Switch the debug web server to IIS Express
Ahhhh, it appears that Cassini (the build in VS Debugger) doesn't work with SessionAuthenticationManager.
However, IIS Express does. Switch it to that in your project settings.
And Voila!
Now I have a page that works.

I think you need to implement the SessionSecurityToken, or something that will persist your session between page requests. Here is a more custom type of approach:
public static int SetAuthCookie(this HttpResponseBase responseBase, User user, bool rememberMe)
{
// Initialize Session Ticket
var authTicket = new FormsAuthenticationTicket(1
, user.Email
, DateTime.Now
, DateTime.Now.AddHours(30)
, rememberMe
, JsonConvert.SerializeObject(new {
Email = user.Email,
FirstName = user.FirstName,
Id = user.Id
})
, FormsAuthentication.FormsCookiePath);
var encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
if (authTicket.IsPersistent)
authCookie.Expires = authTicket.Expiration;
responseBase.Cookies.Add(authCookie);
return encTicket.Length;
}
public static void VerifyAuthCookie(HttpContext context)
{
HttpCookie authCookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
return;
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket == null)
return;
if (authTicket.Expired)
return;
User user = !string.IsNullOrEmpty(authTicket.UserData) ? JsonConvert.DeserializeObject<User>(authTicket.UserData) : null;
if (user == null)
return;
// Create an Identity object
UserIdentity id = new UserIdentity(user, authTicket);
// This principal will flow throughout the request.
GenericPrincipal principal = new GenericPrincipal(id, new [] { "User" });
context.User = principal;
}

Related

Unable to set path ASP.NET_sessionid cookie

Hi I needed a serious help
i have tried everything but am not able to change the path of ASP.NET_sessionid cookie
its path is always set to "/" , i want to set it to a folder or directory
this issue need to be solved as it was raised by app security team
have tried , iis rewrite rule , custom session id manager
any help much aprreciated
As #iamdln said, you need to create your own SessionIDManager but you also need to config it on your Web.config.
It worked for me.
Your SessionIdManager class,
public class MySessionIDManager : SessionIDManager, ISessionIDManager
{
void ISessionIDManager.SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded)
{
base.SaveSessionID(context, id, out redirected, out cookieAdded);
if (cookieAdded)
{
var name = "ASP.NET_SessionId";
var cookie = context.Response.Cookies[name];
cookie.Path = "/yourPath";
}
}
}
Web.config, replace namespace and class for yours.
This goes inside <system.web> section.
<sessionState sessionIDManagerType = "Namespace.MySessionIDManager"></sessionState>
Original links:
ASP.NET Forum - Explains how to override path
StackOverFlow - Explains how to override domain
Both are quite similar anyway.
You will need to create your own SessionIDManager which is inherited from ISessionIDManager and change the cookie.Path to whatever you want.
static HttpCookie CreateSessionCookie(String id) {
HttpCookie cookie;
cookie = new HttpCookie(Config.CookieName, id);
cookie.Path = "/";
cookie.SameSite = Config.CookieSameSite;
// VSWhidbey 414687 Use HttpOnly to prevent client side script manipulation of cookie
cookie.HttpOnly = true;
return cookie;
}

Identity server 4 not removing cookie

I have front app on angular 5 and backend api on c# using identity server.
The problem is that when I click logout button, the token is removed and i am redirected to logout page.
But when I try to refresh main page, I am redirected to microsoftonline.com
authenticated automatically and redirected back to main page
I am missing providing username and password here, and this occurs in chrome incognito.
What I noticed is that if I remove manually the cookie from microsoftonline.com
and repeat the process, this time I will be asked for username and password.
So first I tried to clean all cookies this way but it din't help
foreach (var key in HttpContext.Request.Cookies.Keys)
{
HttpContext.Response.Cookies.Append(key, "", new CookieOptions() { Expires = DateTime.Now.AddDays(-1) });
}
bellow is my accountcontroller logout method and cookie screen
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutViewModel model)
{
var idp = User?.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
var subjectId = HttpContext.User.Identity.GetSubjectId();
if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider)
{
if (model.LogoutId == null)
{
model.LogoutId = await interaction.CreateLogoutContextAsync();
}
try
{
await signInManager.SignOutAsync();
}
catch (NotSupportedException)
{
}
}
// set this so UI rendering sees an anonymous user
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await interaction.GetLogoutContextAsync(model.LogoutId);
var vm = new LoggedOutViewModel
{
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = logout?.ClientId,
SignOutIframeUrl = logout?.SignOutIFrameUrl
};
await persistedGrantService.RemoveAllGrantsAsync(subjectId, "angular2client");
return View("LoggedOut", vm);
}
If I understand correctly you are federating to Microsoft from your IdentityServer4 service? If so when you sign out of your identity service you should also give the user the option to sign out of the external provider (if it supports the relevant feature - it'd need to define an end_session_endpoint in the discovery document).
This functionality is supported by the standard OIDC middleware so you should be able to initiate signout by calling SignoutAsync() and passing the name of the scheme for the MS federated sign in.
Another option is to always send prompt=login in your external sign in requests and then check the auth_time claim you get back. That way to you force interactive sign in always and also verify when it happened.
Try cleaning the cookies from the HttpContext itself, using the extension method, provided by Identity Server, like here.
Or try this:
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);
in your Logout controller method.
3rd option (what I have in one of my test MVC clients is):
public ActionResult Logout()
{
Request.GetOwinContext().Authentication.SignOut();
return Redirect("/");
}
public void SignoutCleanup(string sid)
{
var cp = (ClaimsPrincipal)User;
var sidClaim = cp.FindFirst("sid");
if (sidClaim != null && sidClaim.Value == sid)
{
Request.GetOwinContext().Authentication.SignOut("Cookies");
}
}
Where the Logout method is called on the button click, and the SignoutCleanup is the one that is passed to Identity Server, when registering the client as a Client.BackChannelLogoutUri (or Client.FrontChannelLogoutUri, or both, depending on your scenario).
PS: Now, in general I think that your approach is not right, but I don't know your full case, so I'm not judging you - just giving and advice.
For front-end clients (Angular, Vue, vanilla JS etc..) it is recommended to use the client-side oidc-client-js library. And here is the usage example. As I said - this is just an advice, but if you are in the very beginning of your authentication setup, I would recommend you to have a look.

Pass query string parameter through OpenId Connect authentication

Let me put the problem with a bit of structure.
Context
We have a web application build with Web Forms and hosted in an Azure Web App that authenticates the users against an Azure Active Directory using the OWIN + OpenId Connect standards.
The authentication process works like a charm and users are able to access the application without any problem.
So, whats the issue?
After struggling for many days with it I'm unable to pass any query string parameter to the application through the authentication process. For example, if I try to access the application for the first time through the URL: https://myapp.azurewebsites.net/Default.aspx?param=value. The reason I need to pass this parameter is that it triggers some specific actions in the main page.
The problem is that after the authentication redirects to the webapp's main page the original query string parameters of the request are gone.
The code
The startup class looks like this:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = Constants.ADTenant.ClientId,
Authority = Constants.ADTenant.Authority,
PostLogoutRedirectUri = Constants.ADTenant.PostLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = context =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(Constants.ADTenant.ClientId,
Constants.ADTenant.AppKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(
Constants.ADTenant.ObjectIdClaimType).Value;
AuthenticationContext authContext = new AuthenticationContext(Constants.ADTenant.Authority,
new NaiveSessionCache(userObjectID));
if (HttpContext.Current != null)
{
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential,
Constants.ADTenant.GraphResourceId);
AuthenticationHelper.token = result.AccessToken;
AuthenticationHelper.refreshToken = result.RefreshToken;
}
return Task.FromResult(0);
}
}
});
And it works properly!
What I already tried
I've got access to the original request Url by adding an overwrite of the RedirectToIdentityProvider notification:
RedirectToIdentityProvider = (context) =>
{
// Ensure the URI is picked up dynamically from the request;
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + context.Request.Uri.PathAndQuery;
context.ProtocolMessage.RedirectUri = appBaseUrl;
return Task.FromResult(0);
}
With this I tried to force the redirect to the main page including the original query string parameter, but then the redirection after authentication breaks and gets stuck in an infinite loop.
I've also tried with changing the redirect url of the application configuration in Azure AD without luck. Also tried to store the query string parameters somewhere else, but the Session is not accessible that early in the process.
Does anyone know what am I doing wrong? Or I'm just asking for something impossible? Any help would be appreciated.
Thank you very much in advance!
I recently had a need to do the exact same thing. My solution may not be the most sophisticated, but simple isn't always bad either.
I have two Authentication Filters...
The first filter is applied to all controllers that could potentially be hit with query string parameters prior to authorization. It checks if the principal is authenticated. If false it caches the complete url string in a cookie. If true it looks for any cookies present and clears them, just for cleanup.
public class AuthCheckActionFilter : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
if (!filterContext.Principal.Identity.IsAuthenticated)
{
HttpCookie cookie = new HttpCookie("OnAuthenticateAction");
cookie.Value = filterContext.HttpContext.Request.Url.OriginalString;
filterContext.HttpContext.Response.Cookies.Add(cookie);
}
else
{
if (filterContext.HttpContext.Request.Cookies.AllKeys.Contains("OnAuthenticateAction"))
{
HttpCookie cookie = filterContext.HttpContext.Request.Cookies["OnAuthenticateAction"];
cookie.Expires = DateTime.Now.AddDays(-1);
filterContext.HttpContext.Response.Cookies.Add(cookie);
}
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
}
}
The second filter is applied only to the default landing page, or in other words where the identity server is redirecting after login. This second filter looks for a cookie and if it exists it calls response.Redirect on cookie value.
public class AutoRedirectFilter : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
if(filterContext.Principal.Identity.IsAuthenticated)
{
if(filterContext.HttpContext.Request.Cookies.AllKeys.Contains("OnAuthenticateAction"))
{
HttpCookie cookie = filterContext.HttpContext.Request.Cookies["OnAuthenticateAction"];
filterContext.HttpContext.Response.Redirect(cookie.Value);
}
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
}
}

ASP.net MVC FormsAuthentication cookie missing

I'm writing an ASP.net MVC 5 application using FormsAuthentication. I had everything up and working properly using FormsAuthentication.SetAuthCookie(user.Email, model.RememberMe).
However, I wanted to create a custom ticket so I could store some extra information in the UserData field of the ticket. This is how I'm creating my ticket and storing it in a cookie:
var ticket = new FormsAuthenticationTicket(1, user.Email, DateTime.Now, DateTime.Now.AddMinutes(FormsAuthentication.Timeout.Minutes), model.RememberMe, user.AuthToken);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) { Domain = FormsAuthentication.CookieDomain, Path = FormsAuthentication.FormsCookiePath, HttpOnly = true, Secure = FormsAuthentication.RequireSSL };
HttpContext.Response.Cookies.Add(cookie);
This creates an encrypted ticket and sends it to the browser. I've verified with developer tools and Fiddler that the ticket is present in the browser and that it is sent back to the server on the subsequent requests.
But authentication is now broken. Also, the cookie is not available in Application_AuthenticateRequest or Application_PostAuthenticateRequest events. When I use the debugger to explore Context.Request.Cookies it is not present in the list.
Oddly enough the cookie does exist if I step back in the pipeline and check it in Application_BeginRequest:
void Application_BeginRequest(object sender, EventArgs e)
{
// Auth cookie exists in the collection here! Ticket decrypts successfully
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
return;
var encTicket = authCookie.Value;
var ticket = FormsAuthentication.Decrypt(encTicket);
}
void Application_AuthenticateRequest(object sender, EventArgs e)
{
// Auth cookie missing from the cookies collection here!
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
return;
var encTicket = authCookie.Value;
var ticket = FormsAuthentication.Decrypt(encTicket);
using (var db = new BadgerContext())
{
var user = db.Users.OfType<RegisteredUser>().FirstOrDefault(x => x.UserName == ticket.Name);
if (ticket.UserData != user.AuthToken)
{
FormsAuthentication.SignOut();
Response.Redirect(FormsAuthentication.DefaultUrl);
}
}
}
So it appears that something is stripping my custom FormsAuthenticationTicket out of the cookies after BeginRequest but before AuthenticateRequest. Unfortunately, this breaks authentication altogether on the site.
Any ideas what is causing this behavior when I create a custom ticket? Am I doing something wrong with my cookie creation?
Check in the .config file the inside the system.web node, the httpRuntime tag.
<httpRuntime targetFramework="4.5" />
as same as main web site
Rowan suggested I look at the value for FormsAuthentication.Timeout.Minutes. After investigation, this value always came back as 0. This led to an immediate expiration of the ticket. I had to use FormsAuthentication.Timeout.TotalMinutes instead and everything started working properly

Role Management in ASP MVC 5 (Microsoft.AspNet.Identity)

in ASP MVC5 RC I didn't get the role system to work.
My database has all needs tables an role exist but proofing if user is in role always return false (no SQL exception or something)!?
Did I need to activate role system for IPrincipal somewhere?
Test code:
AccountController accCont = new AccountController();
// check role exist : result = true
var roleExist = await accCont.IdentityManager.Roles.RoleExistsAsync("61c84919-72e2-4114-9520-83a3e5f09de1");
// try find role by name : result = role object
var role = await accCont.IdentityManager.Roles.FindRoleByNameAsync("ProjectAdministrator");
// check with AccountController instance : result = true
var exist = await accCont.IdentityManager.Roles.IsUserInRoleAsync(User.Identity.GetUserId(), role.Id);
// check if current user is in role : result (both) = false????
var inRole = User.IsInRole(role.Id);
var inRole2 = User.IsInRole(role.Name);
I also try to build an custom extenuation like the IIdentity.GetUserId() extension method from Microsoft.AspNet.Identity.Owin Namespace.
namespace Microsoft.AspNet.Identity
{
public static class IdentityExtensions
{
public static string IsUserInRole(this IIdentity identity)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
ClaimsIdentity identity2 = identity as ClaimsIdentity;
if (identity2 != null)
{
var result = identity2.FindFirstValue(IdentityConfig.Settings.GetAuthenticationOptions().RoleClaimType);
return null; // later result
}
return null;
}
}
}
But the result for claim Type RoleClaimType is always null :(
I'm really stuck with this.
Thank you for your help! Steffen
I'm trying to understand how to use roles in MVC 5 myself, which is what brought me here. I can't answer your question, but check out this link. The downloaded solution works right out of the box and I've already been able to cut-and-paste some of the code and get it working in my own app. Now I'm trying to fully understand what it's doing.
http://www.typecastexception.com/post/2013/11/11/Extending-Identity-Accounts-and-Implementing-Role-Based-Authentication-in-ASPNET-MVC-5.aspx
It may not answer your question but at least it's a fully working solution that actually does work as described without a lot of hassle, so it's a good starting point.
User.IsInRole is basically looking at the claims for the currently signed in user. What does your sign in logic look like? That is what is responsible for minting the cookie that turns into the User identity. That needs to have the Role claim set properly for the IsInRole method to work correctly.

Categories

Resources