Search code examples
springspring-bootspring-securityangular9angular-oauth2-oidc

Implement refresh token in Spring Security + Angular


I'm working on this Spring Security implementation with OAuth2 and JWT:

According to the author I can access resources using token this way:

To access a resource use (you'll need a different application which has configured ResourceServer):

http localhost:8080/users 'Authorization: Bearer '$ACCESS_TOKEN

About this step:

To use the refresh token functionality:

http --form POST adminapp:password@localhost:9999/oauth/token grant_type=refresh_token refresh_token=$REFRESH_TOKEN

It's not clear for me when I need to refresh the token and how to handle this part into Angular. When the Token expires do I need to first send request to the endpoint for refreshing the token and then to the login page?

How this case should be implemented?


Solution

  • At the time of authentication, two JWTs will be created - access token and refresh token. Refresh token will have longer validity. Both the tokens will be written in cookies so that they are sent in every subsequent request.

    On every REST API call, the tokens will be retrieved from the HTTP header. If the access token is not expired, check the privileges of the user and allow access accordingly. If the access token is expired but the refresh token is valid, recreate new access token and refresh token with new expiry dates and sent back through Cookies


    Access tokens carry the necessary information to access a resource directly. In other words, when a client passes an access token to a server managing a resource, that server can use the information contained in the token to decide whether the client is authorized or not. Access tokens usually have an expiration date and are short-lived.

    Refresh tokens carry the information necessary to get a new access token. In other words, whenever an access token is required to access a specific resource, a client may use a refresh token to get a new access token issued by the authentication server. Common use cases include getting new access tokens after old ones have expired, or getting access to a new resource for the first time. Refresh tokens can also expire but are rather long-lived.


    High level code

    authenticate()

    public ResponseEntity<OAuth2AccessToken> authenticate(HttpServletRequest request, HttpServletResponse response, Map<String, String> params) {
            try {
                String username = params.get("username");
                String password = params.get("password");
                boolean rememberMe = Boolean.valueOf(params.get("rememberMe"));
                OAuth2AccessToken accessToken = authorizationClient.sendPasswordGrant(username, password);
                OAuth2Cookies cookies = new OAuth2Cookies();
                cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
                cookies.addCookiesTo(response);
                if (log.isDebugEnabled()) {
                    log.debug("successfully authenticated user {}", params.get("username"));
                }
                return ResponseEntity.ok(accessToken);
            } catch (HttpClientErrorException ex) {
                log.error("failed to get OAuth2 tokens from UAA", ex);
                throw new BadCredentialsException("Invalid credentials");
            }
        }
    

    refreshToken()

    Try to refresh the access token using the refresh token provided as a cookie. Note that browsers typically send multiple requests in parallel which means the access token will be expired on multiple threads. We don't want to send multiple requests to UAA though, so we need to cache results for a certain duration and synchronize threads to avoid sending multiple requests in parallel.

    public HttpServletRequest refreshToken(HttpServletRequest request, HttpServletResponse response, Cookie refreshCookie) {
            //check if non-remember-me session has expired
            if (cookieHelper.isSessionExpired(refreshCookie)) {
                log.info("session has expired due to inactivity");
                logout(request, response); //logout to clear cookies in browser
                return stripTokens(request); //don't include cookies downstream
            }
            OAuth2Cookies cookies = getCachedCookies(refreshCookie.getValue());
            synchronized (cookies) {
                //check if we have a result from another thread already
                if (cookies.getAccessTokenCookie() == null) { //no, we are first!
                    //send a refresh_token grant to UAA, getting new tokens
                    String refreshCookieValue = OAuth2CookieHelper.getRefreshTokenValue(refreshCookie);
                    OAuth2AccessToken accessToken = authorizationClient.sendRefreshGrant(refreshCookieValue);
                    boolean rememberMe = OAuth2CookieHelper.isRememberMe(refreshCookie);
                    cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
                    //add cookies to response to update browser
                    cookies.addCookiesTo(response);
                } else {
                    log.debug("reusing cached refresh_token grant");
                }
                //replace cookies in original request with new ones
                CookieCollection requestCookies = new CookieCollection(request.getCookies());
                requestCookies.add(cookies.getAccessTokenCookie());
                requestCookies.add(cookies.getRefreshTokenCookie());
                return new CookiesHttpServletRequestWrapper(request, requestCookies.toArray());
            }
        }