Completely custom authentication in ASP.NET MVC: losing HttpContext.User - asp.net

I have my very own authentication system (https://bitbucket.org/anton_gogolev/octalforty-structural) which doesn't use any of the standard ASP.NET stuff (<authentication mode="None" />).
It uses plain IHttpModules to do its job: BeginRequest inspects incoming cookies and sets HttpContext.Current.User and Thread.CurrentPrincipal upon successful authentication
Thread.CurrentPrincipal = HttpContext.Current.User =
new GenericPrincipal(tokenIdentity,new string[] { });
whereas EndRequest issues all the required cookies for an authenticated user.
This has been working fine for months now, but on some systems (and I really cannot tell how are they different from ones this actually works on) ASP.NET seems to be losing the value of HttpContext.Current.User, replacing it with whatever default values there are (GenericPrincipal aggregating GenericIdentity with IsAuthenticated set to false, etc).
So the question is: how and why is HttpContext.Current.User getting lost?

It sounds like there is another module that is modifying HttpContext.Current.User after BeginRequest. I would recommend setting it instead in PostAuthenticateRequest.
I've had this problem before with ASP.NET enabling the RoleManager module. Adding the following to the system.web section of web.config fixed it.
<httpModules>
<remove name="RoleManager"/>
</httpModules>
Here's some amplifying information about what I did to fix this:
1) Figure out what other modules are running. Here's an article that provides some code for doing this.
2) Make sure you are setting HttpContext.Current.User in the correct place. BeginRequest isn't a good place to hook in. PostAuthenticateRequest is usually the best (and recommended). This won't prevent the problem if another module is also using PostAuthenticateRequest and it happens to be run after yours, but in many cases it will resolve the issue (use the web.config snippet above).
3) Selectively disable each installed module and test your application until your custom Principal object is not overwritten.

Related

How to set machineKey on Azure Website

I'm running an Azure Website. Whenever I deploy, everyone gets logged out because the machineKey changes.
I specified the machineKey in the web.config but this didn't solve the issue. I believe this is because Azure automatically overwrites the machineKey [1].
I've found a couple of similar questions here but the answers link to dead links.
So, what's the solution? Surely there's a way to keep users logged in regardless of deployments on Azure.
Try to reset the machine-key configuration section upon Application_Start:
protected void Application_Start()
{
// ...
var mksType = typeof(MachineKeySection);
var mksSection = ConfigurationManager.GetSection("system.web/machineKey") as MachineKeySection;
var resetMethod = mksType.GetMethod("Reset", BindingFlags.NonPublic | BindingFlags.Instance);
var newConfig = new MachineKeySection();
newConfig.ApplicationName = mksSection.ApplicationName;
newConfig.CompatibilityMode = mksSection.CompatibilityMode;
newConfig.DataProtectorType = mksSection.DataProtectorType;
newConfig.Validation = mksSection.Validation;
newConfig.ValidationKey = ConfigurationManager.AppSettings["MK_ValidationKey"];
newConfig.DecryptionKey = ConfigurationManager.AppSettings["MK_DecryptionKey"];
newConfig.Decryption = ConfigurationManager.AppSettings["MK_Decryption"]; // default: AES
newConfig.ValidationAlgorithm = ConfigurationManager.AppSettings["MK_ValidationAlgorithm"]; // default: SHA1
resetMethod.Invoke(mksSection, new object[] { newConfig });
}
The above assumes you set the appropriate values in the <appSettings> section:
<appSettings>
<add key="MK_ValidationKey" value="...08EB13BEC0E42B3F0F06B2C319B..." />
<add key="MK_DecryptionKey" value="...BB72FCE34A7B913DFC414E86BB5..." />
<add key="MK_Decryption" value="AES" />
<add key="MK_ValidationAlgorithm" value="SHA1" />
</appSettings>
But you can load your actual values from any configuration source you like.
If Azure is rewriting your machineKey, you can't do much about it, as it is part of their infrastructure. However, there are other methods.
Override FormsAuthentication
This should not be difficult as you can easily look up for source code of FormsAuthentication and create your own logic and replace MachineKey with your own key stored in web.config or in your database.
Custom Authentication Filter
The simplest way would be to create a filter and check, verify, encrypt decrypt cookies in your filter. You need to do this on OnAuthorization method and create new instance of IPrincipal and set IsAuthenticated to true if descryption was successful.
OAuth
Enable OAuth and create OAuthProvider. However you will need to host OAuthProvider on server that is in your control as that will need machineKey working.
Enable Third Party OAuth, if you enable OAuth with Google, Facebook etc, it will be easy as user will be redirected to OAuth provider and they will continue to login automatically and a new session will be established.
I had the same issue and in my case I was using the webdeploy to Azure wizard in VS13. I thought I was going crazy as I would set the machinekey in the web.config and then it would be changed on the deployed web.config to autogenerate. It is something in the webdeploy script/settings. My solution was to open the live azure site from within VS13 using the Server Explorer and then editing the web.config and saving changes. This preserved my settings with my supplied keys and all works fine.

