Is there a way to clear server side OWIN / authentication data? - c#

After a recent security scan, the info sec team said they do not like the fact they can save the .AspNet.ApplicationCookie value, and use it again afterwards allowing the user access to the site.
After reading around, I understand this is standard behaviour but I have to find a way of completely killing a session upon signing the user out.
My understanding is a little thin here so my searching is bringing up very little. Is there a way of going about this?

A late reply, but for those that come across this:
We handled this by adding a custom attribute that validates a thumbprint. We use the attribute for any page behind a login.
The following is a rough example of how this is achieved.
The thumbprint is created at sign in and added to cache:
private void OwinSignIn(tblUser user)
{
var thumbPrint = Guid.NewGuid();
var claims = new List<Claim>
{
....
new Claim(ClaimTypes.Thumbprint, thumbPrint.ToString())
};
MemoryCache.Default.Set(thumbPrint.ToString(), true, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(60) });
}
The attribute then looks for this thumbprint and acts accordingly:
public class ValidateThumbprint : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
var identity = (ClaimsIdentity)filterContext.HttpContext.User.Identity;
var thumbPrint = identity.Claims?.Where(s => s.Type == ClaimTypes.Thumbprint).First().Value;
if (thumbPrint != null)
{
if (MemoryCache.Default.Contains(thumbPrint))
{
return;
}
}
// handle invalid thumbprint
}
I am not sure if this is the best way and most secure way, but it does prevent saving and reusing the cookie after logging out.

Related

SignalR .Net-Framework how to implement Authentication

since I am rather new to SignalR and couldn't really find anything online, I want to ask my questions here.
I use SignalR with C# and .Net-Framework and want to Implement a function in which i can login with Username and Password to the Host/Hubs with specific Roles, but I couldn't really find anything helpfull in this regard at the Microsoft Docs.
(https://learn.microsoft.com/en-us/aspnet/signalr/overview/security/hub-authorization)
So my Question is:
How can I implement Authentication on my SignalR-Host and is it possible to reject a connection if a the Username and Password which is send to the Host isn't correct?
Thanks for the Help
DerDane
I use an Attribute for that:
public class AuthorizeRolesAttribute : AuthorizeAttribute
{
public AuthorizeRolesAttribute(params string[] roles)
{
this.Roles = string.Join(",", roles);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
// Make sure the user is authenticated.
var roles = this.Roles.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var role in roles)
{
if (httpContext.User.IsInRole(role))
{
return true;
}
}
return false;
}
}
Usage:
[AuthorizeRoles("Admin")]
public class ExampleHub...
UPDATE
Authentication is not a trivial topic. If you want to dive deeper into it, take a look at https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-6.0 to understand how to authenticate a user. Then, at https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-6.0 you can see how roles are associated with a previously authenticated identity.
Generally, you will never do this manually. You use existing frameworks, which are already proven.
In httpContext.User.Identity you have access to your identity, the logged in user.
I think it can be very interesting for you to understand the pipeline of ASP .NET. Search for "asp net pipeline diagram" and also check this
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0 for more information about the middleware.
There is a lot going on behind the scenes. You can just use them but it's always interesting to know them to a greater or lesser extent.
You define the roles. If your application is to manage a restaurant, you can create roles such as "waiter", "cook"... while in an educational center they can be "teacher", "student", "director", etc.
You can get a list of roles like this:
var roleStore = new RoleStore<IdentityRole>(context);
var roleMngr = new RoleManager<IdentityRole>(roleStore);
var roles = roleMngr.Roles.ToList();
To add a role to user, you can use UserManager.AddToRole.

ASP.NET Core Invoke an Authentication

