Oct 3 2013

Implement your 2-legged OAuth server with Java Restlet

In the previous post we’ve been looking into how we can implement a simple Java client to access resources protected via 2-legged OAuth.
This time we’ll focus on the server side code needed to develop a 2-legged OAuth server that enables your REST API’s resources to be protected with the same method.

In order to do so, we’ll use Restlet, a Java framework to create RESTful web services, and again Scribe, the simple OAuth Java library for the validation part. The code presented here has been tested and is ready to be used in your Restlet application.

Quick introduction to Restlet

A detailed documentation of Restlet framework can be found in the official website.
Restlet associates URLs or URLs patterns to so-called resources. Every resource must inherit from org.restlet.resource.ServerResource, and its public methods (along with the needed annotations that define which HTTP method has to be used to access them, the expected content type and eventually other aspects) are called accordingly to provide a response.
The implementation presented below will subclass org.restlet.resource.ServerResource into an abstract class that will intercept all the calls done to whatever subclass will inherit from it. Such OAuthProtectedResource class will extract all the OAuth related parameters (assuming in this case that they are passed as GET query parameters), retrieve also the secret key of the user issuing the request and validats the request against the OAuth signature provided within the request itself. If the signature is proved to be valid (and the request is not too old or has not been replayed), the request is passed and served by the method of the subclass.

A 2-legged OAuth server implementation

Let’s have a look at the implementation of OAuthProtectedResource class, the first step towards our Java 2-legged OAuth server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import java.util.Map;
import org.restlet.data.Parameter;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.Variant;
import org.restlet.resource.ResourceException;
import org.restlet.resource.ServerResource;
import org.restlet.util.Series;
import org.scribe.model.Verb;
 
import api.oauth.OAuthRequestValidator;
 
/**
 * This class represents an abstract resource that is protected by 2-legged OAuth.
 * It intercepts every incoming request, extracts the OAuth parameters and computes the
 * HMAC hash. 
 * If the hash matches with the one sent along with the request the request is served, 
 * otherwise it returns a 403 - Forbidden.
 *
 */
public abstract class OAuthProtectedResource extends ServerResource {
 
	static final String TIMESTAMP_QUERY_PARAM = "oauth_timestamp";
	static final String NONCE_QUERY_PARAM = "oauth_nonce";
	static final String SIGNATURE_METHOD_QUERY_PARAM = "oauth_signature_method";
	static final String VERSION_QUERY_PARAM = "oauth_version";
	static final String CONSUMER_KEY_QUERY_PARAM = "oauth_consumer_key";		
	static final String SIGNATURE_QUERY_PARAM = "oauth_signature";
 
	static final String SECRET_KEY_HEADER = "3scale-app-key";
 
	private OAuthRequestValidator oauthValidator; 
 
	public OAuthProtectedResource() {
		oauthValidator = OAuthRequestValidator.getInstance();
	}
 
	OAuthProtectedResource(OAuthRequestValidator oauthValidator) {
		this.oauthValidator = oauthValidator;
	}
 
	@Override
	protected Representation doHandle(Variant variant) throws ResourceException {
		doOAuthHMACValidation();
		return super.doHandle(variant);
	}
 
	private void doOAuthHMACValidation() {
 
		// Check that all the needed parameters are contained in the request
		Map<String,String> queryValues = getQuery().getValuesMap();
		if (!queryValues.containsKey(TIMESTAMP_QUERY_PARAM) ||
			!queryValues.containsKey(NONCE_QUERY_PARAM) ||
			!queryValues.containsKey(SIGNATURE_METHOD_QUERY_PARAM) ||
			!queryValues.containsKey(VERSION_QUERY_PARAM) ||
			!queryValues.containsKey(CONSUMER_KEY_QUERY_PARAM) ||
			!queryValues.containsKey(SIGNATURE_QUERY_PARAM))
			throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Missing OAuth credentials");
 
		// Validate the request
		boolean isValid = oauthValidator.validate(
				Verb.valueOf(getMethod().getName()), 
				getReference().toString(false, false),
				getQuery().getFirstValue(TIMESTAMP_QUERY_PARAM),
				getQuery().getFirstValue(NONCE_QUERY_PARAM),
				getQuery().getFirstValue(SIGNATURE_METHOD_QUERY_PARAM),
				getQuery().getFirstValue(VERSION_QUERY_PARAM),
				getAccessKey(), 
				getSecretKey(), 
				getQuery().getFirstValue(SIGNATURE_QUERY_PARAM));
 
		if (!isValid)
			throw new ResourceException(Status.CLIENT_ERROR_FORBIDDEN, "Wrong credentials");
	}
 
