I wish to secure individual (dynamic) pages in an ASP.NET MVC application.
I do not want to use a full blown authentication system - we are already using forms authentication for the administrators of the site. Instead, this is so that we can send out links to a page with a password for specific users.
The way I am handling this currently is when a valid password is submitted we create an encrypted cookie containing the page id (Guid) and their session id and redirect them to the page. In our "Page" controller action we then validate this cookie.
So first question, is this the best (most secure) approach (aside from using forms authentication)?
Second question, can I read the machine key used by Forms Authentication to perform the encryption, or better yet use the FormsAuthentication to encrypt the cookie (the only overload I can see is one that requires a FormsAuthenticationTicket)?
Since we always generate a new machine key before deploying it would be better if all our encryption used the same key.
[Update]
Regarding how to access the machine key I found my answer at http://rich-rogers.com/archive/asp.net-c-sharp-encrypt-hash-using-machinekey-values
[Update 2]
I realize after asking this question that since I will need to maintain a list of pages that they do have access to, I would probably be better off just storing these in the current session. I can store a list of security tokens with an expiry date. Since I already have a wrapper around session, this should be easy to unit test too.
To make really secure your cookie-key, you need to make it available only on SSL pages, or else some one can get it, and even if its encrypted he can use it.
Request.Cookies[cookieName].HttpOnly = true;
Request.Cookies[cookieName].Secure = true;
Also to read: Can some hacker steal the cookie from a user and login with that name on a web site?
Related
I want to create a authorization mechanism for an application based on WebAPI and AngularJs.
I've seen some articles, which use BasicHttpAuthentication, but i really don't like the whole idea of sending username, and password on every request. The more it doesn't fit for me is because i want to use OpenId authentication, where you don't have username/password pair.
I'm thinking about a solution, but I don't really know how to implement it. The concept is that user is authenticated as in an usual Web application - posts a form with user / password or selects an OpenId provider. If the user is authenticated succesfully, it is placed in a static object, which stores the User object for a certain ammount of time. Next a usertoken is generated and passed to the Client Application. The client passes the token on each request to the server, if the user exists in the above mentioned static object with the appropriate authentication token it is authorized to get the data.
Firstly - Do you think this is a good approach to the problem?
Secondly - How should I pass the authentication token, WITHOUT using cookies? I guess it should sit in the request headers, like in BasicHttpAuthentication but, I really dont' know how to handle it.
BasicHttpAuthentication
I'm with you on feeling dirty about caching the username and password on the client and forever transferring it with every request. Another aspect of Basic authentication that might work against you is the lack of sign-off. Other than changing the password, you can't "invalidate" a basic authentication session. Tokens on the other hand, will typically offer an expiration date, and if you want server-side invalidation you can check the issue date and say "any tokens older than issue date xyz are invalid".
Server State
You mention "If the user is authenticated successfully, it is placed in a static object". But this is independent of the token? This sounds like you're wanting to implement server state management of authentication sessions, but this isn't strictly necessary. The token itself should be sufficient for user authentication, managing server state is another potential obstacle. Server state can become difficult to manage when you factor app-pool recycles or web-farm environments (what if you want two services to share the same authentication token, but not require communication with a central "authentication server" for storing the state / session?)
Passing Authentication Token
Headers is definitely a good place for it. Really, where else is there? Cookies, Headers, Message. Other than a browser client, cookies don't make a lot of sense, and including it in the message can muddy your message formatting a bit, so headers is the only remaining option that makes much sense in my view.
Client Implementation
You've not specified, but I suspect you're interested in calling the service from .NET? In which case System.Net.Http.HttpClient could be your friend. In particular, the DefaultRequestHeaders collection. You can use this to add a custom header to store your authentication token.
Server Implementation
When researching ASP.NET authentication recently, I learned a lot about customisation by examining the Mixed Authentication Disposition ASP.NET Module (MADAM). I wasn't interested in using MADAM as-is, but learning about it from that article and examining the source code gave me a lot of ideas of how I could insert my own authentication module into the web stack.
I'm using a login form to authenticate users.
FormsAuthentication is right out as it stores sensitive user/role membership in either client-side in a cookie or within the URL. Within the URL is a huge security risk, so I won't even get into that. With the
FormsAuthentication cookie, this creates problems with a) security where the client is in the position of dictating it's own roles; and b) way too much data stored in cookies. Since I'm gaining nothing through security and loosing out big time on the size of user data storage, I'd rather just work with Sessions.
I'd like to reuse something like FormsAuthentication for all its basic login form-handling features. But i would rather have it store user data server-side in perhaps Session rather than client-side all stuffed into a single cookie. I'd rather just authenticate against a Session token of some sort.
I have no database and local disk storage of user data is forbidden. I rely on a 3rd party authentication service provider, and a requirement is that I must reduce chatter with this service. Thus, sessions for temporary storage of user info. Sucks, but that's not necessarily the problem I'm asking about. Also, a requirement is that I must set/use HttpContext.user and likely Thread.CurrentPrincipal for use later on in such things as AuthorizeAttribute, for displaying user info in views, etc.
So FormsAuthentication stores all user data client-side in a cookie. Whereas Session stores all data server-side and just relies on a simple client-side token cookie. However, Session is not available anywhere during the asp.net startup and authentication steps. Is there an equivalent forms "membership" provider that stores all data in Session server-side instead of client-side?
If there is no Session equivalent...
Where do I set HttpContext.user and Thread.CurrentPrincipal to make both values available throughout the rest of both MVC apps without interfering or messing up other MVC components?
Hinging on #1, is Session available at that entry point? If not, how do I make it available so I can create the Principle/Identity object using the data stored in Session?
This can't possibly be a unique requirement. Are there libraries already available which handle this?
Session stores information in a client-side cookie too! (or in the URL if cookieless).
If you want to authenticate a client, he'll have to provide some kind of credentials - usually an encrypted token in a cookie once he has logged on. If not a cookie, then what do you propose?
You should use FormsAuthentication. The sensitive information stored in a client-side cookie is encrypted using a key that should only be known to the web server. "the encryption methods being public knowledge" doesn't mean that you can decrypt the data without access to the appropriate cryptographic key.
You mention "roles" and a "third-party authentication provider". If your third party is also providing roles (i.e. an "authorization provider" as well as an "authentication provider"), then it would be reasonable to cache roles obtained from the provider on the server. Session is not available when a request is being authorized, so the best solution is to use the Cache (System.Web.Caching.Cache).
Personally I would encapsulate this in a custom RoleProvider. The custom RoleProvider would implement GetRolesForUser by getting roles from the third party on the first call, then caching them in Cache.
Not sure if I like what I'm about to suggest, but you could do the following:
Leverage the Application State or System.Cache as a global storage for user credentials.
Use an InMemory database (like RavenDb) which can also have encryption (in memory, I believe).
Using the Application state as a place to storage relatively common / frequent stuff I think is not a great place because of
Scaling / locking issues? <-- just a gut feeling.
Permenant data? so you have users in the website's memory .. then the website crashes or recycles, etc... what happens now to that data?
RavenDb is awesomeballs - go.use.it.now.
I understand that you are not storing anything locally, so whenever a user hits your system, you need to refresh your inmemory cache, etc. Fine. A pain in the f'ing butt , but fine none-the-less. (EDIT: unless the data has been cached in memory .. that is)
Anywys, two suggestions.
ProTip:
Oh! move away from role based shiz and start using Claims based identity stuff. Yes, it still works with IPrincipal and HttpContext.User, etc. So all previous code is NOT busted. But now it's baked into .NET 4.5
Awesome Video on this for you, me, everyone!
Finally - bonus suggestion
A nice package that auth's with either Facebook/Google/Twitter. You said you're keeping the user cred's on another site (good move!). If you're using other providers, then stick with DNOA or SS.
GL!
We recently updated our solution to MVC 2, and this has updated the way that the AntiForgeryToken works. Unfortunately this does not fit with our AJAX framework any more.
The problem is that MVC 2 now uses symmetric encryption to encode some properties about the user, including the user's Name property (from IPrincipal). We are able to securely register a new user using AJAX, after which subsequent AJAX calls will be invalid as the anti forgery token will change when the user has been granted a new principal. There are also other cases when this may happen, such as a user updating their name etc.
My main question is why does MVC 2 even bother using symmetric encryption? And then why does it care about the user name property on the principal?
If my understanding is correct then any random shared secret will do. The basic principle is that the user will be sent a cookie with some specific data (HttpOnly!). This cookie is then required to match a form variable sent back with each request that may have side effects (POST's usually). Since this is only meant to protect from cross site attacks it is easy to craft up a response that would easily pass the test, but only if you had full access to the cookie. Since a cross site attacker is not going to have access to your user cookies you are protected.
By using symmetric encryption, what is the advantage in checking the contents of the cookie? That is, if I already have sent an HttpOnly cookie the attacker cannot override it (unless a browser has a major security issue), so why do I then need to check it again?
After having a think about it it appears to be one of those 'added layer of security' cases - but if your first line of defence has fallen (HttpOnly) then the attacker is going to get past the second layer anyway as they have full access to the users cookie collection, and could just impersonate them directly, instead of using an indirect XSS/CSRF attack.
Of course I could be missing a major issue, but I haven't found it yet. If there are some obvious or subtle issues at play here then I would like to be aware of them.
It was added to offer greater protection in the case where you have one subdomain trying to attack another - bad.example.com trying to attack good.example.com. Adding the username makes it more difficult for bad.example.com to contact good.example.com behind the scenes and try to get it to generate a token on your behalf.
Going forward, it's possible that the cookie will be removed as it's not strictly necessary for the proper functioning of the system. (For example, if you're using Forms Authentication, that cookie could serve as the anti-XSRF cookie instead of requiring the system to generate a second cookie.) The cookie might only be issued in the case of anonymous users, for example.
Besides the "evil subdomain"-scenario outlined by Levi, consider an attacker that has an account on the targeted site. If the CSRF-token does not encode user-specific information, the server can not verify that the token has been generated exclusively for the logged-in user. The attacker could then use one of his own legitimately acquired CSRF-tokens when building a forged request.
That being said, anonymous tokens are during certain circumstances accepted by ASP.NET MVC. See Why does ValidateAntiForgeryTokenAttribute allow anonymous tokens?
A client uses classic ASP to log in to their web based backoffice.
I have written a new ASP.Net app to be included in the backoffice, and I need to utilize the already existing login-system, so that when they are logged in there, they don't need to log in again in the new ASP.Net app.
Logins and passwords are stored as clear text in a SQL Server db, that I can access from my ASP.Net app.
What would be an effective way to integrate these systems?
My current best idea is:
In the link to my ASP.Net app, I link to a "gateway" login-page with their userid and a hashed password + common secret in the querystring. I then compare this to the password of the user in the database... But the problem is, that if this querystring is intercepted, it can be used to access the asp.net site, without actually knowing the username and password...
I am most likely overlooking something simple.
I think your idea is on the right path.
As you probably already know, classic asp and asp.net cannot share the same session state, so you do need to have a mechanism to log from one into the other.
What I would do is: when someone logs in, create a unique GUID that you save in the database for that user. When you jump from one site to the other, pass that GUID into the query string. When you try to auto-log them into the other site, look up that GUID and see if it's attached to anyone. If it is, log them in.
This way you aren't passing anything that a user could guess or decrypt.
How does the classic ASP system maintain login state? "Piggy-backing" off that would be your best bet, by far.
All classic ASP systems I've worked on have used cookies for tracking authentication information, so just read those and compare against the database that you can access.
As the information is stored in a Classic ASP session, could you add a "redirect page" to the classic ASP side of things that is the "entrance" to the new module, and cause this to write the useful data out as cookies, or trigger a POST to your start page? By using cookies or a POST request, you minimise your worry about having the url "hijacked" allowing someone to get into the ASP.net site without username/password.
You are rightly worried about a MITM type attack, possibly through DNS cache poisoning or similar. Depending on your circumstances, it may be enough to mitigate the potential effects of this by adding a time constraint to the login token that is passed across the application boundaries.
The 'GUID in the database approach' is something I have used successfully myself in the past, both for passing users between two applications sharing the same authentication database, and also for 'password reset email' type scenarios. You could 'expire' this by having an additional column on the record specifying the date at which the GUID was added, and modifying your application code to only log in GUID auths which are less than x minutes / hours / days old.
An alternative could be to avoid additional fields in the database by concatenating something like:
UserId + [Value representing current time to nearest x minute / hour /day] + Salt
.. hashing it, then then duplicating your algorithm on the other application and comparing the two generated values.
In general, I think your proposed solution is appropriate to the problem. It is certainly not too complicated.
Couldn't you submit it via Form and not through the Querystring? That would eliminate the possibility of it being intercepted in the url.
If interception is a serious issue then you need to be running the site over HTTPS. Otherwise using the UserID + Nonce that is then hashed by the password is reasonably strong.
Alternatively you could get the ASP app to add a GUID session cookie once logon has been acheived and store that GUID in a DB table. Your ASP.NET can look up the GUID from the cookie to see if logon has been acheived. If you include the ASP session cookie value in the table you can make reasonably sure that the current ASP session is the same session that was used when the GUID was created.
I have two web applications and sometimes I need user to jump from one application to another. Since they are two web applications and may reside on different domains/machines, I can not share session between them.
The technical challenge for me is how to pass session information (I only need to pass userID string information in the session) from one source application to another destination application -- so that the user feels Single Sign On and personal information is displayed for him/her in both application (as the userID is passed to the destination application, no re-login is needed).
My current solution is generate all URL strings in both application and append them with user ID after user logins successfully, like http://www.anotherapplication.com/somepage?userID=someuserID, the userID value is retrieved from session. But I think my solution is stupid and I want to find some way to automatically append the query string ?userID=someuserID when the user jumps to another URL in another application -- so that I just need to generate the common unified URL http://www.anotherapplication.com/somepage in both application.
Is there a solution to automatically append the userID query string?
thanks in advance,
George
Rather than doing it via the Querystring, it might be more maintainable in the long run if you use create a FormsAuthenticationTicket with the required values.
I especially recommend reading Michael Morozov's excellent article on the subject of SSO (Single sign ons).
I do not think it is a good idea to have the user id in query string.
A better idea would be to implement a single-sign on solution. In your scenario, you could do the following:
Whenever one of your applications receive an unauthenticated request, redirect the user back to the other application to a special single-sign-on url.
This page checks whether the user is logged in, and if so, redirects back with an authentication token in querystring.
This token is checked by the un-authenticated application; and if it passes, you can login the user.
Of course, this seems like "a lot" of redirecting, but it should be reliable, and it only happens once, and then your user will be authenticated on both applications.
Obviously you would need to implement a security scheme so that you can check that the authentication token you get passed is really valid and originating from your other application. You could do this with a challenge-response algorithm; which could be:
Both applications should know a common key.
First application sends some random data (the "challenge") to the second application.
The second application includes a hash-value of the random data + it's answer + the secret key in its response.
Now the first application can check that the second application knew the secret key by calculating the same hash-value.
Have a look at:
http://en.wikipedia.org/wiki/Challenge-response_authentication
EDIT:
With regards to session state, see http://msdn.microsoft.com/en-us/library/ms178581.aspx for an overview. It is possible to share session state between the applications, but I would not recommend it in general. If your application resides on different domains (URLs) you would have to use cookieless session state; which is not safe. If you decide to go this way, you would either have to use State server or SQL Server for session persistence, depending on your setup.
You can persist the session using something else than InProc (which is short for in process). If you persist the session using a SQL Server backend you'll be able to retrive the session cross domain/machine if they are setup to use the same SQL Server backend for session storage. This is configurable in ASP.NET and support out-of-the-box. I suggest you look it up.