Outlook API for access token returns 400 bad request in Java Spring

593 views Asked by At
  • The code does not seem to have any error.
  • This is a test app and all the Ids are usable.
  • In the function getToken() one can alternatively unblock the line call for getScopes() and try further.
  • I have an index.jsp with a button.
  • On button press "oauthorize" is activated to generate code and id_token.
  • I am able to login, code and id_token are generated.
  • These values are displayed on a "authtoken.jsp" which has a button.
  • The button press posts to /common/oauth2/v2.0/token.
  • At this stage 400 bad request is displayed on the Microsoft page.

I am not sure what is going wrong:

import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriComponentsBuilder;


@Controller
public class IndexController {

    //all oauth2 urls
    private static final String authority = "https://login.microsoftonline.com";
    private static final String authorizeUrl = authority + "/common/oauth2/v2.0/authorize";
    private static final String tokenUrl = authority + "/common/oauth2/v2.0/token";
    private static final String redirectUrl = "http://localhost:8080/OutlookProfiles/authtoken";
    private static final String reTokenUrl = "http://localhost:8080/OutlookProfiles/showToken";

    //credentials
    private static final String appId = "7414b3a3-26f1-4928-9d0b-7060d01dd41c";
    private static final String appPassword = "cQg9F0EaxuaErNp2YEgYaz8";

    private static final String[] scopes = { 
                "openid", 
                "offline_access",
                "profile", 
                "User.Read",
                "Contacts.Read",
                "Mail.Read"
              };


    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index(Model model, HttpServletRequest request, HttpServletResponse response){

        UUID state = UUID.randomUUID();
        UUID nonce = UUID.randomUUID();

        // Save the state and nonce in the session so we can
        // verify after the auth process redirects back

        HttpSession session = request.getSession();
        session.setAttribute("expected_state", state);
        session.setAttribute("expected_nonce", nonce);

        return "index";
    }

    @RequestMapping(value = "/oauthorize", method = RequestMethod.POST)
    public void oauthorize(Model model, HttpServletRequest servletRequest, HttpServletResponse servletResponse) {

        try{

        UUID state = UUID.randomUUID();
        UUID nonce = UUID.randomUUID();

          HttpSession session = servletRequest.getSession();
          session.setAttribute("expected_state", state);
          session.setAttribute("expected_nonce", nonce);
          session.setAttribute("error", null);

        UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromHttpUrl(authorizeUrl);
        urlBuilder.queryParam("client_id", appId);
        urlBuilder.queryParam("redirect_uri", redirectUrl);
        urlBuilder.queryParam("response_type", "code id_token");
        urlBuilder.queryParam("scope", getScopes());
        urlBuilder.queryParam("state", state);
        urlBuilder.queryParam("nonce", nonce);
        urlBuilder.queryParam("response_mode", "form_post");

        String locationUri = urlBuilder.toUriString();
        System.out.println(locationUri);

        servletResponse.sendRedirect(locationUri);

        }catch(Exception e){
            e.printStackTrace();
        }

    }


    @RequestMapping(value = "/authtoken", method = RequestMethod.POST)
    public String authorize(
                @RequestParam("code") String code, 
                @RequestParam("id_token") String idToken,
                @RequestParam("state") UUID state, 
                HttpServletRequest servletRequest, 
                HttpServletResponse servletResponse) {

        // Get the expected state value from the session
        HttpSession session = servletRequest.getSession();
        UUID expectedState = (UUID) session.getAttribute("expected_state");
        UUID expectedNonce = (UUID) session.getAttribute("expected_nonce");


        String strState = state.toString().trim().toLowerCase();
        String strExState = expectedState.toString().trim().toLowerCase();


        // Make sure that the state query parameter returned matches
        // the expected state
        if (strState.equals(strExState)){
          session.setAttribute("authCode", code);
          session.setAttribute("idToken", idToken);
          System.out.println("Expectedstate : NO Error");
        }else {
          session.setAttribute("error", "Unexpected state returned from authority.");
          System.out.println("\n\nUnexpected state returned from authority.");
        }

        return "authtoken";
    }

