loading...

Securing Microservices with Auth0 Pt. 4 (Bring it all together)

bbenefield89 profile image Brandon Benefield ・4 min read

This is the third part to a series of posts called Securing Microservices with Auth0. If you missed the previous post, I would suggest you go back and read that post first.

Overview

In this part of the Securing Microservices with Auth0 series, we're going to finally secure our Resource Service by requiring that all requests to an endpoint must first go through our Auth Service. If you remember from the previous post, if the request fails due to an invalid access_token then the request stops there. If the request goes through and sends over a valid User we can then make a query to our DB to perform CRUD operations on behalf of a user.

Just to clarify the Auth Flow, let's take another look at the diagram from the previous post.

  • Make request from client side (or Postman), passing up our access_token

  • Resource Service make request to Auth Service

  • Auth Service sends access_token to Auth0

  • Auth Service sends a User object back to Resource Service

  • Resource Service performs CRUD operation

  • Return data back to the client

Back to the Resource Service

Create RestInterceptorAll Interceptor

Back in our Resource Service, we need to create another Interceptor. This Interceptor is going to be very similar to the Interceptor from our Auth Service and the idea is pretty much the same. Go ahead and create a new package in your Resource Service, Interceptors, and create a new class, RestInterceptorAll.java.

RestInterceptorAll.java

package ${}.${}.TodoApp_API.Interceptors;

import ${}.${}.TodoApp_API.Models.User;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class RestInterceptorAll extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
        try {
            HttpHeaders headers = setAuthorizationHeader(req);
            HttpEntity<String> entity = new HttpEntity<>("headers", headers);
            User user = getUserInfoFromAuthService(entity);
            req.getSession().setAttribute("user", user);
            return super.preHandle(req, res, handler);
        } catch (Exception e) {
            // Users "access_token" is wrong so we should notify them that they're unauthorized (401)
            res.setStatus(401, "401 Unauthorized");
            // Return "false" so the "ValidateController" method isn't called
            return false;
        }
    }

    // Sets the "Authorization" header value (Authorization: Bearer ${access_token})
    private HttpHeaders setAuthorizationHeader(HttpServletRequest req) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", req.getHeader("Authorization"));
        return headers;
    }

    // Sends a GET request grab the users info
    private User getUserInfoFromAuthService(HttpEntity<String> entity) {
        RestTemplate httpRequest = new RestTemplate();
        return httpRequest.exchange(
                "http://localhost:8081/api/validate",
                HttpMethod.GET,
                entity,
                User.class
        ).getBody();
    }

}

I'm sure you'll notice that it's extremely similar to our Auth Service, and like I said, the idea is pretty much the same.

  • Intercept requests to an endpoint

  • Make HTTP request to our Auth Service

  • Auth Service will then validate the access_token

Create MvcConfig Config

Again, similar to our Auth Service, we need to register our Interceptor. Create a new package, Configs, and inside create a new file, MvcConfig.java

MvcConfig.java

package ${}.${}.TodoApp_API.Configs;

import ${}.${}.TodoApp_API.Interceptors.RestInterceptorAll;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    private RestInterceptorAll restInterceptorAll;

    @Autowired
    public MvcConfig(RestInterceptorAll restInterceptorAll) {
        this.restInterceptorAll = restInterceptorAll;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Registers our "RestInterceptorAll" into the list of global interceptors
        registry.addInterceptor(restInterceptorAll);
    }

}

Revisiting the TodoController Controller

Now that we've registered our Interceptor, we need to alter our Controller for some additional security. At this point, a user could easily send up an access_token that is for user1@gmail.com but they could send this from the /api/todos/user2@gmail.com. We need to make sure that not only is the access_token valid, but that we're grabbing data for the correct user.

To save space, I'm only going to show a portion of the TodoController. I'd really like to encourage you to use the same pattern in this @GetMapping method and try to secure the rest of your methods. If you have trouble, don't worry, I'll provide the updated code in the controller.

TodoController.java

@RestController
@RequestMapping("/api/todos")
public class TodoController {

    private TodoService todoService;

    @Autowired
    public TodoController(TodoService todoService) {
        this.todoService = todoService;
    }

    /**
     * Returns a List of Todos
     * Here we are adjusting the parameters for our "GetAll" method
     * We want to grab the User object being passed around the current session
     * and compare the users email address from the User object with the
     * path variable for the current URL
     * If something doesn't match we're going to tell the user that they're
     * 401 Unauthorized
     */
    @GetMapping("/{userEmailAddress}")
    public List<Todo> findAllByUserEmailAddress(
            @SessionAttribute User user,
            @PathVariable String userEmailAddress,
            HttpServletResponse res) {

        if (user.getEmail().equals(userEmailAddress)) {
            return todoService.findAllByUserEmailAddress(userEmailAddress);
        } else {
            todoService.unAuthorizedAccess(res);
            return null;
        }
    }

    ... the rest of the methods are down here ...
}

Conclusion

Wow, you did it! You should have a pretty secure set of microservices ready to be extended into whatever amazing project you can think up.

I'm sure I'll get a few confusing looks about why we're not wiring up the frontend we create in part one. That's because this really wasn't meant to be a full-fledged tutorial on React and I really wanted to focus on the backend. Hopefully, if you made it this far, you learned something new and I encourage you to flesh out your frontend. If you do happen to finish this project, be sure to host it somewhere and share it in the comments below.

What did we learn?

  • The Microservice Architecture

  • Securing a SPA with Auth0

  • Spring Interceptors

  • How to make HTTP requests using RestTemplate from Spring

  • How to validate access_tokens

Discussion

pic
Editor guide