My Problem: Find a way to Authorize a User if need be with BasicAuth if it is set in the DB for the specific Controller/Action, before JWT Authentication is used.
I am searching for a way to call two different AuthenticationHandlers from a custom AuthenticationHandler, is this even possible or maybe I am approaching the problem wrong?
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
//Get the Controller/Action from this.OriginalPath
//Ask the DB if the Controller/Action should be authorize using
//BasicAuth first or just use JWT
//Call the Basic or JWT Handler
}
This is the best Solution I found so far:
if (AuthType.StartsWith("Basic"))
{
var handlers = Context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
var handler = handlers.GetHandlerAsync(Context, BasicAuthenticationDefaults.AuthenticationScheme).Result;
var res = handler.AuthenticateAsync().Result;
Debug.WriteLine(res.Succeeded);
if (res.Succeeded)
return Task.FromResult(res);
}
else if(AuthType.StartsWith("Bearer"))
{
var handlers = Context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
var handler = handlers.GetHandlerAsync(Context, JwtBearerDefaults.AuthenticationScheme).Result;
var res = handler.AuthenticateAsync().Result;
Debug.WriteLine(res.Succeeded);
if (res.Succeeded)
return Task.FromResult(res);
}
It is not working with WindowsAuthentication. I only get an Error saying:
No authentication handler is configured to authenticate for the scheme: Windows
But I added IISDefaults
services.AddAuthentication(IISDefaults.AuthenticationScheme)
Pls feel free to comment or Edit.

ASP.NET Identity 2 and Anonymous Users

In our developing e-commerce solution we are using AspNet Identity 2.2.1 and it is required that any guest (anonymous) users should complete checkout without prior registration to the website. In order to fullfill this requirement have written an ActionFilter named UserMigrationAttribute which obtains SessionTrackId (string GUID) from cookie -which we set from a HttpModule for every request if SessionTrackId is not found along with request cookies- and creates and actual IdentityUser in database with the username something like SessionTrackId#mydomain.com.
We have decorated our BaseController class with this UserMigration attribute in order to utilize its functions throughout the site.
Everything up to this point works as expected with single downside issue, which is when the page is being loaded for the first time for any user, if we try to make an Jquery Ajax Call to a Method which have [ValidateAntiForgeryToken] attribute, the call fails with the 'The provided anti-forgery token was meant for a different claims-based user than the current user.' error, even though we are sending __RequestVerificationToken parameter with every ajax call.
But if user opens another page by clicking link and/or reloads/refreshes current page, all the subsequent ajax calls complete successfully.
In our understanding UserMigrationAttribute creates user on OnActionExecuting method, but after we signIn user in the process #Html.AntiForgeryToken() is not being updated with the right values.
You may find the UserMigrationAttribute code below;
[AttributeUsage(AttributeTargets.Class)]
public class UserMigrationAttribute : ActionFilterAttribute
{
public ApplicationSignInManager SignInManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
public UserManager UserManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().GetUserManager<UserManager>();
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CreateMigrateCurrentUser(filterContext);
base.OnActionExecuting(filterContext);
}
private static readonly object LockThis = new object();
private void CreateMigrateCurrentUser(ActionExecutingContext filterContext)
{
lock (LockThis)
{
var signInManager = SignInManager(filterContext);
var userManager = UserManager(filterContext);
var sessionTrackId = GetSessionTrackId(filterContext);
if (!filterContext.HttpContext.Request.IsAuthenticated)
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user == null)
{
user = new User() {UserName = username, Email = username};
var result = userManager.Create(user);
userManager.AddToRole(user.Id, StringResources.AnonymousVisitorsGroup);
}
signInManager.SignIn(user, true, true);
}
}
else
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user != null)
{
if (!HttpContext.Current.User.IsInRole(StringResources.AnonymousVisitorsGroup))
{
var targetUserId = HttpContext.Current.User.Identity.GetUserId<int>();
var service = new Service();
service.Users.MigrateUser(user.Id, targetUserId);
}
}
}
}
}
}
private string GetSessionTrackId(ActionExecutingContext filterContext)
{
var retVal = string.Empty;
if (filterContext.HttpContext.Request.Cookies["stid"] != null)
{
retVal = filterContext.HttpContext.Request.Cookies["stid"].Value;
}
return retVal;
}
}
Any help or suggestions are highly appreciated.
Thank you,
This is happening because the anti-forgery token is set in a cookie, which will not be updated until the next request. If you're manually signing a user in, you should also issue a redirect (even if to the same page they were already headed to), simply to ensure that the cookie data is correct. This normally happens naturally, as the sign in form will redirect to the URL that needed authorization after the user is signed in, thus negating the problem. Since you're not redirecting currently, the data is out of sync.
However, I have to say that this seems like a very poor solution to this particular use case. Creating some sort of temporary-type user and signing that user in to handle guest checkout creates an unnecessary glut of useless data in your database, at best, and leads to bugs and other issues like this one you're experiencing, at worst.
I also run an ecommerce site, and the way we handled guest checkout is incredibly simplistic. The checkout data is just stored in the session (email, shipping/billing address, etc.). We build a view model to handle the actual checkout where the data necessary for submitting the sale comes either from the user object, if they're logged in, or these session variables, if they aren't. If the user is neither logged in, nor has the requisite session variables set, then they are redirected to the onboarding form where billing/shipping, etc. is collected.
For other aspects like maintaining an anonymous cart, we use a permanent cookie with the cart identifier. If the user ends up creating an account, we associate the anonymous cart with their user, and then remove the cookie. This ensures that their cart survives past the session timeout and things like closing the browser, even if they're anonymous.
In other words, in all these things, no user object is actually needed. If it's there (user is logged in), great, we'll use it. Otherwise, we collect and persist the requisite information for checkout via other means.