    @RequestMapping(value = "/getToken", method = RequestMethod.POST)
    public void getToken(
            HttpServletRequest servletRequest, 
            HttpServletResponse servletResponse) {

        try{

        HttpSession session = servletRequest.getSession();
        String strCode = (String) session.getAttribute("authCode");

        UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromHttpUrl(tokenUrl);
        urlBuilder.queryParam("client_id", appId);
        urlBuilder.queryParam("client_secret", appPassword);
        urlBuilder.queryParam("code", strCode);
        urlBuilder.queryParam("redirect_uri", redirectUrl);
        urlBuilder.queryParam("grant_type", "authorization_code");
        urlBuilder.queryParam("scope", getScopes());

        String locationUri = urlBuilder.toUriString();
        System.out.println("getToken : " + locationUri);

        servletResponse.setHeader("Content-Type", "application/x-www-form-urlencoded");

        servletResponse.sendRedirect(locationUri);

        }catch(Exception e){
            e.printStackTrace();
        }
    }

    @RequestMapping(value = "/showToken", method = RequestMethod.POST)
    public String showToken(
            @RequestParam("token_type") String code, 
            @RequestParam("expires_in") String idToken,
            @RequestParam("access_token") String accessToken, 
            //@RequestParam("scope") String paramScope,
            HttpServletRequest servletRequest, 
            HttpServletResponse servletResponse) {

        return "getToken";

    }

    @RequestMapping("/logout")
    public String logout(HttpServletRequest request) {
      HttpSession session = request.getSession();
      session.invalidate();
      return "index";
    }

    private static String getScopes() {
            StringBuilder sb = new StringBuilder();
            for (String scope: scopes) {
              sb.append(scope + " ");
            }

            String strscope = sb.toString().trim();
            System.out.println(strscope);

            return strscope;
    }
}

The 400 error is messing with my head for no reason.

1

There are 1 answers

0
Surya Kameswara Rao Ravi On
  • Microsoft Outlook API end point for access token: https://login.microsoftonline.com/common/oauth2/v2.0/token expects a form based POST i.e. application/x-www-form-urlencoded.
  • Which means all the input parameters should be form based and not url appended
  • So technically the code is wrong using UriComponentsBuilder
  • The code should instead use JSON based or form based approach using HttpClient and HttpPost OR use OKHttp3 based OKHttpClient and RequestBody.

I will soon post the working code here.

...here is the working code...

import java.util.List;
import java.util.ArrayList;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriComponentsBuilder;


@Controller
public class IndexController {

    //all oauth2 urls
    private static final String authority = "https://login.microsoftonline.com";
    private static final String authorizeUrl = authority + "/common/oauth2/v2.0/authorize";
    private static final String tokenUrl = authority + "/common/oauth2/v2.0/token";
    private static final String redirectUrl = "http://localhost:8080/OutlookProfiles/authtoken";
    private static final String reTokenUrl = "http://localhost:8080/OutlookProfiles/showToken";

    //credentials
    private static final String appId = "7414b3a3-26f1-4928-9d0b-7060d01dd41c";
    private static final String appPassword = "cQg9F0EaxuaErNp2YEgYaz8";

    private static final String[] scopes = { 
                "openid", 
                "offline_access",
                "profile", 
                "User.Read",
                "Contacts.Read",
                "Mail.Read"
              };


    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index(Model model, HttpServletRequest request, HttpServletResponse response){

        UUID state = UUID.randomUUID();
        UUID nonce = UUID.randomUUID();

        // Save the state and nonce in the session so we can
        // verify after the auth process redirects back

        HttpSession session = request.getSession();
        session.setAttribute("expected_state", state);
        session.setAttribute("expected_nonce", nonce);

        return "index";
    }

