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 create the Auth Service microservice. The Auth Services job is to keep our applications' endpoints secure from any malicious users.
The idea here is that when a user makes a request from the frontend to an endpoint, sending up an Authorization: Bearer ${access_token}
header, the request will then be redirected to our Auth Service where that access_token will be sent to our /userinfo
endpoint provided to us by Auth0. Auth0 will attempt to validate the token and if successful our request will then be sent to an endpoint on our Auth Service that will finally return a User object to our API that will eventually return some data back to the frontend. Now, that was a lot of information so hopefully, this flowchart will help.
You can also go ahead and play around with the code for this post. This branch, bbenefield89/tutorial_pt3
, is the UI, Insecure RESTful API (Resource Service), and our Auth Service.
Create the Auth Service
Just like in the last part of this series, I've once again made the decision to go with the Spring Framework. You'll soon see how quick and effortless it is to secure your application(s) using Spring Security.
Let's head on over to the Spring Initializr and like last time, add in the details for your project and pick the libraries you'd like to start with.
Project Details
Libraries
Download your project and let's get started.
Inside our Auth Service
Because we'll eventually have multiple services running we need to make sure that each service uses an open port. Inside of your application.yml
/application.properties
go ahead and set your port to 8081.
application.yml
server:
port: 8081
Create User
Model
Create a new package called Models
and inside create a new class called User.java
and insert the following code.
User.java
package ${}.${}.TodoApp_Auth.Models;
import lombok.Data;
@Data
public class User {
private String email;
private boolean email_verified;
private String family_name;
private String given_name;
private String locale;
private String name;
private String nickname;
private String picture;
private String sub;
private String updated_at;
}
The User class will be used to map the response from https://auth0Username.auth0.com/userinfo
to an object that will be passed back to your Resource Service. Back in our Resource Service we will then user the users email value to grab Todos specific to that user.
Create RestInterceptorAll
Interceptor
Create a new package called Interceptor
and inside create a new class called RestInterceptorAll.java
and insert the following code.
RestInterceptorAll.java
package ${}.${}.TodoApp_Auth.Interceptors;
import io.github.bbenefield89.TodoApp_Auth.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 {
/**
* Wrap the logic of this method in a try/catch
* If this method fails then we know that something is wrong with the "access_token"
*/
try {
HttpHeaders headers = setAuthorizationHeader(req);
HttpEntity<String> entity = new HttpEntity<>("headers", headers);
User user = getUserInfoFromAuth0(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 getUserInfoFromAuth0(HttpEntity<String> entity) {
RestTemplate httpRequest = new RestTemplate();
return httpRequest.exchange(
"https://bbenefield.auth0.com/userinfo",
HttpMethod.GET,
entity,
User.class
).getBody();
}
}
Create MvcConfig
Config
Create a new package called Configs
and inside create a new class called MvcConfig.java
and insert the following code.
MvcConfig.java
package ${}.${}.TodoApp_Auth.Configs;
import io.github.bbenefield89.TodoApp_Auth.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);
}
}
Create ValidateController
Controller
Create a new package called Controllers
and inside create a new class called ValidateController.java
and insert the following code.
ValidateController.java
package ${}.${}.TodoApp_Auth.Controllers;
import ${}.${}.TodoApp_Auth.Models.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.SessionAttribute;
@RestController
@RequestMapping("/api/validate")
public class ValidateController {
// Really simple, if the request makes it this far we can return the "User" object
@GetMapping
public User validateUser(@SessionAttribute User user) {
return user;
}
}
Manually testing our Auth Service
Now that we've written our Auth Service we need to make sure things are working.
Grab an access_token
To get an access_token
you need to start up your frontend and log in as a user. Typically, I just log in through Google. To actually get the access_token
you need to call the getTokenSilenty()
method that comes from react-auth0-wrapper.js on the frontend. As an example, you can take a look at my Profile.js component in the test()
method near the bottom of the file.
The getTokenSilently()
method will return your users access_token
.
Test Auth Service through Postman
After getting the access_token
make sure you copy it and open up Postman and let's make a GET
request to our Auth Service.
Example Request
GET http://localhost:8081/api/validate
Headers: Authorization: Bearer ${access_token}
Example Response
{
"email": "bsquared18@gmail.com",
"email_verified": true,
"family_name": "Benefield",
"given_name": "Brandon",
"locale": "en",
"name": "Brandon Benefield",
"nickname": "bsquared18",
"picture": "https://lh6.googleusercontent.com/-ASD8&89ASD/photo.jpg",
"sub": "google-oauth2|9071248919",
"updated_at": "2019-09-28T18:21:16.685Z"
}
If you pass in invalid access_token
you should receive an empty response with an HTTP Status 401 Unauthorized
.
Conclusion
First, give yourself a pat on the back as you've completed the most difficult portion of this series. Security is extremely complicated and takes a long time to wrap your head around so congratulations!
Let's take a look at what you've learned in this post:
How to write Interceptors to intercept a request to a Controller
How to make HTTP requests using RestTemplate provided by Spring
How to deny access to an endpoint and return a custom HTTP Status
How to validate
access_tokens
sent from your frontend
In the next and final post (link coming soon) of this series, we're going to return to our Resource API where we will make a request to the API will be presented with a list of Todos from a specific user if they've been properly authenticated.
Top comments (0)