Web API Basic Auth inside an MVC app with Identity Auth

So I have a C# MVC app using Identity for its authentication. I now have a need to expose a few things via Web API to some of my clients. Instead of building a separate app, project, deployment... I've simply added an API Controller to my existing project. To keep things simple for my clients, I've decided to use Basic Auth, opting rather to force my clients into using SSL connections to my API.
I've followed this very useful tutorial to implement the Basic Auth in my API:
http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/
Problem is, that their instructions take over Auth for the entire app...
I need my MVC app to keep using the Identity Auth that it is currently using and hopefully roll my own custom attribute (like [APIAuthorize]) so that it only applies to my API Controller.
I can probably hack around and try to get this to work, but as this is concerning security, I decided to ask for some pro help on how to best implement this. Specifically, I need to know 1) what do I do in my Global.asax (if anything) as the above URL suggests I do this:
protected void Application_Start()
{
GlobalConfiguration.Configuration.MessageHandlers
.Add(new BasicAuthMessageHandler(){
PrincipalProvider = new DummyPrincipalProvider()
});
//...
}
But again, this would take over the Authentication to the entire app... 2) What do I need to do in my custom auth attribute to make all of this work seamlessly.
And of course, if there's a better way to do all of this (without creating a separate app or increasing the implementation difficulty to my clients) then I'm all ears.
I us a filter attribute to adorn the actions i wanted to expose to Simple Auth. I cant remember where i got this code from (probably stackoverflow i just don't have the link so i cant claim credit for it)
public class BasicHttpAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (Thread.CurrentPrincipal.Identity.Name.Length == 0)
{
// Get the header value
AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
// ensure its schema is correct
if (auth != null && string.Compare(auth.Scheme, "Basic", StringComparison.OrdinalIgnoreCase) == 0)
{
// get the credientials
string credentials = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(auth.Parameter));
int separatorIndex = credentials.IndexOf(':');
if (separatorIndex >= 0)
{
// get user and password
string passedUserName = credentials.Substring(0, separatorIndex);
string passedPassword = credentials.Substring(separatorIndex + 1);
SimpleAES crypto = new SimpleAES();
string userName = crypto.DecryptString(ConfigurationManager.AppSettings.Get(Constants.SIMPLEUSERNAME));
string password = crypto.DecryptString(ConfigurationManager.AppSettings.Get(Constants.SIMPLEUSERPASSWORD));
// validate
if (passedUserName == userName && passedPassword == password)
{
Thread.CurrentPrincipal = actionContext.ControllerContext.RequestContext.Principal = new GenericPrincipal(new GenericIdentity(userName, "Basic"), new string[] { });
}
}
}
}
return base.IsAuthorized(actionContext);
}
}
Then i use it as so
[BasicHttpAuthorize]
public HttpResponseMessage MyExposedSimpleAuthAction()

