As RIAs (Rich Internet Applications) gain in popularity, it becomes easier for programmers to accidentally create security holes in their application. If you’re used writing server-side code, it can be easy to forget that any code running on the client (compiled or not) can ultimately be seen by prying eyes. And if you come from the desktop world, you might not be familiar with all the ins and outs of securely talking to services in the cloud. Unfortunately, there are no good tools (that I’m aware of) to help audit your apps to make sure you’re doing things right. Until then, here’s a quick crash course in RIA security for any app that needs to restrict calls to web services. Maybe you only want paying customers to use your app. Maybe you need to store personal information about each user in a database. Either way, you’ll need users to login and you’ll need to authenticate calls from the client to your web services.
- The user logs into the client (the RIA) and the client passes that username and password up to the server via a call to a web service.
- The server then validates that login and sends back a reply of success to the client.
- The client then permits the user to start froliking around in the application.
- Eventually, the client needs to talk to the server again to save or request some data (maybe this happens immediately after login to retrieve the data they saved during their last session).
How do we make sure that the client calling the web service actually successfully logged in earlier? From the server’s point of view, you CANNOT assume that just because the client can only call this service if the login was successful, that this is actually what’s happening. Hackers can easily spoof your client and therefore fool the server.
All the hacker needs is the URL of the service which they can then easily type it into a browser to find the service’s entire API. To get this URL they can either decompile your code using tools like SilverlightSpy or Reflector (if you’re writing in .NET), or they can just use Fiddler to monitor the calls to your server.
- Obfuscating your code won’t help. Obfuscation might encrypt the service urls in your code but they will be decrypted by the time they show up in a tool like Fiddler.
- HTTPS/SSL won’t solve this either. HTTPS won’t do anything but encrypt the calls going over the internet. From the client and therefore from a tool like Fiddler, the URLs themselves will all be visibile.
- Passing up a hardcoded password with each request won’t solve this either. If you pass up a password, that means you need to store that password in your code somewhere. Even with obfuscation, hackers can still find it by decompiling your app and altering it in a very minor way to so it shows your hardcoded password in a message box at runtime.
You must keep track of the user’s login (of some form of it) and pass that up with each sensative request to the server. The server then needs to validate that login each time before performing the requested operation.
The simple way:
Keep track of the username and password in a local variable and pass them up to the server with each request. This is relatively straight-forward, but has drawbacks:
- Using this approach makes it impossible to safely login from a webpage outside your RIA or support a “remember me” feature because it will invarably result in the user’s password being stored somewhere on the hard drive. Even if encryped this is still vulnerable to hackers. For more info, see my other article on Posting login credentials to your Silverlight app where I go over the concerns.
- Hackers can catch a packet being sent over the internet and resend that packet up to the server to get back a valid response. How would the server know the difference?
The bad way:
You could pass up a non-guessable user ID with each request (a GUID for example or a random ID that is unique to each user). Unfortunately, this has the same drawbacks as passing up a username and password. Additionally, user IDs are not necessarily private; your application may need to include that ID in a URL that could accidentally be pasted into an eamil by an unknowing user. Or, you application might need to expose a user’s ID to other users. Imagine that your application had a feature where users could see a list of other users that they could collaborate with. The client would get a list of these users and their IDs, the user would choose one or more, and the IDs would be sent back up to the server to notify it of the selection. As programmers, we need to assume that any data passed down to the client is ultimately hackable and therefore viewable by that client… therefore completely exposing each of those other users’ credentials. The bottom line: user IDs are not good surrogates for login credentials.
The best way:
Use session IDs (aka tokens) as an alternate to using real data like the username and password or a user ID. Once the user logs in, create a random string or GUID (this is your token), associate that token with the username and password (or better yet, associate it with their user ID if they have one) by storing these associations somewhere only your web service can access (most likely in memory or in a database). Your service can then pass that token back to the client upon a successful login. From that point forward, the client can use that token instead of a username and password everytime it wants to talk to the server. The server just needs to look up that token each time to find out the identity of the user, and the mere fact that the token was found indicates that the user making the request had logged in successful. Note: you should also make sure that the token expires eventually, otherwise, the token becomes just as useful to a hacker as the username and password.
Finally, how to accomplish this:
Method #1, the DIY (do it yourself) approach. As explained above, create a database or in-memory dictionary to hold the tokens for all the currently logged in users and add an extra parameter for the token to each web service method. Check out my previous article: Posting login credentials to your Silverlight app, where I demonstrate how to do this in a Silverlight client and an WCF web service. You can also download the source code from here.
Method #2, the session approach. In Part 2 of this article (RIA security 102: Using ASP.NET session state to authenticate web service calls) I demonstrate how to use built in ASP.NET session state objects to manage tokens for us. Note: There are other 3rd party options out there to do this for you too if you don’t use ASP.NET.
Method #3: If you though it couldn’t get any easier for us ASP.NET developers, there’s a third approach to use the Forms Authentication application service built into ASP.NET. Special thanks to John Papa for coming across this and Tim Huer for creating the only Silverlight example I’ve seen to date on using this library. This approach uses the System.Web.ApplicationServices.AuthenticationService namespace to manage and preserve a user’s login status between calls by passing back and forth an encrypted cookie. The encrypted cookie (or ticket as it is called) contains your username and login expiration. Each time the user makes a request to a method in your service that needs to be protected, you can use HttpContext.Current.User.Identity to determine if the user has been validated and what their username is. This approach works great once you get it configured and shifts some of the details to ASP.NET. Check out the source code here to see this technique in action from both the client and server’s perspective.
If you check out the source code for all 3 approaches, you’ll notice that they are identical from the end user’s perspective. Yet each uses the different approach mentioned above. Have fun comparing to see which works better for you.