OAuth2 for a Spring REST API – Handle the Refresh Token in AngularJS

1. Overview

In this tutorial, we’ll continue exploring the OAuth password flow that we started putting together in more our previous article and we’ll focus on how to handle the Refresh Token in an AngularJS app.

Note: this article is using the Spring OAuth legacy project.  For the version of this article using the new Spring Security 5 stack, have a look at our article OAuth2 for a Spring REST API – Handle the Refresh Token in Angular.

2. Access Token Expiration

First, remember that the client was obtaining an Access Token when the user was logging into the application:

function obtainAccessToken(params) {
    var req = {
        method: 'POST',
        url: "oauth/token",
        headers: {"Content-type": "application/x-www-form-urlencoded; charset=utf-8"},
        data: $httpParamSerializer(params)
    }
    $http(req).then(
        function(data) {
            $http.defaults.headers.common.Authorization= 'Bearer ' + data.data.access_token;
            var expireDate = new Date (new Date().getTime() + (1000 * data.data.expires_in));
            $cookies.put("access_token", data.data.access_token, {'expires': expireDate});
            window.location.href="index";
        },function() {
            console.log("error");
            window.location.href = "login";
        });   
}

Note how our Access Token is stored in a cookie which will expire based on when the token itself expires.

What’s important to understand is that the cookie itself is only used for storage and it doesn’t drive anything else in the OAuth flow. For example, the browser will never automatically send out the cookie to the server with requests.

Also note how we actually call this obtainAccessToken() function:

$scope.loginData = {
    grant_type:"password", 
    username: "", 
    password: "", 
    client_id: "fooClientIdPassword"
};

$scope.login = function() {   
    obtainAccessToken($scope.loginData);
}

3. The Proxy

We’re now going to have a Zuul proxy running in the front-end application and basically sitting between the front-end client and the Authorization Server.

Let’s configure the routes of the proxy:

