MVC4 / IIS / Forms Authentication SSO issue - c#

I’ve got a weird intermittent issue with MVC4 / IIS / Forms Authentication.
I’ve got a pair of sites that pass control to each other using SSO. Most of the time the handover occurs correctly and the user is redirected to the next site as intended. However, in some cases, the user is asked to log in again, even though valid SSO information was sent across. The SSO method is decorated with the [AllowAnonymous] attribute and the web.config also has a location entry granting access to /account/sso to all users.
It appears to occur when the destination site is being hit for the first time - once the app pool is warmed up, the issue disappears.
Some other points:
1 both sites are .net 4, so there should not be any legacy encryption issues.
2. this issue happens quite rarely (<10% of the time) so the code itself should be sound
3. Hosting is IIS 7.5 on win7x64 locally, and azure - happens in both places
4. Seems to be browser independent
<location path="account/sso">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
[Authorize]
public class AccountController : BaseControllerTestable
{
public AccountController()
: base()
{
}
[AllowAnonymous]
public ActionResult SSO(string AuthToken, string Target)
{
//SSO logic here
}
}
Any ideas?

You have an Authorize attribute on your Controller class which means that your SSO method would have AllowAnonymous and Authorize applied to it. In this instance the Authorize attribute looks like it needs to be removed.

What is your BaseControllerTestable? Do you have any authorization attributes there? Your Base class will be instantiated firs before it will get to your other methods on the derived class. So if by any chance you have [Authorize] on the base controller that may be an issue for you.

I think I've finally resolved it (we'll only know for sure once we've had a good while without recurrence given that it was intermittent anyway)
A couple of factors came into play. Firstly I noticed a few static items (css+js files mostly) that were getting caught up in authentication loop even though they should be freely accessible, so I added a location rule in web.config to make sure they were allowed to anonymous users. I also added a route exception to ignore favicon.ico requests for good measure too. This seemed to stop the code from tripping over itself when authenticating for the first time. Finally, the reason the issue was intermittent was due to another bug where if there was any other sessions open (db driven) the issue didn't occur. this explained why the bug only happened early in the morning ie: all the sessions from the previous day had expired.

Related

Live testing ASP.NET custom Role Provider

Using ASP.NET Webforms + VB/C#
I've been tasked to restrict ASP.NET page access to users not in specific roles. And I need to be able to live test my solution (versus unit testing where I could use mocks or fakes). Our site is rather complex so I doubt I'd be able to find all the "gotchas" with just unit testing.
I have something on my development computer I'm pretty sure will work in Production: I have a custom Role Provider hooked into the web.config file. It's being initialized and called when I debug the website, so I'm reasonably sure it's working OK. I have a folder ("Administration") marked only for a specific role. Our roles are defined in our own database and are not tied to Microsoft or Windows roles/permissions.
The problem is: I cannot actually login as a user I wish to debug with. I can "simulate" this with a special development-only start page. That works OK for our menu/navigation items which are built from the roles database, but of course without restricting pages (with a Role Provider or something similar), you can still manually type in a page and it will be served up. This devel-only start page sets the username I pass in as a FormsAuthentation auth cookie.
FormsAuthentication.Initialize()
FormsAuthentication.SignOut()
FormsAuthentication.SetAuthCookie(simUserName, True)
It seems like when I first start debugging (usually after a reboot), the custom Role Provider will get called with the same username I'm simulating, but after a while, that suddenly stops and my local Windows name gets passed instead. (Cookie issue?) After that, it no longer works.
Anyway -- Is there a way I can test roles locally during development or will we just have to put this on Production and hope for the best.
I didn't know what code or settings would be useful at this time, so let me know what you need. Thanks!
I think I've found the solution to my problem. I decided to work with some of the samples in Ch. 7 of Beginning ASP.NET Security by Barry Dorrans (published by Wrox) in the hopes working with authentication and authorization in simple examples might lead to a solution, and it did.
One of the examples of using Forms authentication (pp. 155-157) showed a simple login.aspx page similar to my development startup page. The code I had in my Development startup page (shown above) was incorrect. It should have been:
FormsAuthentication.Authenticate(simUserName, simUserPassword)
FormsAuthentication.RedirectFromLoginPage(simUserName, False)
I also defined my simulated users in my Development web.config:
<authentication mode="Forms">
<forms defaultUrl="default.aspx" loginUrl="mystart.aspx">
<credentials passwordFormat="Clear">
<user name="GeorgeWashington" password="password"/>
<user name="AndrewJackson" password="password"/>
<user name="AbrahamLincoln" password="password"/>
<user name="TeddyRoosevelt" password="password"/>
<user name="JackKennedy" password="password"/>
</credentials>
</forms>
</authentication>
When FormsAuthentication.Authenticate is called and authenticates which ever of the simulated users I choose, it looks like it causes ASP.NET to now always use this username in calls to the Role Provider. At least, this appears to be working when debugging the website.
I've set the project to always call my Development startup page (the login page -- mystart.aspx). This way I always start with a fresh authentication in case I need to work with a different role.
Anyone wanting to use this solution, WARNING: Your Development startup page must NEVER be used for the Production website. Likewise, NEVER use usernames and passwords in a Production web.config. This is ONLY for debugging and testing. Depending how you version control your development and production code, you may have to manually merge certain changes going from development to production in order to avoid sending this debugging code to Production.
I also understand Microsoft wants us to use a Membership Provider in place of the FormsAuthentication class. And in Production code, we should. But for my purposes (ensuring I can interact with the website in differing users/roles while debugging), this appears to be the simplest solution.
EDIT -- one more piece of the puzzle: I was still getting strange method calls in my Custom Role Provider. A few times a method would be called with the username I logged in with on my login page (this is what I expected); most often it was my Windows username which led me to believe Windows authorization was still being used by ASP.NET somewhere. After more research (thanks SO!!!), I found in my applicationhost.config file for my project, Windows authentication was set to True which I guess was causing a conflict with my web.config file. Setting the values in applicationhost.config to:
<location path="MyWebSite">
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="false" />
</authentication>
</security>
</system.webServer>
</location>
seems to have solved that issue.
If you have any questions, or need more information, please let me know.

