Pages

Thursday, April 17, 2014

Securing RESTful APIs with HTTP Basic Authentication

HTTP Basic Authentication is the simplest way for a HTTP User Agent to provide a username and password to the web server to enforce access control of the resources. The Basic Authentication method provides no confidentiality and the credentials are transmitted as merely Base64 encoded string. Therefore, this method of authentication is typically used over HTTPS for added security.

The user credentials are sent using the Authorization header. The header is constructed as follows:

  • Combine username and password into a string “username:password”
  • Encode the resulting string in a Base64 variant
  • Prefix the encoded string with “Basic ” ; notice the space here
These encoded credentials are then sent over to the server. On the server side you can then extract these credentials and use them to authenticate the user.

In this example, I shall create a very simple RESTful web service and a very simple java client that will call this restful web service with an HTTP Authorization header. Let's start with creating a RESTful web resource that extracts the authentication data from the HTTP Header and returns the decoded credentials as simple text back to the client.


package com.test.service;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import sun.misc.BASE64Decoder;

@Path("auth")
public class TestHTTPAuthService {
 
 @Context
 private HttpServletRequest request;
 
 @GET
 @Path("basic")
 @Produces(MediaType.TEXT_PLAIN)
 public String authenticateHTTPHeader(){
  
  String decoded;
  try{
   // Get the Authorisation Header from Request
   String header = request.getHeader("authorization");
   
   // Header is in the format "Basic 3nc0dedDat4"
   // We need to extract data before decoding it back to original string
   String data = header.substring(header.indexOf(" ") +1 );
   
   // Decode the data back to original string
   byte[] bytes = new BASE64Decoder().decodeBuffer(data);
   decoded = new String(bytes);
   
   System.out.println(decoded);
   
  }catch(Exception e){
   e.printStackTrace();
   decoded = "No/Invalid authentication information provided";
  }
  
  return decoded;
 }
}

We know the format of data in the authorization header and thus we can extract the encoded part of the data and then decode it to get the credentials. This service is simply returning the decoded credentials back to the caller.

Now let’s create a simple java class that will call this service. We will send the username and password in the HTTP Header and printout the result from the service.


package servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

import sun.misc.BASE64Encoder;

public class BasicHTTPAuthentication {

 public static void main(String[] args) {

  try {
   
   String webPage = "http://localhost:8080/TESTService/resource/auth/basic";
   String name = "Aladdin";
   String password = "Open Sesame";

   String authString = name + ":" + password;
   System.out.println("Auth string: " + authString);
   
   String authStringEnc = new BASE64Encoder().encode(authString.getBytes());
   System.out.println("Base64 encoded auth string: " + authStringEnc);

   URL url = new URL(webPage);
   URLConnection urlConnection = url.openConnection();
   urlConnection.setRequestProperty("Authorization", "Basic " + authStringEnc);
   InputStream is = urlConnection.getInputStream();
   InputStreamReader isr = new InputStreamReader(is);

   int numCharsRead;
   char[] charArray = new char[1024];
   StringBuffer sb = new StringBuffer();
   while ((numCharsRead = isr.read(charArray)) > 0) {
    sb.append(charArray, 0, numCharsRead);
   }
   String result = sb.toString();

   System.out.println("---------------------------------------------");
   System.out.println("Response from the server: " + result);
   
  } catch (MalformedURLException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

Here we are simply converting and encoding the credentials according to the HTTP guidelines. After that we are using the setRequestProperty() to add these values to an authorization header. Once the header is set, we then send this request to the service which can extract the credentials form the request and sends them bac as a response.

Below is the console output when we run this code:


Auth string: Aladdin:Open Sesame
Base64 encoded auth string: QWxhZGRpbjpPcGVuIFNlc2FtZQ==
---------------------------------------------
Response from the server: Aladdin:Open Sesame

The service side of the code is simply extracting the credentials from the HTTP Header and writing them in the log file before generating a response, which is also nothing more than the decoded credentials.

Below is an extract from the log files showing the entry logged by this service:

[glassfish 4.0] [INFO] [tid: _ThreadID=22 _ThreadName=Thread-3] [[Aladdin:Open Sesame]]

This is a very simple example to show the HTTP Basic Authentication using the HTTP Authorization headers. This is not a recommended approach by any means. You can clearly see more than a few flaws in this approach, not to mention how easy it is to decode the credentials.

However, simply adding the added security of HTTPS over TLS, you significantly improve the level of security of this simple authentication mechanism.

Another improvement would be to use Servlet Filters instead of authentication credentials in every service/resource. You can intercept the request to a resource and authenticate the user even before it reaches anywhere near the resource. Feel free to browse my previous post on Using Servlet Filters for a demonstration on how the servlet filters can be used to intercept request and responses.

1 comment: