title: OKTA multi-factor authentication + Java + RestAssured: /login/step-up/redirect always returns 403 through RESTAssured
body: <p>I am attempting to authenticate into OKTA using Java's RestAssured API. The authentication type in question is multi-factor, and while I am always able to get this authentication to succeed in Google Chrome, it consistently fails in RestAssured with a 403 despite matching all of the request headers and cookies. My end goal is to get the bearer authentication token in order to be able to run services after I login.</p>
  
  <p>Essentially, this is the sequence of services I'm calling:</p>
  
  <p>1) [redacted]/api/v1/authn; I provide my username and password, as well as some alphanumeric state token. (I use a wrapper class around RestAssured's RequestSpecification:) This returns successfully with a 200:</p>
  
  <pre><code>            // Create a brand new request to login using OKTA.
          RequestWrapper requestWrapper11 = new RequestWrapper();
          requestWrapper11.setResponseContentType(ContentType.JSON);
          requestWrapper11.setAcceptedContentType(ContentType.JSON);
          requestWrapper11.setHTTPMethod(Method.POST);
  
          requestWrapper11.setRequestPayload(new HashMap&lt;&gt;() {
              {
                  put("username", "[redacted]");
                  put("password", "[redacted]");
                  put("options", new HashMap&lt;String, Object&gt;() {
                      { put("warnBeforePasswordExpired", true); }
                      { put("multiOptionalFactorEnroll", true); }
                  });
              }
          });
  
          // Authenticate and get a brand new state token.
          requestWrapper11.setBaseURL(new URL("[redacted]/api/v1/authn"));
          ResponseWrapper response = requestWrapper11.executeAndGetResponse();
  </code></pre>
  
  <p>From this service call, I get back a state token that I use in the next step, as well as a factor ID.</p>
  
  <p>2) I then call the POST service [redacted]/api/v1/authn/factors and provide the answer to the security question: (this also returns successfully with a 200):</p>
  
  <p>{answer: "[redacted_1]", stateToken: "[state_token]"}</p>
  
  <pre><code>     RequestWrapper requestWrapper2 = new RequestWrapper();
          requestWrapper2.setResponseContentType(ContentType.JSON);
          requestWrapper2.setAcceptedContentType(ContentType.JSON);
          requestWrapper2.setHTTPMethod(Method.POST);
  
          requestWrapper2.setRequestPayload(new HashMap&lt;&gt;() {
              {
                  put("answer", "[redacted]");
                  put("stateToken", stateToken);
              }
          });
          requestWrapper2.setOverrideQueryParams(new HashMap&lt;&gt;() {
              { put("rememberDevice", false); }
          });
  
          // Authenticate and get a brand new state token.
          requestWrapper2.setBaseURL(new URL("[redacted]/api/v1/authn/factors/" + factorId + "/verify"));
  </code></pre>
  
  <p>3) Finally, I make a GET call [redacted]/login/step-up/redirect?stateToken=[state_token] to return a special code used for authentication purposes.</p>
  
  <pre><code>        RequestWrapper requestWrapper4 = new RequestWrapper();
          // requestWrapper4.setAllowRedirects(true);
          requestWrapper4.setOverrideQueryParams(new HashMap&lt;&gt;() {
              {put("stateToken",stateToken); }
          });
          requestWrapper4.setHeader("Connection", "Keep-Alive");
          requestWrapper4.setHeader("Host", "[redacted]");
          requestWrapper4.setHeader("Accept", "text/html,application/xhtml+xml,application/xml" +
                  ";q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
          requestWrapper4.setHeader("Accept-Encoding", "gzip, deflate, br");
          requestWrapper4.setHeader("Accept-Language", "en-US,en;q=0.9");
          requestWrapper4.setHeader("Sec-Fetch-Dest","document");
          requestWrapper4.setHeader("Sec-Fetch-Mode", "navigate");
          requestWrapper4.setHeader("Sec-Fetch-Site", "same-origin");
          requestWrapper4.addCookies(responseWrapper2.cookies);
          requestWrapper4.addCookie("oktaStateToken", stateToken);
          requestWrapper4.addCookie("t", "summer");
          requestWrapper4.addCookie("DT", "DI0--aZ4ipPS8mFXhEWHFwXUw");
          requestWrapper4.addCookie("ADRUM_BTa", "R:0|g:dd262b5c-ae86-4a1d-86aa-a89b3fed2bed|n:Okta_6d5b1e30-d05a-4894-a37b-81b5f6c60e0e");
          requestWrapper4.addCookie("ADRUM_BT1", "R:0|i:617|e:41");
          requestWrapper4.setHTTPMethod(Method.GET);
          requestWrapper4.setHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36");
          requestWrapper4.setBaseURL(new URL("[redacted]/login/step-up/redirect"));
          ResponseWrapper responseWrapper4 = requestWrapper4.executeAndGetResponse();
  </code></pre>
  
  <p>When I authenticate in normally using the browser, these three service calls are made in the browser without issue and all return 200 or 302. However, whenever I run these using the RestAssured API, I always get a 403 on the third service executed, regardless of whether the token passed in as the query parameter {stateToken} is legitimate or not. I always get 400 when this parameter isn't passed in through the RestAssured API, so I know that this method call distinguishes between whether a state token is passed in or not.</p>
  
  <p>My question is: is there anything notable that I'm missing from the set of steps above to cause a 403 to always be returned through one medium but not through a web browser? Is there something in place to prevent authentication using the RestAssured API? If so, is there another route I could take to get the bearer token?</p>