Azure Ad - Redirect Uri hidden behind authorization

I'm encountering an issue where I can observe an infinite redirect loop. My project is based on the official MS example - active-directory-b2c-dotnet-webapp-and-webapi
Does "Redirect URI" (defined in Azure Portal) have to be publicly accessible endpoint?
What would happen if in my controller I decorated it with an [Authorize] attribute?
So basically in this example Redirect Uri (set as website root, i.e. localhost:1234/) would also be a route for an action in the controller, which requires authorization.
[Authorize]
public class ControllerA : Controller
{
[Route("~/")]
public ActionResult Index()
{
}
}
Could it cause an infinite redirect loop?
Removing the route attribute fixes the problem, but at the same time, I feel like it's not a real cause of the issue.
I imagine that OWIN authorization is higher in the application stack compared to the controller's authorization, so I think that OWIN authorization middle-ware would parse the response from Azure Ad in a first place, instead of enforcing [Authorize] attribute policy and rejecting it upfront.
You can certainly create an infinite loop scenario this way but it will end up short-circuited by Azure AD. After a few loops, it will stop redirecting back and surface an error.
The redirect_uri needn't be a publicly accessible URI, it will work with http://localhost for example. It need only be accessible by the client. After all, a "redirect" is simply an HTTP response issued by the server. It is actually executed by the client.
In general, the controller you're using for authorization (i.e. receiving the redirect) shouldn't be decorated by an [Authorize] at the class level. Typically you would only decorate the handful of methods that require a signed in user. That said, you could certainly decorate the class with [Authorize] so long as you decorate your callback endpoint with [AllowAnonymous].
The core of the problem and solutions are described in following document - System.Web response cookie integration issues. I've implemented 3rd solution (reconfigure the CookieAuthenticationMiddleware to write directly to System.Web's cookie collection) and it has resolved the issue. What lead me to discover that the cookies were the issue is another StackOverflow's question, which describes a really similar symptoms to the one I was observing.
A fact that the default route [Route("~/")] was mapped to one of the controller's methods which required authorization, and that it was also matching a redirect_uri that I've set up in Azure Portal was not a culprit. In my opinion this is a proof that redirect_uri call will be handled directly by OWIN auth middleware and it won't even reach a controller's method.

Index not found on HomeController, possible hack attempt?

Occasionally I get the following stack trace from some of our production websites:
A public action method 'Index' was not found on controller 'HomeController'.
Now this route obviously exists and the site works fine in numerous test environments.
The IP addresses that originates these requests are not in our target markets and are what I would consider as people trying to 'hack' the site. I assume they are doing something weird with the headers to cause this problem.
Is this something I should be concerned about or should I suppress? By concerned I mean is there something I could do to handle this error more gracefully to avoid showing the error message.
I am unable to replicate the situation in a browser or using fiddler.
Website is running ASP.NET MVC 5, IIS 7.5, Windows Server 2008.
There is a very similar question answered on SO here: https://stackoverflow.com/a/2008013/1541224
Basically it asks you to disable the verbs you don't need in your app like:
<authorization>
<deny users="*" verbs="OPTIONS, PROPFIND, HEAD"/>
</authorization>
I would imagine .NET responding with Method not allowed error in these cases but in any case, if this solves your issue go with it!

Asp.net mvc Specified UserAgent no session Id

