loading...

Securing Microservices with Auth0 Pt. 3 (Auth Service)

bbenefield89 profile image Brandon Benefield ・5 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 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.

Discussion

pic
Editor guide