Invalid XSRF token at /oauth/token
<p>Complete code for a Spring OAuth2 implementation of Multi-Factor Authentication has been uploaded to <a href="https://jumpshare.com/v/oj7wSZaZRgJHEwqYiJ2p" rel="nofollow noreferrer">a file sharing site at this link</a>.  Instructions are given below to recreate the current problem on any computer in only a few minutes.</p>
<hr/>
**CURRENT PROBLEM:**
<hr/>  
  
Most of the authentication algorithm works correctly.  The program does not break until the very end of the control flow shown below.  Specifically, an `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error is being thrown at the end of the **SECOND PASS** below.  The app in the link above was developed by adding a custom `OAuth2RequestFactory`, `TwoFactorAuthenticationFilter` and `TwoFactorAuthenticationController` to the <a href="https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/oauth2/authserver" rel="nofollow noreferrer">`authserver` app</a> of this <a href="https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/oauth2" rel="nofollow noreferrer">Spring Boot OAuth2 GitHub sample</a>.  **What specific changes need to be made to the code below in order to resolve this CSRF token error and enable 2-factor authentication?**  
  
My research leads me to suspect that the `CustomOAuth2RequestFactory` (<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/OAuth2RequestFactory.html" rel="nofollow noreferrer">API at this link</a>) might be the place to configure a solution because it defines ways for managing <a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/AuthorizationRequest.html" rel="nofollow noreferrer">`AuthorizationRequest`</a>s and <a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/TokenRequest.html" rel="nofollow noreferrer">`TokenRequest`</a>s.  
  
**<a href="https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1" rel="nofollow noreferrer">This section of the official OAuth2 spec</a> indicates that the `state` parameter of the request made to the authorization endpoint is the place where the `csrf` token is added.**  
  
Also, the code in the link uses <a href="https://www.rfc-editor.org/rfc/rfc6749#section-4.1" rel="nofollow noreferrer">the Authorization Code Grant Type described at this link to the official spec</a>, which would mean that Step C in the flow does not update the `csrf` code, thus triggering the error in Step D.  (You can view the entire flow including Step C and Step D in <a href="https://www.rfc-editor.org/rfc/rfc6749#section-4.1" rel="nofollow noreferrer">the official spec</a>.)
  
<hr/>
**CONTROL FLOW SURROUNDING THE CURRENT ERROR:**  
<hr/>  
  
The current error is being thrown during the **SECOND PASS** through `TwoFactorAuthenticationFilter` in the flowchart below.  Everything works as intended until the control flow gets into the **SECOND PASS**.    
  
The following flowchart illustrates the control flow of the two factor authentication process that is employed by the code in the downloadable app.
  
<img src="https://s32.postimg.cc/5sahbqvrp/2_FA_OAuth_Flow.gif">  
  
Specifically, the Firefox `HTTP` Headers for the sequence of `POST`s and `GET`s show that the same `XSRF` cookie is sent with every request in the sequence.  The `XSRF` token values do not cause a problem until after the `POST /secure/two_factor_authentication`, which triggers server processing at the `/oauth/authorize` and `/oauth/token` endpoints, with `/oauth/token` throwing the `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error.  
  
To understand the relationship between the above control flow chart and the `/oauth/authorize` and `/oauth/token` endpoints, you can compare the above flowchart side by side <a href="https://www.rfc-editor.org/rfc/rfc6749#section-4.1" rel="nofollow noreferrer">with the chart for the single factor flow at the official spec</a> in a separate browser window.  The **SECOND PASS** above simply runs through the steps from the one-factor official spec a second time, but with greater permissions during the **SECOND PASS**.  
  
<hr/>  
**WHAT THE LOGS SAY:**
<hr/>  
  
The HTTP Request and Response Headers indicate that:  
  
1.) A POST to `9999/login` with the correct `username` and `password` submitted results in a redirect to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` followed by a `GET 9999/secure/two_factor_authenticated`.  One XSRF token remains constant across these exchanges.  
  
2.) A POST to `9999/secure/two_factor_authentication` with the correct pin code sends the same `XSRF` token, and gets successfully re-directed to `POST 9999/oauth/authorize` and makes it into `TwoFactorAuthenticationFilter.doFilterInternal()` and proceeds to `request 9999/oauth/token`, but `9999/oauth/token` rejects the request because the same old XSRF token does not match a new `XSRF` token value, which was apparently created during the **FIRST PASS**.    
  
One obvious difference between `1.)` and `2.)` is that the second `request 9999/oauth/authorize` in `2.)` does not contain the url parameters which are included in the first request to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` in `1.)`, and also defined in <a href="https://www.rfc-editor.org/rfc/rfc6749#section-4.1" rel="nofollow noreferrer">the official spec</a>.  But it is not clear if this is causing the problem.  
  
Also, it is not clear how to access the parameters to send a fully formed request from the `TwoFactorAuthenticationController.POST`.  I did a SYSO of the `parameters` `Map` in the `HttpServletRequest` for the `POST 9999/secure/two_factor_authentication` controller method, and all it contains are the `pinVal` and `_csrf` variables.  
  
You can read all the HTTP Headers and Spring Boot logs at a file sharing site <a href="https://jumpshare.com/v/Xy2g91GLXLB8gkWmliMF" rel="nofollow noreferrer">by clicking on this link</a>.
  
<hr/>
**A FAILED APPROACH:**  
<hr/>  
  
