I have an ASP.NET application that sends an authentication cookie to an ASP.NET MVC application, used as an back office application.
I've added a global filter that checks every controller action for the authentication cookie. If the cookie exists, it allows the user to enter the page.
The code looks like this:
public class SecurityFilter : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// TODO: For some reason .AUTHCookie cookie isn't exist in request context of filter,
HttpCookie cookie = filterContext.RequestContext.HttpContext.Request.Cookies[".AUTHCookie "];
if (cookie != null) {
From the other side I can see the cookie sent from the ASP.NET application in Application_BeginRequest event in the Global.asax file.
Where and why the cookie disappeared? In what part of the MVC Request-Handling pipeline was the cookie thrown away?
protected void Application_BeginRequest(object sender, EventArgs e)
{
var cookies = HttpContext.Current.Request.Cookies;
// HERE I CAN SEE BOTH cookies. In filter action only one cookie was found. The authentication cookie is thrown somewhere ...
}
I found a solution for my scenario. I've added a compatibilityMode="Framework45" into the machinekey in both applications and it's all working perfectly.
Note: If one of your applications is using an older versions of the .NET framework, you must explicitly configure your .NET 4.5 apps to use the earlier machine compatibility modes, or they will not be able to encrypt/decrypt the forms authentication ticket.
Just to remind you my scenario:
WebForms ASP.NET 4.5
<machineKey compatibilityMode="Framework45" decryption="AES" validation="SHA1" decryptionKey="your_key1" validationKey="your_keu2" />
<authentication mode="Forms">
<forms name="_authcookie" domain=".domain.com" loginUrl="Default.aspx?View=1" defaultUrl="Default.aspx?View=1" timeout="30" path="/" protection="All" slidingExpiration="true" enableCrossAppRedirects="true" />
</authentication>
MVC 4
<machineKey compatibilityMode="Framework45" decryption="AES" validation="SHA1" decryptionKey="your_key1" validationKey="your_keu2" />
<authentication mode="Forms">
<forms name="_authcookie" domain=".domain.com" defaultUrl="~/" timeout="30" path="/" protection="All" slidingExpiration="true" enableCrossAppRedirects="true" />
</authentication>
Possible values for the compatibility mode:
http://msdn.microsoft.com/en-us/library/system.web.configuration.machinekeysection.compatibilitymode.aspx
Related
The issue I have is we currently are using IdentityServer as our SSO authentication for our corporate applications. However, the bulk of our applications are under the same Site ID in IIS 7.5. When navigating to more than 5 of these applications under the same Site ID, you end up getting a 400 error, request header too long. The reason being each application has its own cookie, so the request header is passing around 5+ cookies with token information and the becoming too large.
My question is, are you able to prevent the sharing of cookies between applications under the same Site ID in IIS 7.5?
We also have IdentityServer for SSO and internal applications hosted on the same machine on IIS.
And I faced with the same problem too.
Here is a solution:
1) You need to solve Owin/Katana middleware problem to avoid nonce overfloating. Here you can find the code for that fix
2) You have to stop sharing cookies.
So if your base address for applications is "mysite.com".
And you have a lot of different applications like this:
Good App: mysite.com/good_app/
Best App: mysite.com/best_app/
Super App: mysite.com/super_app/
Use CookiePath for each application on an application's side and it will limit cookies (and look here too).
Use the code like this (for "Good App"):
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieName = "GoodAppCookies",
// Cookie Path same as application name on IIS
CookiePath = "/good_app
};
Hope it'll help.
Few things that you can try. Make the following changes at the server level.
Highlight the server name in IIS, select "configuration editor", select "system.web" and "httpRuntime" and change "maxRequestLength" to "1048576".
You can also edit the "applicationHost.config" file in the following way- C:\Windows\System32\inetsrv\Config
<configuration>
<system.web>
<httpRuntime maxRequestLength="1048576" />
</system.web>
</configuration>
Edit "Request Filtering" settings at server level on IIS and set "maxAllowedContentLength" to "1073741824"
You can also edit the root web.config file in the following manner - C:\Windows\Microsoft.NET\Framework64*\v4.0.30319*\Config
*Folder is based on your application. if its a 32 bit application, navigate to "Framework" folder. If its a .net 2.0 application, navigate to v2.0.50727.
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="1073741824" />
</requestFiltering>
</security>
</system.webServer>
First of all - I want to say that I have not tried this myself, so I can't assure that it is a solution, but I'm trying to help.
The problem with the cookies originates from the Microsoft OWIN/Katana and the way they are encrypting them. They become enormous, but this has nothing to do with Identity Server. However here and here there are good discussion around this.
The main thing to try first is in the Startup.cs of the IdentityServer project, in the IdentityServerOptions.AuthenticationOptions there is a property SignInMessageThreshold which defaults to 5. Try setting it to something lower, this will keep your header smaller (which may cause round trips to identity server when an app doesn't have its message in the cookies, but this will not force the user to re-login).
Another thing, that we achieved in one of out projects, is to create a DataBase backed cookie session handler. In your clients, where you use
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieName = cookieName,
});
There is also a property SessionStore. You can have a custom implementation of the Microsoft.Owin.Security.Cookies.IAuthenticationSessionStore. In our case this reduced the cookie size to less than (or around) 300.
I've got an Azure Web App using ELMAH to log unhandled exceptions.
When I first deployed it, the web.config had the full SMTP setup defined in it, and ELMAH emailed exceptions:
<system.net>
<mailSettings>
<smtp from="me#mydomain.com">
<network host="smtp.mailprovider.com"
port="123"
userName="myUserName"
password="p#ssw0rd" />
</stmp>
</mailSettings>
</system.net>
The username and password have since been removed from the web.config, and they're now stored as application settings, configured through the Azure Portal.
Most of the emails I send still work fine, as the email code can access these application settings and use them when instantiating the SmtpClient, e.g.:
var userName = WebConfigurationManager.AppSettings["smtp.userName"];
var password = WebConfigurationManager.AppSettings["smtp.password"];
var credentials = new NetworkCredential(userName, password);
using (var smtpClient = new SmtpClient { Credentials = credentials })
{
await smtpClient.SendMailAsync(mailMessage);
}
What's the best way to get ELMAH to use the credentials stored in the application settings?
Options I can see:
There is a page on the wiki explaining how to use ELMAH's ErrorTweetModule to do an HTTP form post with the error details to any URL. The controller receiving the post could then use the stored credentials to email the details on.
The WebBase has a link to an article suggesting you can send emails directly to the recipient's SMTP server without authentication, but it says this may not work if you have DomainKeys set up, which I do.
This answer links to an article about intercepting the Mailing event, to customise the message.
I ended up creating a custom version of Elmah's ErrorMailModule, derived from the standard one, but overriding the SendMail method, based on some advice from Atif Aziz in a discussion on Google Groups.
The only changes required were to create the new module, and switch the Web.Config to use the custom module instead of the standard one.
Module
using System;
using System.Net.Mail;
namespace Test
{
public class ErrorMailModule : Elmah.ErrorMailModule
{
protected override void SendMail(MailMessage mail)
{
if (mail == null) throw new ArgumentNullException(nameof(mail));
// do what you want with the mail
// (in my case this fires up the email service, which
// gets the credentials from the Azure settings)
}
}
}
Web Config Changes
All that's required is to change the two occurrences of Elmah.ErrorLogModule, Elmah to your own module, in this case Test.ErrorMailModule.
So, instead of this...
<system.web>
<httpModules>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
</httpModules>
</system.web>
<system.webServer>
<modules>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
</modules>
</system.webServer>
...you should now have this:
<system.web>
<httpModules>
<add name="ErrorMail" type="Test.ErrorMailModule" />
</httpModules>
</system.web>
<system.webServer>
<modules>
<add name="ErrorMail" type="Test.ErrorMailModule" preCondition="managedHandler" />
</modules>
</system.webServer>
You will still need the errorMail section, as Elmah is still responsible for creating the email. Mine looks like this:
<elmah>
<errorMail from="user#domain.com" to="user#domain.com" subject="Custom Email Module"/>
</elmah>
Creating a HTTP request could work, but that should be the solution if everything else doesn't work IMO. Intercepting the Mailing event doesn't work, since you do not have access to the SmtpClient with the credentials in that event.
I've looked at different ways to update the SMTP settings from code. At first I though that I could just get a reference to the smtp section and update the properties, since they all have setter. But the code throw a configuration exception on runtime.
From what I can find, the only way to update the username and password in smtp section, is to read the web.config, update it and write the new version. Here's an example of writing updates to web.config:
var configuration = WebConfigurationManager.OpenWebConfiguration("~");
var section = configuration.GetSection("system.net/mailSettings/smtp") as SmtpSection;
section.Network.UserName = ConfigurationManager.AppSettings["myusername"];
section.Network.Password = ConfigurationManager.AppSettings["mypassword"];
configuration.Save();
The code actually updates the web.config. The code can be run at startup, but that would modify your web.config file locally as well. Another approach would be to run the code as part of a post deployment task with Azure.
I'm trying to implement an application that uses the same Token Based Authentication mechanism demonstrated in this really awesome example by Taiseer Joudeh.
In my application I kept encountering Cors problems. In some configurations I would get a 500 error on the Preflight (OPTIONS) request for the POST to get the token or I could get the token but then get a 404 error on the preflight request for the GET request to the actual API call with the Bearer token.
One difference was that Taiseer's code was setup to host in IISExpress (or Azure) and mine is hosted on Local IIS (running on Windows 7 at the moment).
On a hunch I tried hosting his API under Local IIS and I found the exact same problem. (500 error on the preflight request for the token and it looks like the actual API will work properly)
From what I've been reading it seems like this may be some conflict between the modules and handlers in IIS and the Cors implementation in WebApi but Taiseer's implementation works when hosted in Azure so perhaps it is a difference in the version of IIS (I'm currently running under Windows 7).
How can I sort out what is causing the problem?
The root of the problem
The Token action is not hosted in a controller but is instead built in somewhere in the lower level plumbing. The only access to the mechanism is through the override method GrantResourceOwnerCredentials() in the class that extends OAuthAuthorizationServerProvider. (In our case is ApplicationOAuthProvider.cs).
GrantResourceOwnerCredentials() does have the context available but it is not called as part of the PreFlight request so you have no way to insert the appropriate PreFlight response headers for CORS.
The solution
We eventually settled on the following solution. I'm not a big fan of it because it forces these headers into every response but at least it works.
The solution was to override Application_PreSendRequestHeaders() method in Global.asax to insert the appropriate headers.
Global.asax.cs
void Application_PreSendRequestHeaders(Object sender, EventArgs e)
{
var origin = Request.Headers.Get("Origin");
var validOrigins = ConfigurationManager.AppSettings["allowedCorsOrigins"].Split(',');
if(validOrigins.Any(o => o == origin))
{
Response.Headers.Set("Access-Control-Allow-Origin", origin);
Response.Headers.Set("Access-Control-Allow-Credentials", "true");
Response.Headers.Set("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, withcredentials, Prefer");
Response.Headers.Set("Access-Control-Expose-Headers", "Claims, *");
Response.Headers.Set("Access-Control-Max-Age", "600");
Response.Headers.Set("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");
}
}
This requires the following web.config entries:
web.config
<configuration>
<appSettings>
<add key="allowedCorsOrigins" value="http://www.allowedsite1.net,http://localhost:22687" />
<add key="allowedCorsMethods" value="get, post, put, delete, options, batch" />
<add key="allowedCorsHeaders" value="*" />
</appSettings>
...
</configuration>
The reason for the loop to search for the valid origins is that you can't respond with a list of allowed origins...
This solved most of the problems with one exception (If I recall correctly was problems with PUT and DELETE verbs). This required removing the "ExtensionlessUrlHandler-Integrated-4.0" and re-adding it with a path and verb in the handlers section of the web.config.
web.config (2nd change)
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="" />
</handlers>
....
</system.webServer>
Useful links related CORS
Really good description of PreFlight for CORS
Excellent Sample Application using Token Auth
It is not the IdentityServer you are using but it could be the same problem. Regarding to the IdentityServer´s Github page you have to activate RAMMFAR (runAllManagedModulesForAllRequests) for your application when running under the IIS.
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
</modules>
</system.webServer>
I had this same issue, I did everythin as suggested by Mr. Tom hall. But still chrome reported no Access-control-allow-origin header is present.. after inspecting with fidler i realized that my request goes through a proxy server and my proxy server is handling the preflight options request..
So in "internet options" i removed the proxy server and found out that everything is working...!!!
Good day all
I'm completely stumped. I am developing an MVC App using MS VSExpress 2013 for Web, and am at the point where I need to add Windows Authentication. Creating a default MVC App using the Wizard and selecting Windows Authentication, it appears to work fine. Lets call the default App App1, my developed App App2.
Monitoring the Context.User object within Watch for both Apps:
App1 returns:
User {System.Security.Principal.WindowsPrincipal}
System.Security.Principal.WindowsIdentity
AuthenticationType "Negotiate" string
IsAuthenticated true bool
Name "MyDomain\\Andrew" string
When using the developed app, the result is as follows: Please note, the returned object is different (App1 = System.Security.Principle.WindowsPrinciple, App2 = System.Web.Security.RolePrinciple.
User {System.Web.Security.RolePrincipal}
Identity {System.Security.Principal.GenericIdentity}
AuthenticationType "" string
IsAuthenticated false bool
Name "" string
HttpContext.Current.Request.LogonUserIdentity.Name "MyDomain\\Andrew"
When switching the developed app Development Server properties to Windows Authentication = Enabled, Anonymous Authentication = Disabled, the result is an immediate:
Server Error in "/" Application.
Resource cannot be found.
Http 404...
Requested URL:/Account/Login
I've checked and compared:
Web.config files and IISExpress\config\applicationhost.config settings for both applications.
My limited knowledge (based on reading all the questions on SO I could find), I'm guessing App2 thinks it is using Forms Authentication, not Windows Authentication. App2 is getting the user information through the
HttpContext.Current.Request.LogonUserIdentity.Name object (found this on SO).
I've added:
<add key="autoFormsAuthentication" value="false" />
to Web.config ... no joy.
Anyone have any idea why the two apps are returning different user objects, and where this can be corrected? Why does App2 not get IsAuthenticated=true from the same IISExpress server as App1?
Thanks
http://www.itorian.com/2013/05/windows-authentication-in-mvc4-with-iis.html
For some strange reason (EDIT Here is the reason-> ASP.NET MVC3 and Windows Auth on IIS keeps redirecting to /Account/Login /EDIT , the wizard generated app1 did not require these two lines within Web.config
<add key="autoFormsAuthentication" value="false" />
<add key="enableSimpleMembership" value="false"/>
After adding the 2nd line, the Context User object returned changed from
System.Security.Principal.GenericIdentity
to
System.Security.Principal.WindowsPrincipal
and all is well.
Also ensure your IISExpress server config file applicationhost.config (within the IISExpress installation folder) contains correct entries as required:
Global Entry:
<windowsAuthentication enabled="true">
<providers>
<add value="Negotiate" />
<add value="NTLM" />
</providers>
</windowsAuthentication>
I have a js file that includes in the master page.
I want to deny the file access when user type the direct link in the browser address bar.
I've tried the URL filtering IIS,like:
<security>
<requestFiltering>
<denyUrlSequences>
<add sequence="Scripts/Foo/bar.min.js" />
</denyUrlSequences>
</requestFiltering>
</security>
This does work, when i type 'localhost://blah/Scripts/Foo/bar.min.js' I get blocked.
but the page whitch need this js file can not render.
Does anyone have a workaround? Thanks in advance!
I finnaly found this.
and added these sections to web.config:
<security>
<requestFiltering>
<filteringRules>
<filteringRule name="protectjs" scanUrl="true" scanQueryString="true">
<scanHeaders>
<clear />
<add requestHeader="Accept" />
</scanHeaders>
<appliesTo>
<clear />
<add fileExtension=".js" />
</appliesTo>
<denyStrings>
<clear />
<add string="text/html" />
</denyStrings>
</filteringRule>
</filteringRules>
</requestFiltering>
</security>
It works at present, though I know this way is not that reliable.
Not 100% sure but I don’t think there is a way to do this. When browser renders your page it actually sends the same request to the server as the one you do when you manually type in JS file in the browser.
IIS doesn’t have means to distinguish requests you make and the one browser makes in the background while processing your page.
One thing that might work is
adding some kind of ID to your JS file like this Scripts/Foo/bar.min.js?ID=E3CF305B-4444-E011-8FD2-1CC1DEE89A7F
ID is time limited and works only 20 seconds or so after it’s created (enough time for browser to load the page)
creating custom handler that will parse ID and decide if server should return the request or not
So when someone types Scripts/Foo/bar.min.js handler will reject the request but it will also reject request when expired ID is used.
However this is most probably going to be difficult to maintain and also performance intensive for your server.
If you want to hide your JS file why not obfuscate it.
Use HttpModule and Check for HttpContext.Current.Request.UrlReferrer. it will always be null for direct access to the .js file.
public class JSAccessModule : IHttpModule
{
public void Init(System.Web.HttpApplication Appl)
{
Appl.BeginRequest += new System.EventHandler(Rewrite_BeginRequest);
}
public void Rewrite_BeginRequest(object sender, System.EventArgs args)
{
//Block if direct Url is accessed
if (HttpContext.Current.Request.UrlReferrer == null)
{
HttpApplication App = (HttpApplication)sender;
string path = App.Request.Path;
string strExt = System.IO.Path.GetExtension(path);
if (strExt == ".js")
{
HttpContext.Current.Response.Redirect("~/AccessDenied.html");
}
}
}
public void Dispose() { }
}