[HttpServer Series] Authentication and Role based Authorization (RBAC)
Tags: java, learning, frameworks
Published: 2024-02-18 12:37:08

---
title: [HttpServer Series] Authentication  and Role based Authorization (RBAC)
published: true
date: 2024-02-17 10:52:06 UTC
tags: java, learning, frameworks 
series: Project No-Web-Framework-No-Annotations using Java
canonical_url: https://blog.mandraketech.in/httpserver-series-authentication-and-role-based-authorization-rbac
---

In the final step in the process of building out an HTTP based application is to put in place a Role based Access Control mechanism. So, in this article, I am going to walk you through the 20 odd commits, over 15+ days of brooding, exploring technology options, re-evaluating current choices, building and throwing away code, all with the single objective, where it all started. Build a web application with as little magic, third-party libraries and annotations, as possible. Stay true to writing more of Java and less of learning other (as I call it) Annotation based languages.

You can see the detailed [merge request](https://gitlab.com/mandraketech/httpserver-based-todo-app/-/merge_requests/10) for the code changes, the [repository for the entire code](https://gitlab.com/mandraketech/httpserver-based-todo-app) or the [series for all the articles](https://blog.mandraketech.in/series/java-http-server) on the journey.

## Getting rid of the `Router`

The `Router` we built over the last few articles, gave us more control, but also ended up duplicating a lot of work already put in the HttpServer's core API design. So, I experimented with converting its functionality into an implementation of HttpHandler, that acted as a way to simplify the handling of the HttpExchange and other complexities discussed [earlier in the series](https://blog.mandraketech.in/httpserver-series-the-http-router-from-scratch).

The HttpServer already has a mechanism to set up Authenticator against each `HttpContext` . Its a two step process:

* Create the Context based on a path
    
* Associate a Authenticator with the Context
    

And, when dealing with a lot of contexts, the association code can get repetitive. So, in comes `RouteBuilder` , a fluent api design to wire up apis, and Authenticator, into the context. Here is the updated code in `App.java` with the use of this api now:

```java
RouteBuilder.withServer(server)
     .createContext("/", new StaticFileHandler("src/main/resources", true)::handle);
            
RouteBuilder.withServer(server)
    .createContext("/api/ping", PingHandler::handle);

RouteBuilder.withServer(server)
    .setAuthenticator(userRoleBasicAuthenticator)
    .createContext("/api/user/", UserHandler::handle)
    .createContext("/api/admin", AdminHandler::handle);
```

You can see how the `/api` endpoints are getting wired into a `BasicAuthenticator` that understands User Roles. More on that in a bit.

This design allows space for differentiating between the Authentication for the API context (Basic), and the WebApp (Session based, when we build it).

## Authentication, and Roles

Before we talk about the authenticator used in the code example above, let's look at one of the key requirements for any application, a Role based Authorization system. In almost all cases, there are roles assigned to each user that has login rights. And different parts of the application are available based on their roles.

The `HttpPrincipal` class from the HttpServer package does not have that feature. So, I built a `UserRolePrincipal` which looks like this:

```java
public class UserRolePrincipal extends HttpPrincipal {

  private final List<String> roles;

  public UserRolePrincipal(final String username, final String realm, final List<String> roles) {
    super(username, realm);
    this.roles = Collections.unmodifiableList(roles);
  }
  
  public List<String> getRoles() {
    return roles;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime * result + ((roles == null) ? 0 : roles.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (!super.equals(obj))
      return false;
    return (obj instanceof UserRolePrincipal other && roles.equals(other.roles));
  }
}
```

As you can see, it only adds the roles to the base class. Relies on it for everything else.

To construct this `Principal` object, we need a corresponding Authenticator. I wanted the new Authenticator to be completely compatible with the HttpServer api. No reason to anything otherwise. We are just adding a "Role", right. So, I designed it as a Decorator. Here is the code for the `UserRoleAuthenticator` :

```java
public abstract class UserRoleAuthenticator extends Authenticator {

  final Authenticator authenticator;

  protected UserRoleAuthenticator(Authenticator authenticator) {
    Objects.requireNonNull(authenticator);
    this.authenticator = authenticator;
  }

  /**
   * @return a list of roles for the given username, never null
   */
  public abstract List<String> getUserRoles(String username);

  @Override
  public Result authenticate(HttpExchange exchange) {
    var result = this.authenticator.authenticate(exchange);

    if (result instanceof Success s) {
      var p = s.getPrincipal();
      var userRolePrincipal = new UserRolePrincipal(p.getUsername(), p.getRealm(), getUserRoles(p.getUsername()));
      return new Success(userRolePrincipal);
    }

    return result;
  }
}
```

## The Authenticator

With these two in place, in the core router functionality, we now need the Authenticator be instantiated. The `AuthenticatorFactory` will help create various types of those. To start with, only a Role enabled BasicAuth one.

```java
public interface AuthenticatorFactory {

  public static UserRoleAuthenticator createBasicAuthenticator(UserRepository repository) {
    Objects.requireNonNull(repository, "If UserRepository is null, authentication cannot work");

    var basicAuth = new BasicAuthenticator("sample") {
      @Override
      public boolean checkCredentials(String username, String password) {
        return repository.checkCredentials(username, password);
      }
    };

    return new UserRoleAuthenticator(basicAuth) {    
      @Override
      public List<String> getUserRoles(String username) {
        return repository.fetchRoleList(username);
      }
    };
  }
}
```

In case you have not already figured out, the `UserRepository` is expected to be wired up to the right place. Currently hard coded.

```java
public final class UserRepository {
  final Map<String, String> userCredMap;
  final Map<String, List<String>> userRolesMap;
  
  @SuppressWarnings("java:S1192")
  public UserRepository() {
    userCredMap = Map.of("admin", "admin", "user", "user");
    userRolesMap = Map.of("admin", List.of("ADMIN", "USER"), "user", List.of("USER"));
  }

  /**
   * checks for credential validity
   * @param username
   * @param password
   * @return
   */
  public boolean checkCredentials(String username, String password) {
    return userCredMap.containsKey(username) && userCredMap.get(username).equals(password);
  }

  /**
   * Assumes that the checkCredentials has been called earlier
   * @param username
   * @return
   */
  public List<String> fetchRoleList(String username) {
    return userRolesMap.get(username);
  }
}
```

## The Admin endpoint

The Admin endpoint is where the functionality can be seen in action, and of course the User endpoint too.

```java
  static RouteResponse handle(RouteRequest req) {
    if (!req.hasUserRole("ADMIN")) {
      return RouteResponse.ErrorResponses.UNAUTHORIZED_ENDPOINT_RESPONSE.toValue();
    }

    if (req.method() == RequestMethod.GET) {
      return RouteResponse.withStatus(200).withHtmlBody("<html><body>Hello Admin %s</body></html>".formatted(req.principal().getUsername()));
    }
    return null;
  }
```

## The User endpoint

The User Endpoint has been enhanced to test for a "nested" endpoint, `/api/user/me` and have the appropriate behaviour in other cases. A good example of how deeper urls can be handled.

```java
  static RouteResponse handle(RouteRequest req) {
    if (!req.hasUserRole("USER")) {
      return RouteResponse.ErrorResponses.UNAUTHORIZED_ENDPOINT_RESPONSE.toValue();
    }

    if (req.method() == RequestMethod.GET && req.uri().getPath().equals("me")) {
      return RouteResponse.withStatus(200).withHtmlBody("<html><body>Hello %s</body></html>".formatted(req.principal().getUsername()));
    }
    return null;
  }
```

### Updating the Tests

As we got rid of the `Router`, those tests are no longer needed. Removing code is always a happy feeling.

The Integration Tests needed more additions, because now we reply on the WrappedRouter and the HttpServer to serve the behaviour. Those have been updated too.

## Learnings

It is, in most cases, perfectly alright to fall back on a complex design, and simplify things. By getting rid of a custom router, we simplified the implementation a lot.

Most web applications these days are served with a Single Page Application, when only an API is needed. So, the need for sessions should also be redundant. The above is a [Basic Auth](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme) implementation. There are enough examples, It is relatively trivial to go from here into building a JWT based Authenticator too. I could get a good Authenticator generated by ChatGPT using the prompt:

`I am using the HttpServer in Java 17 for serving a web application. Can you help me write a JWT authenticator for it using the Authenticator as the base class ?`

## About the Author

The Author, [Navneet Karnani](https://www.linkedin.com/in/navneetkarnani), started coding with Java in 1997, and has been a promoter and enthusiast ever since. He strongly believes in the "Keep It Simple and Stupid", and has incorporated this design philosophy in all the products he has worked on, and built.

Navneet is a freelancer, and can be reached for contract, mentoring, and advisory on technology, and its application in building software products.