	/**
	 * Extract the API secret key from the following request
	 * @return the secret key of the request
	 */
	abstract String getSecretKey();
 
	/**
	 * Extract the API public key from the following request
	 * @return the public key of the request
	 */
	String getAccessKey() {
		return getQuery().getFirstValue(CONSUMER_KEY_QUERY_PARAM);
	}
}

The key part lies on overriding the doHandle(Variant variant) method. This method is called each time a request needs to be handled by the resource, therefore it represents the ideal place where we can put our OAuth validation mechanism.
Please note that org.restlet.resource.ServerResource contains multiple doHandle(…) methods, and depending on the nature of the requests served by your resource, you may need to override some of the others as well.

Let’s move now our attention to the OAuthRequestValidator class. This class will take all the request parameters and decide whether to validate it or not.
As the specification dictates, 2-legged OAuth has two additional checks before considering a request valid:

  • Each request has a nonce field that contains a random generated number that is also used to compute the request hash: once the request is validated, the server stores the nonce and check subsequent requests to detect whether it is reused again. This technique is used to avoid replay attacks.
  • Requests have a timestamp field (which also takes part into the hash computation) that is checked to avoid old requests to be considered.

The class will then use some sort of requests cache (modelled by a subclass of AbstractRequestsFootprintCache) to maintain a datastore of the requests already served.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.OAuthConstants;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Verb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import api.exception.CacheException;
 
public class OAuthRequestValidator {
 
	static final int TIMESTAMP_VALIDITY_IN_SECS = 120;
 
	private static final OAuthRequestValidator instance = new OAuthRequestValidator();
 
	private Logger log;
	private AbstractRequestsFootprintCache requestsCache;
 
	private OAuthRequestValidator() {
		requestsCache = LocalRequestsFootprintCache.getInstance();
		log = LoggerFactory.getLogger(OAuthRequestValidator.class);
	}
 
	OAuthRequestValidator(AbstractRequestsFootprintCache requestsCache, Logger log) {
		this.requestsCache = requestsCache;
		this.log = log;
	}
 
	public static final OAuthRequestValidator getInstance() {
		return instance;
	}
 
	public boolean validate(Verb verb, String url, String timestamp, String nonce, String signatureMethod,
							String version, String publicKey, String secretKey, String requestSignature) {
 
		DefaultApi10a api = new DummyAPIProvider();
 
		// Check if the request is too old
		long seconds = (System.currentTimeMillis() / 1000L) - Long.valueOf(timestamp); 
		if (seconds < 0 || seconds > TIMESTAMP_VALIDITY_IN_SECS) {
			log.warn("Received old request:\n" + 
					 formatRequest(verb, url, timestamp, nonce, signatureMethod, version, publicKey, requestSignature));
			return false;
		}
 
		// Check if the request has already been sent once
		boolean replayedRequest;
		try {
			 replayedRequest = requestsCache.isCachedOrPutInCache(timestamp, nonce, publicKey);
		} catch (CacheException e) {
			// If the cache is not working properly, consider the request as not replayed
			replayedRequest = false;
		}
		if (replayedRequest) {
			log.warn("Received replayed request:\n" + 
					 formatRequest(verb, url, timestamp, nonce, signatureMethod, version, publicKey, requestSignature));
			return false;
		}
 
		OAuthRequest request = new OAuthRequest(verb, url);
		// add the necessary parameters
	        request.addOAuthParameter(OAuthConstants.TIMESTAMP, timestamp);
	        request.addOAuthParameter(OAuthConstants.NONCE, nonce);
	        request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, publicKey);
	        request.addOAuthParameter(OAuthConstants.SIGN_METHOD, signatureMethod);
	        request.addOAuthParameter(OAuthConstants.VERSION, version);
 
