Role-based access control behind a proxy in an OAuth access delegation

Role-based access control behind a proxy in an OAuth access delegation

In my previous article, I demonstrated the complete implementation for enabling OAuth-based authorization in NGINX with Keycloak, where NGINX acts as a relaying party for the authorization code grant. NGNIX can also act as a reverse proxy server for back-end applications (e.g., Tomcat, Open Liberty, WildFly, etc.), which can be hosted on an enterprise application server.

Everything you need to grow your career.

With your free Red Hat Developer program membership, unlock our library of cheat sheets and ebooks on next-generation application development.

SIGN UP

At times, back-end applications hosted behind NGINX are required to have role-based access control (RBAC), as RBAC helps to restrict resources based on users’ roles. In OAuth, roles associated with a user are available in the JSON Web Token (JWT), and thus one can capture the claim from the ID or access token, but the same should be shared through the header by NGINX:

ngx.req.set_header("X-USER", res.id_token.sub)
ngx.req.set_header("X-ROLE", res.id_token.role)

Here, I set the principal based on the subject claim, and the role based on the role claim available in the ID token. Usually, the role claim is not available in the ID token, but it is possible to add this information by configuring the mapper with the associated client in Keycloak.

Because I need to create the principal based on the username captured in the HTTP header, I extended HttpServletRequestWrapper to set the principal in the normal application flow:

public class ConfigPrincipal extends HttpServletRequestWrapper {
    String user;
    List<String> roles;
    HttpServletRequest realRequest;
    public ConfigPrincipal(String user, List<String> roles, HttpServletRequest request) {
      super(request);
      this.user = user;
      this.roles = roles;
      this.realRequest = request;
   }

   @Override
   public boolean isUserInRole(String role) {
    if (roles == null) {
      return this.realRequest.isUserInRole(role);
    }
    return roles.contains(role);
   }

   @Override
   public Principal getUserPrincipal() {
     if (this.user == null) {
       return realRequest.getUserPrincipal();
     }
     //make an anonymous implementation to just return our user
     return new Principal() {
     @Override
       public String getName() {
         return user;
       }
     };
   }

   public boolean authenticate(){
     return true;
   }
}

Next, I defined a servlet filter attached to the application so that it executes before the application’s business logic:

  @Override
  public void doFilter(ServletRequest req, ServletResponse response,
           FilterChain next) throws IOException, ServletException {
     
      HttpServletRequest request = (HttpServletRequest) req;
      String user = request.getHeader("X-USER");
      List<String> roles = new ArrayList<String>();

      //Capturing roles from the request header. 
      if (request.getHeader("role") != null) {
         String[] substrrole = request.getHeader("X-SSBRoleLevel").split(",");
         for (int i = 0; i < substrrole.length; i++) {
            roles.add(substrrole[i]);
         }
      }
      System.err.println("sidde roles:" + roles);
     //call the request wrapper , which overrides getUserPrincipal and is UserInRole
     next.doFilter(new ConfigPrincipal(user, roles, request), response);
   }

Now, request.getUserPrincipal() can be executed anywhere in the application to capture the principal, and request.isUserInRole(role) can be used to capture the associated role to have role-based access control.

Join Red Hat Developer and get access to handy cheat sheets, free books, and product downloads.

Share