Search code examples
node.jsgoogle-apigoogle-oauthgoogle-cloud-iot

Authentication Error when Retrieving and Editing Device Configuration on IoT-Core


I'm trying to use a backend nodeJS server to access (and edit) the device configuration on IoT-Core referring to this API docs

However, I keep getting error:

code 401 with error message "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.", "status": "UNAUTHENTICATED".

I created a service account and a key from Google IAM, and gave it Cloud IoT Device Controller permissions, which could update device configurations but not create or delete. Subsequently, I changed it to Cloud IoT Admin and even Project Editor permissions, but still saw the same error message. Am I getting the keys all wrong, or not doing something else I should be doing?

Code below was how I invoked the request

function createJwt (projectId, privateKeyFile, algorithm) {
    // Create a JWT to authenticate this device. The device will be disconnected
    // after the token expires, and will have to reconnect with a new token. The
    // audience field should always be set to the GCP project ID.
    const token = {
      'iat': parseInt(Date.now() / 1000),
      'exp': parseInt(Date.now() / 1000) + 20 * 60,  // 20 minutes
      'aud': projectId
    };
    const privateKey = fs.readFileSync(privateKeyFile);
    return jwt.sign(token, privateKey, { algorithm: algorithm });
}

app.get('/', function(req, res){

    let authToken = createJwt('test-project', './keys/device-config.pem', 'RS256');

    const options = {
        url: 'https://cloudiot.googleapis.com/v1/projects/test-project/locations/us-central1/registries/dev-registry/devices/test-device',
        headers: {
            'authorization': 'Bearer ' + authToken,
            'content-type': 'application/json',
            'cache-control': 'no-cache'
        },
        json: true
    }

    request.get(options, function(error, response){
        if(error) res.json(error);
        else res.json(response);
    })

});

Solution

  • For backend servers to interact with IoT-Core, the authentication method is not the same as for device MQTT or HTTP connections. Reference: https://cloud.google.com/iot/docs/samples/device-manager-samples#get_a_device

    I was able to retrieve and update device configurations using the code below

    function getClient (serviceAccountJson, cb) {
        const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountJson));
        const jwtAccess = new google.auth.JWT();
        jwtAccess.fromJSON(serviceAccount);
        // Note that if you require additional scopes, they should be specified as a
        // string, separated by spaces.
        jwtAccess.scopes = 'https://www.googleapis.com/auth/cloud-platform';
        // Set the default authentication to the above JWT access.
        google.options({ auth: jwtAccess });
    
        const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
        const API_VERSION = 'v1';
        const discoveryUrl = `${DISCOVERY_API}?version=${API_VERSION}`;
    
        google.discoverAPI(discoveryUrl, {}, (err, client) => {
            if (err) {
            console.log('Error during API discovery', err);
            return undefined;
            }
            cb(client);
        });
    }
    
    function getDevice (client, deviceId, registryId, projectId, cloudRegion) {
        const parentName = `projects/${process.env.GCP_PROJECT_ID}/locations/${cloudRegion}`;
        const registryName = `${parentName}/registries/${registryId}`;
        const request = {
          name: `${registryName}/devices/${deviceId}`
        };
    
        const promise = new Promise(function(resolve, reject){
            client.projects.locations.registries.devices.get(request, (err, data) => {
                if (err) {
                    console.log('Could not find device:', deviceId);
                    console.log(err);
                    reject(err);
                } else {
                    console.log(data.config.binaryData);
                    resolve(data);
                }
            });
    
        });
        return promise;
    }
    
    app.get('/', function(req, res){
        const cb = function(client){
            getDevice(client, 'test-device', 'dev-registry', process.env.GCP_PROJECT_ID, 'us-central1')
                .then(function(response){
                    let decoded = new Buffer(response.config.binaryData, 'base64').toString();
                    res.json(decoded);
                })
                .catch(function(error){
                    res.json(error);
                })
        }
        getClient(serviceAccountJson, cb);
    
    });