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?
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);
});
});
}
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.