Search code examples
node.jshttpsecurityelectrongmail-api

Is it safe to distribute Google API credentials with apps?


I'm trying to make a simple desktop email reader (w/ electron) that shows a user's emails on little cards that can be flipped through. Nothing much, that being why I don't want to setup a whole remote server system for it. I followed the instructions in Google's NodeJS Quickstart guide to get started. That includes saving my Google API credentials to a file in the app. Upon login, a token is saved to disk. If no such token exists, it will open up a login page in the browser that redirects to 127.0.0.1:3000/authorize (the express app running there saves the token). It works and doesn't require a remote server which is what I want.

My question is, is it safe to distribute the credentials.json file (contains client_id, client_secret, project_id) with my app? What are the potential security issues? If this is not suitable, what is the least complicated alternative to make my app distributable safely?

Edit

I looked at Google's docs and found this.

The process results in a client ID and, in some cases, a client secret, which you embed in the source code of your application. (In this context, the client secret is obviously not treated as a secret.)

So the client_secret isn't secret in this case, right? What about the rest of credentials.json? Can someone impersonate my app and do bad things using that information?

Here's the code (it works) that does the first-time login:

function getNewToken(oAuth2Client, callback, method, args) {
    const authUrl = oAuth2Client.generateAuthUrl({
        access_type: 'offline',
        scope: SCOPES,
    });

    require('electron').shell.openExternal(authUrl);
    // mini server for authorization

    const express = require('express')()

    //express part
    express.get('/authorize', function (req, res) {
        oAuth2Client.getToken(req.query.code, (err, token) => {
            if (err) return console.error('Error retrieving access token', err);
            oAuth2Client.setCredentials(token);
            // Store the token to disk for later program executions
            fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
                if (err) return console.error(err);
                console.log('Token stored to', TOKEN_PATH);
            });
            method(oAuth2Client, callback, args);

        });
        res.sendFile(path.join(__dirname, 'authorize.html'));
    });

    express.listen(3000, () => {
        console.log(`Example app listening at http://localhost:3000`)
    })

}

And the function I use to access the API if a token is saved:

function ApiCall(method, callback, args) {
    fs.readFile('credentials.json', (err, content) => {
        if (err) return console.log('Error loading client secret file:', err);
        // Authorize a client with credentials, then call the Gmail API.
        var credentials = JSON.parse(content);
        var {client_secret, client_id, redirect_uris} = credentials.web;
        var oAuth2Client = new google.auth.OAuth2(
            client_id, client_secret, redirect_uris[0]);

        // Check if we have previously stored a token.
        fs.readFile(TOKEN_PATH, (err, token) => {
            if (err) return getNewToken(oAuth2Client, callback, method, args);
            oAuth2Client.setCredentials(JSON.parse(token));
            method(oAuth2Client, callback, args);
        });
    });
}

Solution

  • Was just looking at the google oauth github repo for another project, and came across this:

    If you're authenticating with OAuth2 from an installed application (like Electron), you may not want to embed your client_secret inside of the application sources. To work around this restriction, you can choose the iOS application type when creating your OAuth2 credentials in the Google Developers console:

    https://github.com/googleapis/google-auth-library-nodejs#oauth2-with-installed-apps-electron

    Not sure how that prevents another app from using your client ID in the same fashion, but it's their suggestion.