I tried <a href="https://stackoverflow.com/questions/20862299/with-spring-security-3-2-0-release-how-can-i-get-the-csrf-token-in-a-page-that#20960352">@RobWinch's approach to a similar problem in the Spring Security 3.2 environment</a>, but the approach does not seem to apply to the context of Spring OAuth2.  Specifically, when the following `XSRF` update code block is uncommented in the `TwoFactorAuthenticationFilter` code shown below, the downstream request headers do show a different/new `XSRF` token value, but the same error is thrown.  
  
    if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
        CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
        response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken());
    }
  
**This indicates that the `XSRF` configuration needs to be updated in a way that `/oauth/authorize` and `/oauth/token` are able to talk with each other and with the client and resource apps to successfully manage the `XSRF` token values.**  Perhaps the `CustomOAuth2RequestFactory` is what needs to be changed to accomplish this.  But how?  
  
<hr/>
**RELEVANT CODE:**
<hr/>  
  
The code for `CustomOAuth2RequestFactory` is:  
  
    public class CustomOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
    
        public static final String SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME = "savedAuthorizationRequest";
    
        public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
            super(clientDetailsService);
        }
    
        @Override
        public AuthorizationRequest createAuthorizationRequest(Map authorizationParameters) {
            ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            HttpSession session = attr.getRequest().getSession(false);
            if (session != null) {
                AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
                if (authorizationRequest != null) {
                    session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
                    return authorizationRequest;
                }
            }
    
            return super.createAuthorizationRequest(authorizationParameters);
        }
    }
  
The code for `TwoFactorAuthenticationFilter` is:  
  
    //This class is added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
    /**
     * Stores the oauth authorizationRequest in the session so that it can
     * later be picked by the {@link com.example.CustomOAuth2RequestFactory}
     * to continue with the authoriztion flow.
     */
    public class TwoFactorAuthenticationFilter extends OncePerRequestFilter {
    
        private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        private OAuth2RequestFactory oAuth2RequestFactory;
        //These next two are added as a test to avoid the compilation errors that happened when they were not defined.
        public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";
        public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED";
    
        @Autowired
        public void setClientDetailsService(ClientDetailsService clientDetailsService) {
            oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
        }
    
        private boolean twoFactorAuthenticationEnabled(Collection authorities) {
            System.out.println(">>>>>>>>>>> List of authorities includes: ");
            for (GrantedAuthority authority : authorities) {
                System.out.println("auth: "+authority.getAuthority() );
            }
            return authorities.stream().anyMatch(
                authority -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(authority.getAuthority())
            );
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            System.out.println("------------------ INSIDE TwoFactorAuthenticationFilter.doFilterInternal() ------------------------");
            // Check if the user hasn't done the two factor authentication.
            if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
            System.out.println("++++++++++++++++++++++++ AUTHENTICATED BUT NOT TWO FACTOR +++++++++++++++++++++++++");
            AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
                /* Check if the client's authorities (authorizationRequest.getAuthorities()) or the user's ones
                   require two factor authenticatoin. */
            System.out.println("======================== twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) is: " + twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) );
            System.out.println("======================== twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) is: " + twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) );
            if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ||
                    twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) {
                    // Save the authorizationRequest in the session. This allows the CustomOAuth2RequestFactory
                    // to return this saved request to the AuthenticationEndpoint after the user successfully
                    // did the two factor authentication.
                    request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest);
    
                    // redirect the the page where the user needs to enter the two factor authentiation code
                    redirectStrategy.sendRedirect(request, response,
                        ServletUriComponentsBuilder.fromCurrentContextPath()
                            .path(TwoFactorAuthenticationController.PATH)
                            .toUriString());
                    return;
                }
            }
            //THE NEXT "IF" BLOCK DOES NOT RESOLVE THE ERROR WHEN UNCOMMENTED
            //if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
            //    CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
                // this is the value of the token to be included as either a header or an HTTP parameter
            //    response.setHeader("XSRF-TOKEN", token.getToken());
            //}
    
            filterChain.doFilter(request, response);
        }
    
        private Map paramsFromRequest(HttpServletRequest request) {
            Map params = new HashMap();
            for (Entry entry : request.getParameterMap().entrySet()) {
                params.put(entry.getKey(), entry.getValue()[0]);
            }
            return params;
        }
    }
  
<hr/>
  
**RE-CREATING THE PROBLEM ON YOUR COMPUTER:**  
  
<hr/>  
  
You can recreate the problem on any computer in only a few minutes by following these simple steps:  
  
1.) Download the <a href="https://jumpshare.com/v/oj7wSZaZRgJHEwqYiJ2p" rel="nofollow noreferrer">zipped version of the app from a file sharing site by clicking on this link</a>.  
  
2.) Unzip the app by typing: `tar -zxvf oauth2.tar(2).gz`  
  
3.) launch the `authserver` app by navigating to `oauth2/authserver` and then typing `mvn spring-boot:run`.  
  
4.) launch the `resource` app by navigating to `oauth2/resource` and then typing `mvn spring-boot:run`  
  
5.) launch the `ui` app by navigating to `oauth2/ui` and then typing `mvn spring-boot:run`  
  
6.) Open a web browser and navigate to `http : // localhost : 8080`  
  
7.) Click `Login` and then enter `Frodo` as the user and `MyRing` as the password, and click to submit.  
  
8.) Enter `5309` as the `Pin Code` and click submit.  **This will trigger the error shown above.**  
  
You can view the complete source code by: 
  
a.) importing the maven projects into your IDE, or by  
  
b.) navigating within the unzipped directories and opening with a text editor.  
<hr/>
You can read all the HTTP Headers and Spring Boot logs at a file sharing site <a href="https://jumpshare.com/v/Xy2g91GLXLB8gkWmliMF" rel="nofollow noreferrer">by clicking on this link</a>.