Security is a complex topic which goes far beyond the scope of HAPI FHIR. HAPI does provide mechanisms which can be used to implement security in your server however.

Because HAPI FHIR's REST server is based on the Servlet API, you may use any security mechanism which works in that environment. Some serlvet containers may provide security layers you can plug into. The rest of this page does not explore that method, but rather looks at HAPI FHIR hooks that can be used to implement FHIR specific security.

Authentication vs Authorization

Background reading: Wikipedia - Authentication

Server security is divided into two topics:

  • Authentication (AuthN): Is verifying that the user is who they say they are. This is typically accomplished by testing a username/password in the request, or by checking a "bearer token" in the request.
  • Authorization (AuthZ): Is verifying that the user is allowed to perform the given action. For example, in a FHIR application you might use AuthN to test that the user making a request to the FHIR server is allowed to access the server, but that test might determine that the requesting user is not permitted to perform write operations and therefore block a FHIR create operation. This is AuthN and AuthZ in action.

Authentication Interceptors

The Server Interceptor framework can provide an easy way to test for credentials. The following example shows a simple interceptor which tests for HTTP Basic Auth.

public class BasicSecurityInterceptor extends InterceptorAdapter
{

   /**
    * This interceptor implements HTTP Basic Auth, which specifies that
    * a username and password are provided in a header called Authorization.
    */
   @Override
   public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
      String authHeader = theRequest.getHeader("Authorization");
      
      // The format of the header must be:
      // Authorization: Basic [base64 of username:password]
      if (authHeader == null || authHeader.startsWith("Basic ") == false) {
         throw new AuthenticationException("Missing or invalid Authorization header");
      }
      
      String base64 = authHeader.substring("Basic ".length());
      String base64decoded = new String(Base64.decodeBase64(base64));
      String[] parts = base64decoded.split("\\:");
      
      String username = parts[0];
      String password = parts[1];
      
      /*
       * Here we test for a hardcoded username & password. This is 
       * not typically how you would implement this in a production
       * system of course..
       */
      if (!username.equals("someuser") || !password.equals("thepassword")) {
         throw new AuthenticationException("Invalid username or password");
      }
      
      // Return true to allow the request to proceed
      return true;
   }

   
}

HTTP Basic Auth

Note that if you are implementing HTTP Basic Auth, you may want to return a WWW-Authenticate header with the response. The following snippet shows how to add such a header with a custom realm:

AuthenticationException ex = new AuthenticationException();
ex.addAuthenticateHeaderForRealm("myRealm");
throw ex;

Authorization Interceptor

HAPI FHIR 1.5 introduced a new interceptor, the AuthorizationInterceptor.

This interceptor can help with the complicated task of determining whether a user has the appropriate permission to perform a given task on a FHIR server. This is done by declaring a set of rules that can selectively allow (whitelist) and/or selectively block (blacklist) requests.

AuthorizationInterceptor is a new feature in HAPI FHIR, and has not yet been heavily tested. Use with caution, and do lots of testing! We welcome feedback and suggestions on this feature. In addition, this documentation is not yet complete. More examples and details will be added soon! Please get in touch if you'd like to help test, have suggestions, etc.

The AuthorizationInterceptor works by allowing you to declare permissions based on an individual request coming in. In other words, you could have code that examines an incoming request and determines that it is being made by a Patient with ID 123. You could then declare that the requesting user has access to read and write any resource in compartment "Patient/123", which corresponds to any Observation, MedicationOrder etc with a subject of "Patient/123". On the other hand, another request might be detemrined to belong to an administrator user, and could be declared to be allowed to do anything.

