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.

Thursday, April 10, 2014

Using ServletFilter to authenticate a user's Session

Servlet Filters are interceptors for requests and responses to transform or use the information in the request or response. Filters normally do not create a response themselves, instead, they provide global functions that can be attached to any web resources.

Filters are very important for a number of reasons. First of all, they provide a modular way to create units of functionality that can be reused, and secondly they can be used to transform the data in request or response.

Filters can perform manay different types of functions, including but not limited to :

  • Authenticating the requests
  • Logging and auditing
  • Data Transformation
  • Data Compression
  • Localization
In this example I am going to try to explain Filters by developing a simple filter that verifies that the user still has a valid session. If the session is expired then the Filter will block the request from going any further and redirect the user to the login page.

Let’s base our Filter on the following assumptions

  • We have a session bean called UserBean
  • The bean is stored in the session as user
  • The login page authenticates the user
  • The login page creates and stores a UserBean in session
  • We will always have a user variable in session
Based on the following assumptions, all we need to do is to make sure that we have an instance of UserBean in the session variable user with some valid user credentials.

This is what my Servlet Filter looked like when I first created it. All we need to do is to implement the Filter interface and override the doFilter() method. Whatever we want to do with that filter would be provided in this method.


public class SessionFilter implements Filter {

 @Override
 public void doFilter(ServletRequest req, ServletResponse res,
   FilterChain chain) throws IOException, ServletException {

  String sessionExpired = "/mywebproject/sessionExpired.html";

  try {

   HttpServletRequest request = (HttpServletRequest) req;
   HttpServletResponse response = (HttpServletResponse) res;

   HttpSession session = request.getSession(false);
   if(null == session){
   
    request.getRequestDispatcher("/sessionExpired.html").forward(request, response);
    
   }else{
   
    UserBean ub = (UserBean)session.getAttribute("user");
    if(null == ub){
    
     response.sendRedirect(sessionExpired);
     
    }else if (ub.getUserID() == null){
    
     response.sendRedirect(sessionExpired);
    }
   }

  } catch (Exception f) {
   f.printStackTrace();
  }
  chain.doFilter(req, res);
 }
}

Here we are simply checking for the session if we have a valid UserBean object in the session. If we have an object in the session then we validate the object if it contains a user id. If everything is fine then we continue the process and pass the process flow to the next filter in the chain. If not then redirect the control to the session expired page to let the users know that the session is no longer valid and they have to login to the system again.

Ignored Resources:
There is one problem though; this filter was not working because it was redirecting even the login page to the session expired page. This is because the filter is invoked every time we send a request to the server and a request to the login page will never have a user object in the session on our first request. As a result the system becomes un-usable.

There are a few ways to deal with this issue; the easiest would be to get the context path from the request and avoid any session checks if the context path contains the name of your login page.

The clean way however is to provide a list of ignored resources in the web.xml configuration file. This is what the configuration file looks like when we provide ignored resources:


 <filter>
     <filter-name>SessionFilter</filter-name>
     <filter-class>com.mywebproject.common.filter.SessionFilter</filter-class>
     <init-param>
         <param-name>avoid-urls</param-name>
         <param-value>login</param-value>
     </init-param>     
 </filter>
 <filter-mapping>
     <filter-name>SessionFilter</filter-name>
     <url-pattern>/pages/*</url-pattern>
 </filter-mapping>
 

The avoid-urls parameter tag initializes the Filter with this list of URLs that the filter will avoid. Unfortunately this is simply an indicator here and you still need to provide an implementation for the filter to avoid the URLs provided in this list.

Note: You can provide more than one comma separated URLs in the param-value parameter.

Now we need to provide an implementation to avoid these URLs in our Filter. This is how the updated SessionFilter looked like after adding the functionality to load and then avoid the URLs provided in the web.xml configuration file.


package com.mywebproject.common.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.mywebproject.user.bean.UserBean;

public class SessionFilter implements Filter {
 
 private ArrayList<String> urlList;

 @Override
 public void init(FilterConfig config) throws ServletException {

  urlList = new ArrayList<String>();
  String urls = config.getInitParameter("avoid-urls");
  StringTokenizer token = new StringTokenizer(urls, ",");
  
  while (token.hasMoreTokens()) {
   urlList.add(token.nextToken());

  } 
 }
 
 @Override
 public void doFilter(ServletRequest req, ServletResponse res,
   FilterChain chain) throws IOException, ServletException {

  String sessionExpired = "/mywebproject/sessionExpired.html";

  try {

   HttpServletRequest request = (HttpServletRequest) req;
   HttpServletResponse response = (HttpServletResponse) res;
   
   String url = request.getServletPath();
   boolean allowedRequest = false;
   for (String allowed: urlList) {
    
    if(url.contains(allowed)){
     allowedRequest = true;
     break;
    }
   }

   if(!allowedRequest){

    HttpSession session = request.getSession(false);
    if(null == session){
    
     request.getRequestDispatcher("/sessionExpired.html").forward(request, response);
     
    }else{
    
     UserBean ub = (UserBean)session.getAttribute("user");
     if(null == ub){
     
      response.sendRedirect(sessionExpired);
      
     }else if (ub.getUserID() == null){
     
      response.sendRedirect(sessionExpired);
     }
    }

   }

  } catch (Exception f) {
   f.printStackTrace();
  }
  chain.doFilter(req, res);
 }
}

This Filter will now avoid checking for a valid session for any requests for the login page. Other than that page, it will always look for a valid session and a valid logged in user. If the session is expired or if there is no valid UserBean in the session then the Filter will not allow the request to go ahead and will redirect the user to the login page.

It is just one example of how the filters can be used. As mentioned in the beginning of this post, the filters can be used for all sorts of tasks that you want to perform on all or probably most of requests coming to your server.