		String baseString = api.getBaseStringExtractor().extract(request);
		// Passing an empty token, as the 2-legged OAuth doesn't require it
		String realSignature = api.getSignatureService().getSignature(baseString, secretKey, ""); 
 
		return (requestSignature.compareTo(realSignature) == 0);
	}
 
	private String formatRequest(Verb verb, String url, String timestamp, String nonce, String signatureMethod,
			String version, String publicKey, String requestSignature) {
 
		return new StringBuffer()
			.append(verb.name()).append(" ").append(url)
			.append("\n\ttimestamp: ").append(timestamp)
			.append("\n\tnonce: ").append(nonce)
			.append("\n\tsignature method: ").append(signatureMethod)
			.append("\n\tversion: ").append(version)
			.append("\n\tpublic key: ").append(publicKey)
			.append("\n\trequest signature: ").append(requestSignature)
			.toString();	
	}
}

A quick look into AbstractRequestsFootprintCache and in a possible concrete implementation for it will end our example of 2-legged OAuth server in Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * This abstract class represents a datastore that keeps tuples of (timestamp, nonce, publicKey).
 * The data is used by OAuth to prevent replay attacks.
 *
 */
public abstract class AbstractRequestsFootprintCache {
 
	public abstract boolean isCachedOrPutInCache(String timestamp, String nonce, String publicKey) throws CacheException;
 
	String getKey(String timestamp, String nonce, String publicKey) {
		return new StringBuffer()
				.append(timestamp).append("_")
				.append(nonce).append("_")
				.append(publicKey)
				.toString();
	}
}

The concrete implementation that follows is just a proof of concept, as it keeps track of the requests in a local in-memory cache via . This approach is clearly unsafe and not scalable and it should never be used in production code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.concurrent.TimeUnit;
 
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
 
public class LocalRequestsFootprintCache extends AbstractRequestsFootprintCache {
 
	private Cache<String, Boolean> localCache;
 
	private static final AbstractRequestsFootprintCache instance = new LocalRequestsFootprintCache();
 
	public static AbstractRequestsFootprintCache getInstance() {
		return instance;
	}
 
	private LocalRequestsFootprintCache() {
 
		localCache = CacheBuilder.newBuilder()
			       		.expireAfterWrite(3, TimeUnit.MINUTES)
			       		.build();
	}
 
	public boolean isCachedOrPutInCache(String timestamp, String nonce,	String publicKey) {
 
		String cacheKey = getKey(timestamp, nonce, publicKey);
		if (localCache.getIfPresent(cacheKey) != null)
			return true;
 
		localCache.put(cacheKey, true);
		return false;
 
	}
 
}

You would probably want to store the requests using a more reliable solution: depending on your system architecture a key-value NoSQL, Memcached or a traditional RDBMS could do the job.

What’s left to be done to have your OAuth protected API

In order for you to implement your own OAuth protected resources, you just need to subclass OAuthProtectedResource and provide an implementation for AbstractRequestsFootprintCache.
The code above can be functionally tested by using the 2-legged OAuth Java client presented in the previous post.
All the code presented has been unit tested: if you’re interested in having such tests, or for any other question, feel free to contact me.

About

Globetrotter Software Engineer. I try to conjugate where the ambitions lead me with an environment that makes me feel happy. I love sun and sea. My mind is always spinning, for good and for bad. I enjoy traveling and experiencing new places by being constantly surprised by things I would have never even conceived.

2 comments on “Implement your 2-legged OAuth server with Java Restlet

    • Hi Biagio, thanks for pointing this out!
      Actually, it is just a user-defined exception I’ve created for the occasion. For the sake of clarity, I’ve renamed it with something more meaningful.

Leave a Reply

Your email address will not be published. Required fields are marked *