Log in to SimpleMembership app using external SAML identity provider

I was tasked with adding logging via external service (using SAML 2.0) to an MVC app (.Net 4.5) that uses SimpleMembership. To be honest I'm not even sure where to start. From what I found on the internet there are few points to the problem. Most of the materials I found dealt with communication with the SAML identity provider (frequently written from scratch). However before I can reach that point I need to make sure I can actually integrate it with the SimpleMembership which we are using.
I suspect for starters I would need something like SAMLWebSecurity (akin to OAuthWebSecurity which we also use). I have found no such thing* on the internet which makes me believe it does not exist (though I wouldn't mind being wrong here). This makes me believe I would have to write it myself, but can I do that without have to write my own membership provider?
*I'm not sure what would be a correct way to call this static class.
I'd recommend that you upgrade to ASP.NET Identity and the OWIN Based authentication middleware. Then you can use Kentor.AuthServices middleware that works with ASP.NET Identity (except that the XSRF-guard has to be commented out until bug #127 has been resolved).
You could also use the SAML classes from Kentor.AuthServices if you have to stick with SimpleMembership, so that you don't have to implement SAML from scratch.
Disclaimer: I'm the author of Kentor.AuthServices, but since it's open source, I'm not making money on people using it.
After discussing it with a colleague I think I figured out the course of actions. Both OAuthWebSecurity and WebSecurity appear to be a part of SimpleMembership, so what I wrote in the question would indicate I want to write a custom membership or reverse engineer SimpleMembership to copy OAuthWebSecurity (which doesn't sound like a fun activity to have).
My best bet here is hijacking the OAuthWebSecurity, by writing a custom client (one which implements the IAuthenticationClient interface). Normally one registers various OAuth clients using OAuthWebSecurity's built in methods (like RegisterFacebookClient). But it is also possible to register those clients using OAuthWebSecurity.RegisterClient which accepts IAuthenticationClient. This way I should be able to add this SAML login without writing a custom membership provider and keep using SimpleMembership.
I managed to do this. Thankfully the identity provider wasn't extremely complicated so all I had to do was redirect to a certain address (I didn't even need to request assertion). After a successful login, the IDP "redirects" the user using POST to my site with the base64 encoded SAMLResponse attached. So all I had to do was to parse and validate the response. I placed the code for this in my custom client (implementing IAuthenticationClient interface).
public class mySAMLClient : IAuthenticationClient
{
// I store the IDP certificate in App_Data
// This can by actually skipped. See VerifyAuthentication for more details
private static X509Certificate2 certificate = null;
private X509Certificate2 Certificate
{
get
{
if (certificate == null)
{
certificate = new X509Certificate2(Path.Combine(HttpContext.Current.ApplicationInstance.Server.MapPath("~/App_Data"), "idp.cer"));
}
return certificate;
}
}
private string providerName;
public string ProviderName
{
get
{
return providerName;
}
}
public mySAMLClient()
{
// This probably should be provided as a parameter for the constructor, but in my case this is enough
providerName = "mySAML";
}
public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
// Normally you would need to request assertion here, but in my case redirecting to certain address was enough
context.Response.Redirect("IDP login address");
}
public AuthenticationResult VerifyAuthentication(HttpContextBase context)
{
// For one reason or another I had to redirect my SAML callback (POST) to my OAUTH callback (GET)
// Since I needed to retain the POST data, I temporarily copied it to session
var response = context.Session["SAMLResponse"].ToString();
context.Session.Remove("SAMLResponse");
if (response == null)
{
throw new Exception("Missing SAML response!");
}
// Decode the response
response = Encoding.UTF8.GetString(Convert.FromBase64String(response));
// Parse the response
var assertion = new XmlDocument { PreserveWhitespace = true };
assertion.LoadXml(response);
//Validating signature based on: http://stackoverflow.com/a/6139044
// adding namespaces
var ns = new XmlNamespaceManager(assertion.NameTable);
ns.AddNamespace("samlp", #"urn:oasis:names:tc:SAML:2.0:protocol");
ns.AddNamespace("saml", #"urn:oasis:names:tc:SAML:2.0:assertion");
ns.AddNamespace("ds", #"http://www.w3.org/2000/09/xmldsig#");
// extracting necessary nodes
var responseNode = assertion.SelectSingleNode("/samlp:Response", ns);
var assertionNode = responseNode.SelectSingleNode("saml:Assertion", ns);
var signNode = responseNode.SelectSingleNode("ds:Signature", ns);
// loading the signature node
var signedXml = new SignedXml(assertion.DocumentElement);
signedXml.LoadXml(signNode as XmlElement);
// You can extract the certificate from the response, but then you would have to check if the issuer is correct
// Here we only check if the signature is valid. Since I have a copy of the certificate, I know who the issuer is
// So if the signature is valid I then it was sent from the right place (probably).
//var certificateNode = signNode.SelectSingleNode(".//ds:X509Certificate", ns);
//var Certificate = new X509Certificate2(System.Text.Encoding.UTF8.GetBytes(certificateNode.InnerText));
// checking signature
bool isSigned = signedXml.CheckSignature(Certificate, true);
if (!isSigned)
{
throw new Exception("Certificate and signature mismatch!");
}
// If you extracted the signature, you would check the issuer here
// Here is the validation of the response
// Some of this might be unnecessary in your case, or might not be enough (especially if you plan to use SAML for more than just SSO)
var statusNode = responseNode.SelectSingleNode("samlp:Status/samlp:StatusCode", ns);
if (statusNode.Attributes["Value"].Value != "urn:oasis:names:tc:SAML:2.0:status:Success")
{
throw new Exception("Incorrect status code!");
}
var conditionsNode = assertionNode.SelectSingleNode("saml:Conditions", ns);
var audienceNode = conditionsNode.SelectSingleNode("//saml:Audience", ns);
if (audienceNode.InnerText != "Name of your app on the IDP")
{
throw new Exception("Incorrect audience!");
}
var startDate = XmlConvert.ToDateTime(conditionsNode.Attributes["NotBefore"].Value, XmlDateTimeSerializationMode.Utc);
var endDate = XmlConvert.ToDateTime(conditionsNode.Attributes["NotOnOrAfter"].Value, XmlDateTimeSerializationMode.Utc);
if (DateTime.UtcNow < startDate || DateTime.UtcNow > endDate)
{
throw new Exception("Conditions are not met!");
}
var fields = new Dictionary<string, string>();
var userId = assertionNode.SelectSingleNode("//saml:NameID", ns).InnerText;
var userName = assertionNode.SelectSingleNode("//saml:Attribute[#Name=\"urn:oid:1.2.840.113549.1.9.1\"]/saml:AttributeValue", ns).InnerText;
// you can also extract some of the other fields in similar fashion
var result = new AuthenticationResult(true, ProviderName, userId, userName, fields);
return result;
}
}
Then I just registered my client in App_Start\AuthConfig.cs using OAuthWebSecurity.RegisterClient and then I could reuse my existing external login code (which was originally made for OAUTH). For various reasons my SAML callback was a different action than my OAUTH callback. The code for this action was more or less this:
[AllowAnonymous]
public ActionResult Saml(string returnUrl)
{
Session["SAMLResponse"] = Request.Form["SAMLResponse"];
return Redirect(Url.Action("ExternalLoginCallback") + "?__provider__=mySAML");
}
Additionally OAuthWebSecurity.VerifyAuthentication didn't work with my client too well, so I had to conditionally run my own verification in the OAUTH callback.
AuthenticationResult result = null;
if (Request.QueryString["__provider__"] == "mySAML")
{
result = new mySAMLClient().VerifyAuthentication(HttpContext);
}
else
{
// use OAuthWebSecurity.VerifyAuthentication
}
This probably all looks very weird and might differ greatly in case of your IDP, but thanks to this I was able to reuse most of the existing code for handling external accounts.

Categories

Resources