1. Overview
In this quick tutorial, we're going to show how we can add logout functionality to an OAuth Spring Security application.
We will, of course, use the OAuth application described in a previous article – Creating a REST API with OAuth2.
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 Logout in an OAuth Secured Application.
2. Remove the Access Token
Simply put, logging out in an OAuth-secured environment involves rendering the user's Access Token invalid – so it can no longer be used.
In a JdbcTokenStore-based implementation, this means removing the token from the TokenStore.
Let's implement a delete operation for the token. We're going to use the parimary /oauth/token URL structure here and simply introduce a new DELETE operation for it.
Now, because we're actually using the /oauth/token URI here – we need to handle it carefully. We won't be able to simply add this to any controller – because the framework already has operations mapped to that URI – with POST and GET.
Instead what we need to do is to define this is a @FrameworkEndpoint – so that it gets picked up and resolved by the FrameworkEndpointHandlerMapping instead of the standard RequestMappingHandlerMapping. That way we won't run into any partial matches and we won't have any conflicts:
@FrameworkEndpoint public class RevokeTokenEndpoint { @Resource(name = "tokenServices") ConsumerTokenServices tokenServices; @RequestMapping(method = RequestMethod.DELETE, value = "/oauth/token") @ResponseBody public void revokeToken(HttpServletRequest request) { String authorization = request.getHeader("Authorization"); if (authorization != null && authorization.contains("Bearer")){ String tokenId = authorization.substring("Bearer".length()+1); tokenServices.revokeToken(tokenId); } } }
Notice how we're extracting the token out of the request, simply using the standard Authorization header.
3. Remove the Refresh Token
In a previous article on Handling the Refresh Token, we have set up our application to be able to refresh the Access Token, using a Refresh Token. This implementation makes use of a Zuul proxy – with a CustomPostZuulFilter to add the refresh_token value received from the Authorization Server to a refreshToken cookie.
When revoking the Access Token, as shown in the previous section, the Refresh Token associated with it is also invalidated. However, the httpOnly cookie will remain set on the client, given that we can't remove it via JavaScript – so we need to remove it from the server side.
Let's enhance the CustomPostZuulFilter implementation that intercepts the /oauth/token/revoke URL so that it will remove the refreshToken cookie when encountering this URL:
@Component public class CustomPostZuulFilter extends ZuulFilter { //... @Override public Object run() { //... String requestMethod = ctx.getRequest().getMethod(); if (requestURI.contains("oauth/token") && requestMethod.equals("DELETE")) { Cookie cookie = new Cookie("refreshToken", ""); cookie.setMaxAge(0); cookie.setPath(ctx.getRequest().getContextPath() + "/oauth/token"); ctx.getResponse().addCookie(cookie); } //... } }
4. Remove the Access Token from the AngularJS Client
Besides revoking the access token from the token store, the access_token cookie will also need to be removed from the client side.
Let's add a method to our AngularJS controller that clears the access_token cookie and calls the /oauth/token/revoke DELETE mapping:
$scope.logout = function() { logout($scope.loginData); } function logout(params) { var req = { method: 'DELETE', url: "oauth/token" } $http(req).then( function(data){ $cookies.remove("access_token"); window.location.href="login"; },function(){ console.log("error"); } ); }
This function will be called when clicking on the Logout link:
<a class="btn btn-info" href="#" ng-click="logout()">Logout</a>
5. Conclusion
In this quick but in-depth tutorial, we've shown how we can logout a user from an OAuth secured application and invalidate the tokens of that user.
The full source code of the examples can be found over on GitHub.