Technical Article
Restricting Access to Ajax Services
By Greg Murray and Ed Ort, September 2007
This article highlights some techniques for restricting access to Ajax-based services. It then focuses on using URL-based API keys, which gives you fine-grained protection and allows you to track, meter, and restrict usage of a service.
Contents
Introduction
Mashups are appearing on the web at an extremely fast rate. It's estimated that three new mashups appear on the web each day. As a service provider, you know that exposing your service so that it can be used in mashups can benefit you in a number of ways. Among other things, mashups can advertise the existence of your services to a constantly growing audience. However, there are some things you should consider when you make a service available for mashups. One of them is the possibility of security exposures. You want people to use your service but not abuse it.
As a service provider, you want people to use your service, but not abuse it.
If you're a service provider, you can take actions that protect your service such as limiting or restricting access. This article highlights some techniques for restricting access to a service. It then focuses on using API keys, which gives you finer-grained protection than the other techniques covered in this article. It allows you to restrict access to your service to users in specific host domains. You can also use API keys to identify who is using your service or meter usage, that is, regulate how much your service is used during a given period of time. However, the focus of this article is primarily on how to limit or restrict access to your service.
Here are some approaches that you can take to restrict access to your service:
- Token-based restriction
- Application key-based restriction
- Session-based restriction
- Content type restriction
- Authentication-based restriction
- URL based API key restriction
Token-Based Restriction
In this approach, you use an identifying string called a token to limit a client's access to server-side resources. The resources can include URLs, databases, web services, or domain objects. You can configure a token in a file or through your server's built-in security features.
In token-based restriction, you use an identifying string called a token to limit a client's access to server-side resources.
One of the places that token-based restriction is used is in jMaki, an open-source framework for wrapping widgets that are coded in JavaScript. Wrapping the widgets through jMaki allows the widgets to be used in web applications that are enabled for Ajax. jMaki provides a library of wrapped widgets from various popular client-side JavaScript technology libraries such as those included in the Dojo toolkit and Script.aculo.us.
jMaki widgets are written to load data from a service or through a server-side proxy. When a jMaki widget issues an Ajax request for a service, the client page that includes the widget is subject to the same server-of-origin policy constraint as any other Ajax client. Because of this, jMaki provides a server-side proxy named
XmlHttpProxy
to issue requests to services outside of the web application domain.
As a security protection, XmlHttpProxy
uses tokens to restrict access to services. A separate token is configured for each of the web services that can be accessed through the proxy. The token configurations are specified as JavaScript Object Notation(JSON) objects in a configuration file that is packaged with jMaki. Code Sample 1 shows one of the token configurations. Note that the title of Code Sample 1 indicates that the code resides in the server. The code samples in this article indicate whether the code resides in the server or the client.
Code Sample 1: Configuring a Token in the Server
{"xhp": {
"version": "1.1",
"services": [
{"id": "yahoogeocoder",
"url":"http://api.local.yahoo.com/MapsService/V1/geocode",
"apiKey" : "appid=jmaki-key",
"xslStyleSheet": "yahoo-geocoder.xsl",
"defaultURLParams": "location=santa+clara,+ca"
},
]
}
}
The id
value, yahoogeocoder
, in Code Sample 1 is the token. Notice that the token is mapped to a set of parameters that include the URL of the service, an API key for the service see Application Key-Based Restriction), an XSL style sheet, and default parameters and values that are passed to the service. In this case, the service is the Yahoo Maps Geocoding service. The default parameter and its value are location=santa+clara,+ca
.
To access the Yahoo Maps Geocoding service, a jMaki widget such as the Yahoo Geocoder widget must specify the appropriate token as well as a location, as shown in Code Sample 2.
Code Sample 2: Accessing a Service by Specifying a Token in the Client
var location = encodeURIComponent("location=sunnyvale ca");
var url = "xhp?id=yahoogeocoder&urlparams=" + location;
Notice that the url
parameter specifies the following:
- The token,
yahoogeocoder
. - A location parameter and its value,
location=sunnyvale ca
, that are passed to the Yahoo Maps Geocoding service. The location is encoded to make the request as transportable as possible. - The URL of
XMLHttpProxy
. In this example, the URL is mapped toxhp
.
Keep in mind that the token configuration exists on the server. The client can neither configure nor modify the token configuration. This is intentional. If you allowed a JavaScript client to directly configure parameters such as the URL accessed by the proxy, it could expose the service to unauthorized access.
Application Key-Based Restriction
The application key-based approach is similar to the token approach in that the client provides an identifier to access a resource. In this case, the resource is a service and the identifier is an application key that the service provides. This is the approach that Yahoo uses to control access to its web services. Yahoo calls the key an application ID. To access a Yahoo web service, a client needs to register with Yahoo, obtain an application ID, and specify the application ID in each web service request. For example, the application ID that jMaki obtained for access to Yahoo web services is jmaki-key
, so that is the application ID that is specified in jMaki requests to Yahoo web services. Code Sample 3 shows a jMaki request to the Yahoo Maps Geocoding service.
In application key-based restriction, the client specifies an application key to access a service. The client obtains the application key from the service provider.
Code Sample 3: Accessing a Service by Specifying an Application Key in the Client
http://api.local.yahoo.com/MapsService/V1/geocode?appid=jmaki-key&location=4140%20Network%20Circle,%20Santa%20Clara,%20CA,%2095054
Notice that the application ID is specified in the appid
parameter. Note too that appid
is one of the parameters mapped to the token shown in Code Sample 1.
Using application keys is a relatively developer-friendly approach to protect services. Unlike the long numeric strings that appear in some other types of keys, application IDs can be short and easily readable. However, the approach is a coarse-grained way to restrict access to services. Any application that supplies a valid application key can access the service. In fact, all jMaki widgets that access Yahoo services, as well as the XmlHttpProxy
, use the same application key.
Session-Based Restriction
In this approach, you test whether a session has been established for a client that accesses your service. You grant access to the service only if a session has been established. This is a good way of preventing access to the service across domains. This approach works because, by default, a servlet or Java EE web container creates an HttpSession
when a user navigates to any page of a web application, including a page that accesses a service. An HttpSession
is not established for clients from another domain that attempt to directly access a service. Code Sample 4 shows an example of this technique.
In session-based restriction, the client can access a service only if a session has been established for the client. This is a good way of preventing access to the service across domains.
Code Sample 4: Restricting Access by Testing in the Server For a Session
HttpSession session = req.getSession(false);
if (session == null) {
res.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
Assume that the code in Code Sample 4 is part of the code for a service. When a client requests the service, the service looks for the existence of an HttpSession
. If an HttpSession
has been established, the service allows access from the client. If an HttpSession
has not been established, as would be the case for a client from another domain trying to access the service using a technique such as JSON with Padding (JSONP), the service returns an HTTP 403 "Forbidden" error.
One limitation of this technique is that it does not guarantee that clients from other domains are restricted from accessing your service. For example, a cross-domain client could use a spoofing technique or create an interim iFrame to establish an HttpSession
. Another limitation is that you need to enable HttpSession
tracking, that is, using the HttpSession
API to record data about a session, for the approach to work. And, of course, the session must be active -- the technique won't work for sessions that time out before the test for the existence of a session.
Another consideration is that implementing this approach forces the user to log in. If the application uses a server-side proxy to access the service, the user must log in to the server site. If the application uses a dynamic script approach to access the service, the user must log in to the third-party site hosting the service. Forcing the user to log in might discourage him or her from using your application.
Content Type Restriction
Another way you can restrict cross-domain JavaScript clients from directly accessing your service is to return data only in XML format. You can do this by supporting service requests that specify XML as the only data return format, and ignore other return formats such as JSON or JSONP. You can also do this by simply defaulting to the XML format. For example, the Yahoo Maps Geocoding service supports an output
parameter that allows the user to specify the format of data returned by the service. However, it only supports output
parameter values of xml
, which is the default, or php
, a server-side HTML embedded scripting language. If a user requests the Yahoo Maps Geocoding service and specifies another output
parameter value such as jsonp
, as shown in Code Sample 5, the service returns data in the default format, XML.
In content type restriction, the service returns data in XML format only. This prevents direct JSONP access to the service.
Code Sample 5: A Client Request for the Yahoo Geocoding Service
http://api.search.yahoo.com/WebSearchService/V1/webSearch?appid=YahooDemo&query=jmaki&results=1&output=jsonp&callback=showResults
You can restrict access to your service to prevent direct JSONP access in the same way as the Yahoo Maps Geocoder does. This technique forces JavaScript clients to use a proxy such as XmlHttpProxy
to access your service.
Forcing users to use a proxy by limiting the type of content a service returns is a viable strategy if you want to restrict access to your services to a small set of users or domains.
Authentication-Based Restriction
A common way of restricting access to resources is through HTTP basic authentication or HTTP digest authentication. The servlet API provides built-in support for securing your services or other web resources through these authentication approaches. For more details on setting up HTTP basic and digest authentication, see the section Declaring Security Requirements in a Deployment Descriptor in the Java EE 5 Tutorial.
A common way of restricting access to resources is through HTTP basic authentication or HTTP digest authentication.
Consider using authentication-based restriction if you want to allow users to update data. This technique also allows you to track updates as well as enable some level of accounting. As is the case for limiting content types, authentication-based restriction is a good approach if you want to restrict access to your services to a small set of users or domains. Note too that limiting the content type and requiring authentication are quite restrictive approaches. So consider how much you want to restrict access to your services before you use these techniques.
Also note that as is the case for session-based restriction, implementing this approach forces the user to log in -- a requirement that might discourage users from using your application.
URL-Based API Key Restriction
This technique is similar to application key-based restriction except that the key, called an API key, is mapped to a specific domain and directory. That is, a client can access a service if two conditions apply:
In URL-based API key restriction, the client needs to specify a key that is mapped to the client's domain and directory. The key is obtained for that domain and directory from the service provider.
- The client specifies the correct API key as a parameter in the service request.
- The client is in the domain and directory mapped to the API key.
This is the approach that Google uses to protect its Google Maps service. First, you get an API key from Google. Then you specify the API key in the service request, such as a request for the Google Maps service as shown in Code Sample 6.
Code Sample 6: A Client Request for the Google Maps Service
<script type="text/javascript" src="http://maps.google.com/maps?file=api&v=1& key=ABQIAAAAyEQwWkLnhtibmBGdNd7 ..."></script>
Code sample 6 does not show the full API key represented by the key
value, which is a very long string. Assume that this API key was obtained for the domain and directory corresponding to the URL http://jmaki.com/samples/
. Clients from http://jmaki.com/samples/
that specify that API key can access the Google Maps service. Clients from another URL cannot access the Google Maps service using that API key.
You can use the same technique to provide fine-grained access to your services. In fact, one of the sample applications distributed with jMaki, the API Key Sample, demonstrates this technique. The application accesses a service provided with jMaki. However, before a user can access the service, the application prompts the user for the host URL from which the service will be accessed. After the user submits the URL and clicks the Get Key button, the application returns an API key for that URL, as shown in Figure 1
Figure 1: A URL-Based API Key Returned by the jMaki API Key Sample Application
The important thing here is mapping an individual user to an API key by requiring the user to create an account, as in the Google case, or requiring the user to register using an authentication-based service to generate the key, as in the case of the jMaki API Key Sample.
Restricting access based on an API key mapped to a URL allows you to restrict access to your service to specific domains and directories. In other words, you can control which hosts can access your service. Beyond that, this technique gives you a way to identify who is using your service. You can even meter your service, that is, regulate how much your service is used during a given period of time. This technique also enables you to control the level of service you provide. You can establish service level agreements with different users, and based on those agreements, deliver your service in a way that meets the response time, reliability, or availability goals specified in the agreement.
You can use URL-based API keys to provide fine-grained access to your services. The technique also gives you a way to do the following:
- Identify who is using your service.
- Meter your service, that is, regulate how much your service is used.
- Control the level of service you provide.
However, the URL-based API key approach can make it more difficult for a mashup developer to access your service because it requires the developer to get a new API key for each URL at which the mashup client runs.
Note that this technique does not guarantee that a service is protected from unwarranted access. As described in the section Limitations, spoofing and other techniques can be used to circumvent the restriction. However, using URL-based keys can deter a lot of potential misuse of a service.
To implement this approach you must generate and distribute API keys. You must also validate the API key in any request to access your service. Let's explore how to do these things.
Generating and Validating API Keys
This section describes one way to generate and distribute API keys and to validate an API key specified in a request for your service. In this approach, your service uses a hash function to generate API keys. The service also validates the API key specified in a service request by comparing it to an API key that the service generates for the client's HTTP referer. The HTTP referer identifies the source URL of the request. If the API keys match, the client gets access to the service.
To illustrate the approach, let's examine
GenericService
, a service used by the jMaki API Key Sample application. The service is implemented as a servlet. You can see the service in action by downloading the package for the sample applications -- look for a package with a name of the form jmaki-java-x.x.x.x.zip
, for example, jmaki-java-0.9.7.2.zip
-- and then building and running the samples.
Generating an API Key
Code Sample 7 shows the code in GenericService
that creates an API key.
Code Sample 7: Creating an API Key in the Server
private static String SERVER_TOKEN = "JMAKI_SERVICE";
md = MessageDigest.getInstance("MD5");
private String generateHash(String key) {
key += SERVER_TOKEN;
// start fresh
md.reset();
md.update(key.getBytes());
byte[] bytes = md.digest();
// buffer to write the md5 hash to
StringBuffer buff = new StringBuffer();
for (int l=0;l< bytes.length;l++) {
String hx = Integer.toHexString(0xFF & bytes[l]);
// make sure the hex string is correct if 1 character
if(hx.length() == 1) buff.append("0");
buff.append(hx);
}
return buff.toString().trim();
}
The GenericService
service uses the generateHash()
method to generate the key. This method is a one-way function that generates a simple value called a hash. One-way means that the method generates the hash based on some data and does it in a way that cannot be easily reverse engineered. That is, you can't easily reproduce the input data from the hash value. The input data here is the URL of the client, that is, the HTTP referer.
You should note two things about the generateHash()
method:
- It appends the
SERVER_TOKEN
to the key. The key here is the HTTP referer. Appending theSERVER_TOKEN
to the key makes the hash more difficult to reverse engineer. - It uses the MD5 Message Digest algorithm to generate the hash. The Java platform includes a
MessageDigest
class that makes it easy to implement a message digest algorithm in an application. In thegenerateHash()
method,md
is aMessageDigest
object that implements the MD5 algorithm.
After generating the key, the service returns it to the client, identifying the host domain for the key. The client can then use the key to make requests to the service, provided that the HTTP referer is the appropriate domain.
Validating an API Key
Code Sample 8 shows the code in GenericService
that validates an API key in a request for the service.
Code Sample 8: Validating an API Key in the Server
private boolean testAPIKey(HttpServletRequest req, String apiKey) {
String referer = req.getHeader("Referer");
// check if it's a relative URL and make sure we end with a slash
if (!referer.startsWith("http") &&
!referer.endsWith("/") ) referer = referer + "/";
// get the key for the host used by the request
String refererKey = generateHash(referer + SERVER_TOKEN);
if (apiKey != null) return refererKey.equals(apiKey);
else return false;
}
The GenericService
service uses the testAPIKey()
method to validate the API key. To do this, the method generates an API key for the client's HTTP referer -- it uses the generateHash
method to generate the key. The testAPIKey()
method then compares the API key generated for the HTTP referer to the API key that the client specified in the request for the service. One of the benefits of this approach is that you don't have to store API keys in the service. If the API keys match, the client can access the service -- provided that the request specifies a response format that the service supports. The GenericService
service supports three response formats: JSONP, JSON, and XML.
Assume, for example, that GenericService
is at URL http://localhost:8080/gs
and the API key generated for a client at URL http://localhost:8084/
is 123ABC
. If the client submits a request for GenericService
as shown in Code Sample 9, and submits it from http://localhost:8084/, the service grants the request.
Code Sample 9: A Valid Request for the GenericService Service From the Client
<script src="http://localhost:8080/gs?apikey=123ABC&format=jsonp"></script>
Limitations
Using API keys to restrict access has some limitations. The HTTP referer can be spoofed using a number of tools and techniques. Also, although this technique uses a one-way function that is difficult to reverse engineer, doing so is not impossible. That's true even though the API key is generated using an MD5 algorithm and an appended SERVER_TOKEN
.
Last, this approach requires that JavaScript is enabled in the client browser. If you plan to use API keys to restrict access, consider including code that checks whether JavaScript is enabled. If JavaScript is disabled, your code should gracefully degrade, that is, it should implement an alternative solution or display an error message.
Summary
There are various ways to protect an Ajax-based service. Some techniques such as application key-based restriction provide coarse-grained protection but are relatively easy for developers to use. Other techniques such as URL-based API key restriction provide a finer level of access control, but they can be more difficult for developers to use. It's important to compare the benefits and limitations of these approaches and choose the approach that best serves your needs.
For More Information
- Asynchronous JavaScript Technology and XML (Ajax) With the Java Platform
- Mashup Styles, Part 1: Server-Side Mashups
- Mashup Styles, Part 2: Client-Side Mashups
- Yahoo Maps Web Services - Geocoding API
About the Authors
Greg Murray is the Ajax Architect for Sun Microsystems. He is deeply involved in the Ajax movement through his participation in the OpenAJAX Alliance and contributions to the Dojo Foundation's open-source JavaScript toolkit. Within Sun, Greg lead a grassroots effort advancing the integration of client-side scripting with Java technologies and is the creator and principal architect of Project jMaki. Greg contributed to the design and development of the Ajax-based Java Pet Store Demo 2.0 and helped create the Java BluePrints Solutions Catalog.
Ed Ort is a staff member of JSC. He has written extensively about relational database technology, programming languages, web services, and Ajax.