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 Auth0Auth Service sends a
User
object back to Resource ServiceResource 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 SpringHow to validate
access_tokens
Top comments (0)