Forms Authentication SSO - Why does User.Identity.IsAuthenticated == false

I'm trying to set up SSO on two separate IIS web sites (with a common domain) using Forms Authentication. I'm using the common approach of setting the auth cookie to the common domain and using matching machine key to enable decryption.
This works with no issues on test sites that I created. However, when trying to implement this in a legacy Web Forms site, I'm running into something that I don't understand.
When I log into one site, the test code on the second site has the following results:
var cookie = FormsAuthentication.GetAuthCookie("username", false); // works
var ft = FormsAuthentication.Decrypt(cookie.Value); // returns correct info
var isAuthentication = User.Identity.IsAuthenticated; // false
As an added bonus, whenever I sign in one site the other gets signed out (happens both ways.)
There must be something fundamental that I'm missing here.
Why is User.Identity.IsAuthenticated set to false, even though the FormsAuthentication ticket seems to be decrypting with no issues?
UPDATE: as pointed out below - FormsAuthentication.GetAuthCookie is not a valid way to obtain the existing auth cookie. Which brings me to this: I can see the top domain cookie in the browser, but it doesn't show up in the request. I suspect this is where the problem is occuring.
Solution: Both sites were not targeting the same version of .Net Framework as specified by the web.config:
<httpRuntime targetFramework="4.5" />
<compilation debug="true" targetFramework="4.5" />
Updating both sites to target the same framework fixed the problem.
The GetAuthCookie creates a new cookie:
http://msdn.microsoft.com/en-us/library/vstudio/3fay1e4k(v=vs.100).aspx
Creates an authentication cookie for a given user name. This does not set the cookie as part of the outgoing response, so that an application can have more control over how the cookie is issued.
No wonder it works, it doesn't look into the existing cookie.
My theory is that you have your new sites on an x64 machine and the legacy website sits in an x86. The encryption differs in such scenario even if keys are the same. Another possible reason is a different version of .net as the encryption algorithm has been changed in .net 4.

ASP.NET session has expired or could not be found -> Because the Session.SessionID changes (Reporting Services)