zuul:
  routes:
    oauth:
      path: /oauth/**
      url: http://localhost:8081/spring-security-oauth-server/oauth

What’s interesting here is that we’re only proxying traffic to the Authorization Server and not anything else. We only really need the proxy to come in when the client is obtaining new tokens.

If you want to go over the basics of Zuul, have a quick read of the main Zuul article.

4. A Zuul Filter That Does Basic Authentication

The first use of the proxy is simple – instead of revealing our app “client secret” in javascript, we will use a Zuul pre-filter to add an Authorization header to access token requests:

@Component
public class CustomPreZuulFilter extends ZuulFilter {
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.getRequest().getRequestURI().contains("oauth/token")) {
            byte[] encoded;
            try {
                encoded = Base64.encode("fooClientIdPassword:secret".getBytes("UTF-8"));
                ctx.addZuulRequestHeader("Authorization", "Basic " + new String(encoded));
            } catch (UnsupportedEncodingException e) {
                logger.error("Error occured in pre filter", e);
            }
        }
        return null;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public int filterOrder() {
        return -2;
    }

    @Override
    public String filterType() {
        return "pre";
    }

Now keep in mind that this doesn’t add any extra security and the only reason we’re doing it is because the token endpoint is secured with Basic Authentication using client credentials.

From the point of view of the implementation, the type of filter is especially worth noticing. We’re using a filter type of “pre” to process the request before passing it on.

5. Put the Refresh Token in a Cookie

On to the fun stuff.

What we’re planning to do here is to have the client get the Refresh Token as a cookie. Not just a normal cookie, but a secured, HTTP-only cookie with a very limited path (/oauth/token).

We’ll set up a Zuul post-filter to extract Refresh Token from the JSON body of the response and set it in the cookie:

@Component
public class CustomPostZuulFilter extends ZuulFilter {
    private ObjectMapper mapper = new ObjectMapper();

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        try {
            InputStream is = ctx.getResponseDataStream();
            String responseBody = IOUtils.toString(is, "UTF-8");
            if (responseBody.contains("refresh_token")) {
                Map<String, Object> responseMap = mapper.readValue(
                  responseBody, new TypeReference<Map<String, Object>>() {});
                String refreshToken = responseMap.get("refresh_token").toString();
                responseMap.remove("refresh_token");
                responseBody = mapper.writeValueAsString(responseMap);

                Cookie cookie = new Cookie("refreshToken", refreshToken);
                cookie.setHttpOnly(true);
                cookie.setSecure(true);
                cookie.setPath(ctx.getRequest().getContextPath() + "/oauth/token");
                cookie.setMaxAge(2592000); // 30 days
                ctx.getResponse().addCookie(cookie);
            }
            ctx.setResponseBody(responseBody);
        } catch (IOException e) {
            logger.error("Error occured in zuul post filter", e);
        }
        return null;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public int filterOrder() {
        return 10;
    }

    @Override
    public String filterType() {
        return "post";
    }
}

A few interesting things to understand here:

  • We used a Zuul post-filter to read response and extract refresh token
  • We removed the value of the refresh_token from JSON response to make sure it’s never accessible to the front end outside of the cookie
  • We set the max-age of the cookie to 30 days – as this matches the expire time of the token

In order to add an extra layer of protection against CSRF attacks, we’ll add a Same-Site cookie header to all our cookies.

For that, we’ll create a configuration class:

@Configuration
public class SameSiteConfig implements WebMvcConfigurer {
    @Bean
    public TomcatContextCustomizer sameSiteCookiesConfig() {
        return context -> {
            final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
            cookieProcessor.setSameSiteCookies(SameSiteCookies.STRICT.getValue());
            context.setCookieProcessor(cookieProcessor);
        };
    }
}

Here we’re setting the attribute to strict, so that any cross site transfer of cookies is strictly withheld.

6. Get and Use the Refresh Token from the Cookie

Now that we have the Refresh Token in the cookie, when the front-end AngularJS application tries to trigger a token refresh, it’s going to send the request at /oauth/token and so the browser, will, of course, send that cookie.

So we’ll now have another filter in the proxy that will extract the Refresh Token from the cookie and send it forward as a HTTP parameter – so that the request is valid:

public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    ...
    HttpServletRequest req = ctx.getRequest();
    String refreshToken = extractRefreshToken(req);
    if (refreshToken != null) {
        Map<String, String[]> param = new HashMap<String, String[]>();
        param.put("refresh_token", new String[] { refreshToken });
        param.put("grant_type", new String[] { "refresh_token" });
        ctx.setRequest(new CustomHttpServletRequest(req, param));
    }
    ...
}

private String extractRefreshToken(HttpServletRequest req) {
    Cookie[] cookies = req.getCookies();
    if (cookies != null) {
        for (int i = 0; i < cookies.length; i++) {
            if (cookies[i].getName().equalsIgnoreCase("refreshToken")) {
                return cookies[i].getValue();
            }
        }
    }
    return null;
}

And here is our CustomHttpServletRequest – used to inject our refresh token parameters:

public class CustomHttpServletRequest extends HttpServletRequestWrapper {
    private Map<String, String[]> additionalParams;
    private HttpServletRequest request;

    public CustomHttpServletRequest(
      HttpServletRequest request, Map<String, String[]> additionalParams) {
        super(request);
        this.request = request;
        this.additionalParams = additionalParams;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> map = request.getParameterMap();
        Map<String, String[]> param = new HashMap<String, String[]>();
        param.putAll(map);
        param.putAll(additionalParams);
        return param;
    }
}

Again, a lot of important implementation notes here:

  • The Proxy is extracting the Refresh Token from the Cookie
  • It’s then setting it into the refresh_token parameter
  • It’s also setting the grant_type to refresh_token
  • If there is no refreshToken cookie (either expired or first login) – then the Access Token request will be redirected with no change

7. Refreshing the Access Token from AngularJS

Finally, let’s modify our simple front-end application and actually make use of refreshing the token:

Here is our function refreshAccessToken():

$scope.refreshAccessToken = function() {
    obtainAccessToken($scope.refreshData);
}

And here our $scope.refreshData:

$scope.refreshData = {grant_type:"refresh_token"};

Note how we’re simply using the existing obtainAccessToken function – and just passing different inputs to it.

Also notice that we’re not adding the refresh_token ourselves – as that’s going to be taken care of by the Zuul filter.

8. Conclusion

In this OAuth tutorial, we learned how to store the Refresh Token in an AngularJS client application, how to refresh an expired Access Token, and how to leverage the Zuul proxy for all of that.

The full implementation of this tutorial can be found in the github project.

Related posts:

A Guide to HashSet in Java
Java Program to Implement Ternary Tree
JPA/Hibernate Persistence Context
Beans and Dependency Injection
Converting String to Stream of chars
Loại bỏ các phần tử trùng trong một ArrayList như thế nào trong Java 8?
Java Program to Test Using DFS Whether a Directed Graph is Weakly Connected or Not
Java Program to Perform Searching in a 2-Dimension K-D Tree
Autoboxing và Unboxing trong Java
Java Program to Perform Complex Number Multiplication
The StackOverflowError in Java
Java Program to Remove the Edges in a Given Cyclic Graph such that its Linear Extension can be Found
Guide to the Volatile Keyword in Java
Removing all duplicates from a List in Java
Java Program to Implement LinkedBlockingDeque API
Java Program to Compute Cross Product of Two Vectors
@Before vs @BeforeClass vs @BeforeEach vs @BeforeAll
Uploading MultipartFile with Spring RestTemplate
Receive email using IMAP
Spring Security and OpenID Connect
A Guide to Spring Boot Admin
Spring Cloud AWS – EC2
Java Program to Find the Shortest Path Between Two Vertices Using Dijkstra’s Algorithm
Introduction to Spring Cloud Rest Client with Netflix Ribbon
Consuming RESTful Web Services
Java Program to Implement the MD5 Algorithm
Map Serialization and Deserialization with Jackson
Java Program to Check whether Graph is a Bipartite using BFS
Hướng dẫn Java Design Pattern – Object Pool
Spring RestTemplate Request/Response Logging
LinkedHashSet trong Java hoạt động như thế nào?
Compare Two JSON Objects with Jackson