title: MQTT connection for users authenticated via Cognito Identity Pool
body: <p>Shortly: I have a Federated Identity Pool which has roles for both unauthenticated and authenticated access. I have no problems with unauthenticated access. But when it comes to authenticated access, the user logs in just fine, but my role for the authenticated users does not get actually applied.</p>
  
  <hr>
  
  <p>I have an s3 bucket with simple index.html and index.js files which communicate via MQTT.</p>
  
  <p>Both policies for authenticated and unauthenticated users look exactly the same right now, and are fairly permissive (of course it's not the way to go for production, but I'm just trying to make it work in any way so far). So, both policies look as follows:</p>
  
  <pre><code>{
      "Version": "2012-10-17",
      "Statement": [
          {
              "Effect": "Allow",
              "Action": "*",
              "Resource": "*"
          }
      ]
  }
  </code></pre>
  
  <p>And in index.js I can establish MQTT connection as an unauthenticated user, as follows:</p>
  
  <pre><code>var region = 'eu-west-1';
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: 'my-identity-pool-id',
  });
  AWS.config.credentials.clearCachedId();
  AWS.config.credentials.get(function(err) {
    console.log('accessKeyId:', AWS.config.credentials.accessKeyId);
    if(err) {
      console.log(err);
      return;
    }
    var requestUrl = SigV4Utils.getSignedUrl(
      'wss',
      'data.iot.' + region + '.amazonaws.com',
      '/mqtt',
      'iotdevicegateway',
      region,
      AWS.config.credentials.accessKeyId,
      AWS.config.credentials.secretAccessKey,
      AWS.config.credentials.sessionToken
    );
  
    initClient(requestUrl);
  });
  </code></pre>
  
  <p><code>initClient()</code> just establishes MQTT connection by means of <code>Paho</code>:</p>
  
  <pre><code>function initClient(requestUrl) {
    var clientId = String(Math.random()).replace('.', '');
    var rpcId = "smart_heater_" + String(Math.random()).replace('.', '');
    var client = new Paho.MQTT.Client(requestUrl, clientId);
    var connectOptions = {
      onSuccess: function () {
        console.log('connected');
        // Now I can call client.subscribe(...) or client.send(...)
      },
      useSSL: true,
      timeout: 3,
      mqttVersion: 4,
      onFailure: function (err) {
        console.error('connect failed', err);
      }
    };
    client.connect(connectOptions);
  
    client.onMessageArrived = function (message) {
      console.log("msg arrived: " +  message);
    };
  }
  </code></pre>
  
  <p>And it works just fine: <code>connected</code> gets printed to the console, and I can actually send/subscribe.</p>
  
  <p>Now, I'm trying to do the same for authenticated users. For that, I added Cognito User Pool, created a user there, created an "app", added Authentication provider "Cognito" to my Identity Pool (with appropriate User Pool ID and App Client ID), and the authentication itself works just fine: user gets logged in. Code looks as follows:</p>
  
  <pre><code>var region = 'eu-west-1';
  var poolData = {
    UserPoolId: 'eu-west-1_XXXXXXXXX',
    ClientId: 'ZZZZZZZZZZZZZZZZZZZZZZZZZ',
  };
  var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
  
  var authenticationData = {
    Username: 'myusername',
    Password: 'mypassword',
  };
  var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
  var userData = {
    Username: 'myusername',
    Pool: userPool
  };
  var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
  cognitoUser.authenticateUser(authenticationDetails, {
    onSuccess: function (result) {
      console.log('result:', result);
      console.log('access token: ' + result.getAccessToken().getJwtToken());
  
      var myUserPoolId = 'eu-west-1_XXXXXXXXX';
      console.log('You are now logged in.');
  
      // Add the User's Id Token to the Cognito credentials login map.
      var logins = {};
      logins['cognito-idp.' + region + '.amazonaws.com/' + myUserPoolId] = result.getIdToken().getJwtToken();
  
      AWS.config.credentials.params.Logins = logins;
      // finally, expire the credentials so we refresh on the next request
      AWS.config.credentials.expired = true;
  
      //call refresh method in order to authenticate user and get new temp credentials
      AWS.config.credentials.refresh((error) =&gt; {
        if (error) {
          console.error(error);
        } else {
          console.log('Successfully logged!');
          console.log('accessKeyId:', AWS.config.credentials.accessKeyId);
  
          var requestUrl = SigV4Utils.getSignedUrl(
            'wss',
            'data.iot.' + region + '.amazonaws.com',
            '/mqtt',
            'iotdevicegateway',
            region,
            AWS.config.credentials.accessKeyId,
            AWS.config.credentials.secretAccessKey,
            AWS.config.credentials.sessionToken
          );
  
          initClient(requestUrl);
        }
      });
    },
  
    onFailure: function (err) {
      alert(err);
    },
  
    newPasswordRequired: function(userAttributes, requiredAttributes) {
      // User was signed up by an admin and must provide new 
      // password and required attributes, if any, to complete 
      // authentication.
  
      // the api doesn't accept this field back
      delete userAttributes.email_verified;
  
      var newPassword = prompt('Enter new password ', '');
      // Get these details and call 
      cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
    },
  });
  </code></pre>
  
  <p>The logging-in part wasn't trivial to get working, but now it works fine: in the console, I see:</p>
  
  <pre><code>You are now logged in.
  Successfully logged!
  accessKeyId: ASIAIRV4HOMOH6DXFTWA
  </code></pre>
  
  <p>And then, at the connection time, I'm getting the following error:</p>
  
  <pre><code>connect failed: {invocationContext: undefined, errorCode: 8, errorMessage: "AMQJS0008I Socket closed."}
  </code></pre>
  
  <p>I assume this is because the role, which is actually applied to the authenticated user, does not allow the action <code>iot:Connect</code>; I believe so because I can reproduce exactly the same error for unauthenticated users, if I put the following in the policy for unauthenticated users:</p>
  
  <pre><code>{
      "Action": [
          "iot:Connect"
      ],
      "Resource": "*",
      "Effect": "Deny"
  },
  </code></pre>
  
  <p>Then unauthenticated users will receive the same error <code>AMQJS0008I Socket closed</code> when they try to connect.</p>
  
  <p>So, it looks like my policy for authenticated users does not actually get applied to the authenticated user.</p>
  
  <p>I experimented a lot before writing this question. Currently, in my Identity Pool settings, in "Authenticated role selection" I have just "Use default role", which should indeed pick my authenticated role, which is:</p>
  
  <pre><code>{
      "Version": "2012-10-17",
      "Statement": [
          {
              "Effect": "Allow",
              "Action": "*",
              "Resource": "*"
          }
      ]
  }
  </code></pre>
  
  <p>But I also tried to select "Choose role with rules" and write a rule which would set my role based on some matching clause.</p>
  
  <p>I also tried to create a group in my User Pool, use the same role there, add my user to this group, and in the Identity Pool settings set "Choose role from token".</p>
  
  <p>Nothing helped. I keep getting this <code>errorMessage: "AMQJS0008I Socket closed."</code> message for authenticated users, whereas for unauthenticated users everything works just fine, even though the policies are identical.</p>
  
  <p>Any help is appreciated.</p>