1.-I'm using reporting services and sometimes I get this error ASP.NET session has expired or could not be found when I try to load a report.
2.-I realized that I get this error when the Session.SessionID property changes even though the user is the same. If it does not change, the report is loaded. I mean, if I refresh the report a number of times, whenever the Session.SessionID is the same than the last one, the report is loaded.
3.-Microsoft Documentation says:
When using cookie-based session state, ASP.NET does not allocate
storage for session data until the Session object is used. As a
result, a new session ID is generated for each page request until the
session object is accessed. If your application requires a static
session ID for the entire session, you can either implement the
Session_Start method in the application's Global.asax file and store
data in the Session object to fix the session ID, or you can use code
in another part of your application to explicitly store data in the
Session object.
If your application uses cookieless session state, the
session ID is generated on the first page view and is maintained for
the entire session.
The point is that I can not use a cookieless session state because I need cookies.
What could I do to avoid this error? Or What could I do to avoid the Session.SessionID to change on every request?
You are probably storing your session InProcess. Try changing it to session state server. You can find more details here.
I'm using report viewer 11.0.0; in your web config on system.web section, put the next configuration:
<sessionState timeout ="120" mode="InProc" cookieless="false" />
When you are generating the report (C# code bellow) in the reportviewer object change the KeepSessionAlive property to false and the AsynkRendering property to false, and that's all
this.rvReporte.KeepSessionAlive = false;
this.rvReporte.AsyncRendering = false;
(rvReporte) is a ReportViewer control located on my asp.net Form
This solution work for me, i hope that work for other people.
Regards
<httpCookies httpOnlyCookies="false" requireSSL="false"/>
Solved the problem.
Thanks to :
http://www.c-sharpcorner.com/Blogs/8786/reportviewer-Asp-Net-session-has-expired.aspx
I had the same issue on report viewer page when the web site was accessed from outside intranet. hardvin's suggestion saved the day for me which is to set
this.rvReporte.KeepSessionAlive = false;
this.rvReporte.AsyncRendering = false;
I changed the property on the control itself. I am using report viewer on a user control which raises a custom event for supplying parameters programmatically at the host page instead of prompting the users.
I solved this issue by setting AsyncRendering to false on reportviewer server control
Having the reportviewer being displayed in iframe was giving us this error. If displayed outside of iframe it works nice.
The reportviewer object has this configuration, AsyncRendering = false and KeepSessionAlive = true.
The webapp that has the reportviewer and set the session cookie in the browser was compiled with .net framework 4.6.1. We upgrade to 4.8 and put this in web.config
<sessionState cookieSameSite="None" />
<httpCookies requireSSL="true"/>
Só the solution is from https://learn.microsoft.com/en-us/aspnet/samesite/system-web-samesite#:~:text=The%20updated%20standard%20is%20not%20backward%20compatible%20with,SameSite%3DStrict.%20See%20Supporting%20older%20browsers%20in%20this%20document.
The answer given by Alexsandar is just one of the solution to this problem.
This link clearly explains what is the root cause for this problem and possible solutions:
http://blogs.msdn.com/b/brianhartman/archive/2009/02/15/did-your-session-really-expire.aspx
In case of Brian, the way he has descrived the problem, if he had just a single IIS server, using a session object in his code would have solved the problem because in that case, the SessionID which is passed in the request from browser to the server will get mapped to a corresponding sessionID on the server and hence the session expiry message will not come.
Setting the mode may only work in case of a server cluster where Brian had multiple IIS servers handling the same request. In that case an out of process mode will help to retrieve the session object from the Session Store irrespective of the server hit.
So based on this observation, I would conclude that Brian's problem was not related to cookies but to a server cluster. The information provided by Brian in his question and the subsequent solution misled me and hence this clarification. Hope it helps anyone looking for a similar problem.
Thanks,
Vipul
I have added the below-mentioned line on the web config file and it is working fine for me.
<sessionState mode="InProc" cookieless="true" timeout="3000" />
<pages enableSessionState="false" />
<customErrors mode="Off" />
Try removing SessionState="somevalue" tag from the top of your calling ASPX page. I'm using a custom SessionState and refuse to use InProc since I have multiple instances on Azure. You can even use AsyncRendering=True if you desire. Let me know if this did the trick for you.
For me, it turned out to be having more than one worker process for the app pool.
For me Azure hosted web app turning ON - ARR Affinity fixed the issue.
ARR Affinity ON

Thread.CurrentPrincipal is not set in WCF service called using WebGet

I have a web site hosted in IIS that uses Windows Authentication and exposes WCF web services.
I configure this service with an endpoint behavior:
<serviceAuthorization principalPermissionMode ="UseAspNetRoles"
roleProviderName="MyRoleProvider"/>
and a binding:
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" />
</security>
When the service is called, Thread.CurrentPrincipal is set to a RolePrincipal with the client's Windows identity and roles provided by by configured provider.
All is well with the world.
Now I've added some additional WCF services that are consumed by REST-ful Ajax calls: Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" in the svc file, WebGet attribute in the service contract, and the AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed) attribute on the service implementation.
I also add the following incantation to web.config as recommended in MSDN:
<system.serviceModel>
...
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
...
</system.serviceModel>
My Ajax service almost works the way I want it to. When it's called, HttpContext.Current.User is set to a RolePrincipal with the roles I expect. But Thread.CurrentPrincipal remains set to an unauthenticated GenericPrincipal.
So I need to add a line of code to each of my service methods:
Thread.CurrentPrincipal = HttpContext.Current.User
Is there any incantation in the configuration file I can use to get Thread.CurrentPrincipal to be set automagically, like it is for a normal SOAP service?
UPDATE
Here's a blog from someone who had the same problem, and solved it by implementing custom behaviors. Surely there's a way to do this out of the box?
UPDATE 2
Coming back to add a bounty to this as it's bugging me again in a new project, using a WCF WebGet-enabled service on .NET 3.5.
I've experimented with a number of options, including setting principalPermissionMode="None", but nothing works. Here's what happens:
I navigate to a WebGet URL that calls my service: http://myserver/MyService.svc/...
I've put a breakpoint in Global.asax "Application_AuthorizeRequest". When this breakpoint is hit, both "HttpContext.Current.User" and "Thread.CurrentPrincipal" have been set to a "RolePrincipal" that uses my configured ASP.NET RoleProvider. This is the behavior I want.
I have a second breakpoint when my service's OperationContract method is called. When this breakpoint is hit, HttpContext.Current.User still references my RolePrincipal, but Thread.CurrentPrincipal has been changed to a GenericPrincipal. Aaargh.
I've seen suggestions to implement a custom IAuthorizationPolicy, and will look into that if I don't find a better solution, but why should I need to implement a custom policy to make use of existing ASP.NET authorization functionality? If I have principalPermissionMode = "UseAspNetRoles", surely WCF should know what I want?
This is an interesting question. I don't have the same setup as you, so its difficult to test whether my recommendations will apply exactly to your use case, but I can share what has worked for us with similar projects.
How we keep Thread.CurrentPrincipal and HttpContext.Current.User in Sync
We wrote an HttpModule called "AuthenticationModule" which inherits from IHtppModule.
We then attached to the HttpApplication.AuthenticateRequest event which happens very early in request lifecycle.
In our AuthenticateRequest event handler, we implement our application specific requirements including setting Thread.CurrentPrincipal and if necessary also the current context user. In this way you only implement this code once for your entire application and if it changes (like if you implement a custom Principal IIDentity) you have only one place to change it. (Don't duplicate this code in every service method.)
public class AuthenticationModule : IHttpModule
{
public void Dispose() { return; }
public void Init(HttpApplication app)
{
app.AuthenticateRequest += new EventHandler(app_AuthenticateRequest);
}
void app_AuthenticateRequest(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
// This is what you were asking for, but hey you
// could change this behavior easily.
Thread.CurrentPrincipal = app.Context.User;
}
}
Ours is actually a bit more complex as we implement a custom IIdentity, create an instance of GenericPrincipal and then assign it to both app.Context.User and Thread.CurrentPrincipal; but, the above is what you were asking for.
Don't forget to register your new HttpModule in your web.config!
For integrated app pools:
<system.webServer>
<modules>
<add name="AuthenticationModule" type="YourNameSpace.AuthenticationModule" preCondition="integratedMode" />
</modules>
</system.webServer>
For old classic app pools you'd have to put it in <system.web><httpModules></httpModules></system.web>
You might need to play with what goes inside that AuthenticationRequest event handler and/or the order with which you register the handler. Because ours is totally custom it might be different than what you need. We actually grab the Forms Authentication cookie, decrypt it, etc... you might need to ping some built in methods for WindowsAuthentication.
I believe this is a more generic way to handle your application authentication stuff as it applies to all HttpRequests whether that be a page request, an IHttpHandler, some 3rd party component, etc... That will keep it consistent throughout your app.
I'm not sure. Maybe this will help
<configuration>
<system.web>
<identity impersonate="true" />
</system.web>
</configuration>
http://msdn.microsoft.com/en-us/library/134ec8tc(v=vs.80).aspx

How do I Resolve an application URL from a background thread in ASP.NET MVC?

The app splits off into two threads; the main web app and a secondary thread used for asynchronous event handling. The secondary thread receives an event where it needs to send an email with a fully qualified URL of the main app (plus additional route arguments) inside it.
Eg. http://Server.com/App/RouteData?AdditionalArguments
Of course the background thread does not have the luxury of using HttpContext.Current to resolve a Url, because there's no request. No HttpRequest, no HttpContext...
I discovered that most of the methods ASP.NET (even with MVC) uses to build URLs rely on HttpContext. Does there exist a way to build a fully qualified application URL in ASP.NET without using HttpContext or any of it's derivatives?
I'm looking for a thread-safe method like:
UrlHelper.GetApplicationtUrl()
Any Ideas? Your suggestions are much appreciated.
I had this exact problem. I ended up storing the url in the web.config file. I did mine like so:
<appSettings>
<!-- Urls -->
<add key="AppDomain" value="http://localhost:1273/" />
<add key="ConfirmUrl" value="http://localhost:1273/Auth/Confirm/?code={0}" />
</appSettings>
and called it like this in the service layer:
string confirmUrl = string.Format(ConfigurationManager.AppSettings["ConfirmUrl"], confirmCode);
If you can't just use the configuration file, when creating the Thread, use the ThreadStart delegate to provide the base information you need to the new thread.

Resources