I have a problem with my project Asp.net mvc 1.0, with .net framework 2.0. My application is hosted on a IIS 7.5. My authentication form looks like this:
<authentication mode="Forms">
<forms protection="All" loginUrl="~/Account/LogOn" timeout="60" cookieless="UseUri" />
</authentication>
<httpRuntime executionTimeout="1000" maxRequestLength="600000" />
<sessionState mode="InProc" cookieless="UseUri" timeout="60">
</sessionState>
When a user connects to the webpage, he receives a session id which is stored in the URL. When I connect to my webpage with the default UserAgent (in every browser, Chrome/FF/IE) everything works fine. When I override the browser UserAgent and try to connect with the User agent XXXXXXXX.UP.BROWSER, I receive an infinite redirection loop to address
http://<IP>_redir=1
But when I connect to the default webpage IIS - the user agent doesn't matter and everything loads fine, so it must be a problem with the specified UserAgent and my Application. I tried to find any filters for that XXXXXXXX.UP.BROWSER UserAgent but there aren't any. When I studied application lifecycle I tried to find the differences between good connection and wrong connection and found that functions which are NOT executed are:
Application_AcquireRequestState
Application_PostAcquireRequestState
Application_PreRequestHandlerExecute
Application_PostRequestHandlerExecute
Application_ReleaseRequestState
Application_PostReleaseRequestState
Application_UpdateRequestCache
Application_PostUpdateRequestCache
and another clue I found is that there is no Session in "wrong" connection - Session object is null.
To sum it up: The connection to my application web page with a specified user agent makes an infinite redirection loop, probably because of the lack of the session ID. What could be the problem ?
EDIT: I discovered that User Agent that contains "UP.Browser" is related to mobile. When I changed cookieless to "UseCookies" everything works. Why option "UseUri" doesn't work for mobiles?
EDIT2 : /admin -> my webpage hosted on specified IP address.
Good connection :
Wrong connection:
Sorry, I don't know how to make these images bigger.
http://msdn.microsoft.com/en-us/library/aa479315.aspx
So you're putting two different values into the URI, one for session and one for forms, which would probably create a lengthy URI:
"The principal limitation of this feature is the limited amount of data that can be stored in the URL. This feature is not targeted at common browsers such as IE, since these do support cookies and do not require this feature. The browsers that do not support cookies are the ones found on mobile devices (such as phones), and these browsers typically severely limit the size of the URL they support. So, be careful when you use this feature—try to make sure that the cookieless string generated by your application is small."
My guess is that the key to the infinite redirect loop is this functionality:
"// Step 5: We can't detect if cookies are supported or not. So, send a
// challenge to the client. We do this by sending a cookie, as
// well as setting a query string variable, and then doing a
// redirect back to this page. On the next request, if cookie
// comes back, then Step 3 will report that "cookies are
// supported". On the other hand, if the next request does not
// have any cookies, then Step 4 will report "cookies not
// supported".
SetAutoDetectionCookie();
Redirect(ThisPage + Our_auto_detect_challenge_variable);"
Unfortunately, this sounds like a bit of an architecture rethink, as it's probably going to now matter what the full path to your site is and you may have to drop automatic handling of forms authentication.
As you said the issue is for mobile browsers, I think this issue is limited to the devices(MOBILE) where the cookies are not supported and the Size of the URL increases and mobile browser severely limit that size, as mentioned in the MSDN reference article above.
My solution was to change User Agent containing "UP.Browser" to something else using rewrite rule. Everything works fine ;)
Edit: I found another clue.
In mobile browser - these with user agents containing "UP.Browser", it was necessary to add slash at the of the address.
In conclusion:
Everything works fine for user agents not related with "UP.Browser".
User agents containing "UP.Browser" needed address like:
http://addr/controller/
I don't know why it is necessary. Any ideas?

Why does DotNetNuke log me out on post ajax requests?

Previously, when I tried to do an ajax call to an ashx as a non-superuser account (i.e. as portal specific user) my web server would return cookies to clear my authorization. I posted a question about this and it seemed the answer was to make sure that the portalid=xx was specified in my GET parameters.
However, I have just found out that if I add portalid=xx in a POST request, DotNetNuke seems to ignore and and log out any non-superuser account.
How can I keep authorization during DNN POST ajax requests?
I think I have a good handle on the whole situation, and unfortunately it appears that the only true solution is to make sure each child portal has its own subdomain rather than a sub-url (e.g. portal.domain.com rather than domain.com/portal).
The problem is that when your portal 0 is domain.com but portal 1 is domain.com/portal everything works correctly until you need to access an .ashx file via ajax. What happens then is the URL that's requested is instead domain.com/DesktopModules/MyModule/Handler.ashx, which does not contain the /portal/ in it, thus causing DNN to think you are doing a request on portal 0 and logging you out.
While GET requests can overcome this with a portal=1 parameter, this does not seem to work for POST requests.
Therefore, the best solution it seems is to have your portal on a distinct subdomain (portal.domain.com), and then you don't risk missing something like this.
I've found a few things for you to check out and see if any of them solve your problem.
Make sure you are using a ScriptManagerProxy. This allows ascx pages to use AJAX while the parent page is also using AJAX.
There have been many reports of people not being able to run AJAX with DNN if Page State Persistence is set to "Memory". Those who experience this have been able to fix it by switching Page State Persistence to "Page". The easiest way to do this is to run this query:
update HostSettings
set SettingValue='P'
where SettingName='PageStatePersister'
After you run that, you'll need to recycle the application. If you don't have access to the server, just add a space or carriage return to your web.config file (that will force the app to recycle).
Lastly, you might see if you have this line in your web.config. Sometimes removing it will help:
<system.web>
<xhtmlConformance mode="Legacy" />
</system.web>

Categories