The AuthorizationInterceptor is used by subclassing it and then registering your subclass with the RestfulServer. The following example shows a subclassed interceptor implementing some basic rules:

   public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor {
      
      @Override
      public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
         
         // Process authorization header - The following is a fake 
         // implementation. Obviously we'd want something more real
         // for a production scenario.
         // 
         // In this basic example we have two hardcoded bearer tokens,
         // one which is for a user that has access to one patient, and
         // another that has full access. 
         IdDt userIdPatientId = null;
         boolean userIsAdmin = false;
         String authHeader = theRequestDetails.getHeader("Authorization");
         if ("Bearer dfw98h38r".equals(authHeader)) {
            // This user has access only to Patient/1 resources
            userIdPatientId = new IdDt("Patient", 1L);
         } else if ("Bearer 39ff939jgg".equals(authHeader)) {
            // This user has access to everything
            userIsAdmin = true;
         } else {
            // Throw an HTTP 401
            throw new AuthenticationException("Missing or invalid Authorization header value");
         }

         // If the user is a specific patient, we create the following rule chain:
         // Allow the user to read anything in their own patient compartment
         // Allow the user to write anything in their own patient compartment
         // If a client request doesn't pass either of the above, deny it
         if (userIdPatientId != null) {
            return new RuleBuilder()
               .allow().read().allResources().inCompartment("Patient", userIdPatientId).andThen()
               .allow().write().allResources().inCompartment("Patient", userIdPatientId).andThen()
               .denyAll()
               .build();
         }
         
         // If the user is an admin, allow everything
         if (userIsAdmin) {
            return new RuleBuilder()
               .allowAll()
               .build();
         }
         
         // By default, deny everything. This should never get hit, but it's 
         // good to be defensive
         return new RuleBuilder()
            .denyAll()
            .build();
      }
   }

Using AuthorizationInterceptor in a REST Server

The AuthorizationInterceptor works by examining the client request in order to determine whether "write" operations are legal, and looks at the response from the server in order to determine whether "read" operations are legal.

Authorizing Read Operations

When authorizing a read operation, the AuthorizationInterceptor always allows client code to execute and generate a response. It then examines the response that would be returned before actually returning it to the client, and if rules do not permit that data to be shown to the client the interceptor aborts the request.

Note that there are performance implications to this mechanism, since an unauthorized user can still cause the server to fetch data even if they won't get to see it. This mechanism should be comprehensive however, since it will prevent clients from using various features in FHIR (e.g. _include or _revinclude) to "trick" the server into showing them date they shouldn't be allowed to see.

See the following diagram for an example of how this works.

Write Authorization

Authorizing Write Operations

Write operations (create, update, etc.) are typically authorized by the interceptor by examining the parsed URL and making a decision about whether to authorize the operation before allowing Resource Provider code to proceed. This means that client code will not have a chance to execute and create resources that the client does not have permissions to create.

See the following diagram for an example of how this works.

Write Authorization

Authorizing Sub-Operations

There are a number of situations where the REST framework doesn't actually know exactly what operation is going to be performed by the implementing server code. For example, if your server implements a conditional update operation, the server might not know which resource is actually being updated until the server code is executed.

Because client code is actually determining which resources are being modified, the server can not automatically apply security rules against these modifications without being provided hints from client code.

In this type of situation, it is important to manually notify the interceptor chain about the "sub-operation" being performed. The following snippet shows how to notify interceptors about a conditional create.

   @Update()
   public MethodOutcome update(
         @IdParam IdDt theId, 
         @ResourceParam Patient theResource, 
         @ConditionalUrlParam String theConditionalUrl, 
         RequestDetails theRequestDetails) {

      // If we're processing a conditional URL...
      if (isNotBlank(theConditionalUrl)) {
         
         // Pretend we've done the conditional processing. Now let's
         // notify the interceptors that an update has been performed
         // and supply the actual ID that's being updated
         IdDt actual = new IdDt("Patient", "1123");
         
         // There are a number of possible constructors for ActionRequestDetails.
         // You should supply as much detail about the sub-operation as possible
         IServerInterceptor.ActionRequestDetails subRequest = 
               new IServerInterceptor.ActionRequestDetails(theRequestDetails, actual);
         
         // Notify the interceptors
         subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE);
      }
      
      // In a real server, perhaps we would process the conditional 
      // request differently and follow a separate path. Either way,
      // let's pretend there is some storage code here.
      
      theResource.setId(theId.withVersion("2"));
      MethodOutcome retVal = new MethodOutcome();
      retVal.setCreated(true);
      retVal.setResource(theResource);
      return retVal;
   }

Back to top

Reflow Maven skin by Andrius Velykis.