Search code examples
javascriptoauth-2.0aws-api-gatewayamazon-cognito

Error 400 Cognito /oauth2/token endpoint from client-side javascript


I am using AWS Cognito-hosted UI for my signup and login. There is no app client secret defined.

I am trying to make an API call from the browser javascript code to the /oauth2/token endpoint in order to exchange autohorization_token with an ID token.

The problem is, when I make the call through Postman, Insomnia it works fine. However, when I make the same call through javascript from the browser it fails with the 400 response type and I can't get much about the reason.

This is the Insomnia call which is a success;

enter image description here

However, when I make the same call via javascript it fails. I have used both fetch and XMLHttpRequest and the same result.

const XHR = new XMLHttpRequest();
    
let urlEncodedData = "",
    urlEncodedDataPairs = [],
    name;

urlEncodedDataPairs.push( encodeURIComponent( 'grant_type' ) + '=' + encodeURIComponent( 'authorization_code' ) );
urlEncodedDataPairs.push( encodeURIComponent( 'code' ) + '=' + encodeURIComponent( code ) );
urlEncodedDataPairs.push( encodeURIComponent( 'client_id' ) + '=' + encodeURIComponent( 'xxxxx' ) );
urlEncodedDataPairs.push( encodeURIComponent( 'redirect_url' ) + '=' + encodeURIComponent( 'https://www.xxx.me/xxx' ) );
    
XHR.addEventListener( 'load', function(event) {
   alert( 'Yeah! Data sent and response loaded.' );
} );
    
// Define what happens in case of error
XHR.addEventListener( 'error', function(event) {
    alert( 'Oops! Something went wrong.' );
});
    
// Set up our request
XHR.open( 'POST', 'https://xxx.auth.us-west-2.amazoncognito.com/oauth2/token' );
    
// Add the required HTTP header for form data POST requests
XHR.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
    
// Finally, send our data.
XHR.send( urlEncodedData );

This is the response I am getting:

enter image description here

I have tried the same request with fetch and the result is the same.

   let tokenRequest = new Request(tokenURL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
                'Origin' : 'https://www.xxx.me'
            },
            body: new URLSearchParams({
                'grant_type': 'authorization_code',
                'code': code,
                'client_id': 'xxx',
                'redirect_url': 'https://www.xxx.me/xxx'
            }).toString()

        });

        let response = await fetch(tokenRequest);
        let data = await response.json();

One thing I have realized when I check the browser developer tool for other POST calls made to auth endpoints with content type=application/x-www-form-urlencoded, it shows query params added to URL like shown below, however, for my call, params are not encoded as part of URL. I am not sure if the problem might be related to this.

enter image description here

Any idea how I can make this call with client-side javascript?


Solution

  • Looks like the browser is using redirect_url which is wrong, and Postman is using redirect_uri which is correct. Also you should use PKCE to make the message more secure - see steps 4 and 8 of my blog post to understand how this looks.

    Note also that in OAuth it is common to not return error details, or return to the app, if an unrecognised Client ID or Redirect URI are supplied. If these are both correct you should instead receive the standard OAuth error and error_description fields in a response payload.