    @RequestMapping(value = "/oauthorize", method = RequestMethod.POST)
    public void oauthorize(Model model, HttpServletRequest servletRequest, HttpServletResponse servletResponse) {

        try{

        UUID state = UUID.randomUUID();
        UUID nonce = UUID.randomUUID();

          HttpSession session = servletRequest.getSession();
          session.setAttribute("expected_state", state);
          session.setAttribute("expected_nonce", nonce);
          session.setAttribute("error", null);

        UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromHttpUrl(authorizeUrl);
        urlBuilder.queryParam("client_id", appId);
        urlBuilder.queryParam("redirect_uri", redirectUrl);
        urlBuilder.queryParam("response_type", "code id_token");
        urlBuilder.queryParam("scope", getScopes());
        urlBuilder.queryParam("state", state);
        urlBuilder.queryParam("nonce", nonce);
        urlBuilder.queryParam("response_mode", "form_post");

        String locationUri = urlBuilder.toUriString();
        System.out.println(locationUri);

        servletResponse.sendRedirect(locationUri);

        }catch(Exception e){
            e.printStackTrace();
        }

    }


    @RequestMapping(value = "/authtoken", method = RequestMethod.POST)
    public String authorize(
                @RequestParam("code") String code, 
                @RequestParam("id_token") String idToken,
                @RequestParam("state") UUID state, 
                HttpServletRequest servletRequest, 
                HttpServletResponse servletResponse) {

        // Get the expected state value from the session
        HttpSession session = servletRequest.getSession();
        UUID expectedState = (UUID) session.getAttribute("expected_state");
        UUID expectedNonce = (UUID) session.getAttribute("expected_nonce");


        String strState = state.toString().trim().toLowerCase();
        String strExState = expectedState.toString().trim().toLowerCase();


        // Make sure that the state query parameter returned matches
        // the expected state
        if (strState.equals(strExState)){
          session.setAttribute("authCode", code);
          session.setAttribute("idToken", idToken);
          System.out.println("Expectedstate : NO Error");
        }else {
          session.setAttribute("error", "Unexpected state returned from authority.");
          System.out.println("\n\nUnexpected state returned from authority.");
        }

        return "authtoken";
    }

    @RequestMapping(value = "/getToken", method = RequestMethod.POST)
    public void getToken(
            HttpServletRequest servletRequest, 
            HttpServletResponse servletResponse) {

        try{

        HttpSession session = servletRequest.getSession();
        String strCode = (String) session.getAttribute("authCode");

        HttpClient httpClient = HttpClients.createDefault(); 
        HttpPost httpPost = new HttpPost(tokenUrl); 
        httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
           List <BasicNameValuePair> params = new ArrayList<>();
           params.add(new BasicNameValuePair("client_id", appId));
           params.add(new BasicNameValuePair("client_secret", appPassword));
           params.add(new BasicNameValuePair("redirect_uri", redirectUrl));
           params.add(new BasicNameValuePair("code", strCode));
           params.add(new BasicNameValuePair("grant_type", "authorization_code"));

           httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
           httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");

           HttpResponse httpResponse = httpClient.execute(httpPost);
           org.apache.http.HttpEntity entity = httpResponse.getEntity();
           //String theString = IOUtils.toString(entity.getContent(), "UTF-8");
           String strResponse = EntityUtils.toString(entity, "UTF-8");
           System.out.println(strResponse);
           strResponse = "{\"response\":["+strResponse+"]}";
           System.out.println(strResponse);

           JSONObject result = new JSONObject(strResponse); //Convert String to JSON Object
           JSONArray tokenList = result.getJSONArray("response");
           JSONObject objJson = tokenList.getJSONObject(0);
           String accessToken = objJson.getString("access_token");
           System.out.println(accessToken);

           session.setAttribute("accessToken", accessToken);

        }catch(Exception e){
            e.printStackTrace();
        }
    }

    @RequestMapping(value = "/showToken", method = RequestMethod.POST)
    public String showToken(
            @RequestParam("token_type") String code, 
            @RequestParam("expires_in") String idToken,
            @RequestParam("access_token") String accessToken, 
            //@RequestParam("scope") String paramScope,
            HttpServletRequest servletRequest, 
            HttpServletResponse servletResponse) {

        return "getToken";

    }

    @RequestMapping("/logout")
    public String logout(HttpServletRequest request) {
      HttpSession session = request.getSession();
      session.invalidate();
      return "index";
    }

    private static String getScopes() {
            StringBuilder sb = new StringBuilder();
            for (String scope: scopes) {
              sb.append(scope + " ");
            }

            String strscope = sb.toString().trim();
            System.out.println(strscope);

            return strscope;
    }
}