connect html button to access google's people API

I have a button in my HTML body, which is supposed to load a user's google contacts when a user clicks the button. I've registered for the required credentials and authorization via Google Cloud Platform, but for some reason, the javascript code I have is not working and clicking the button in demo mode in Visual studio does not open a new window asking me to login into my gmail account, permission for the website to access my contacts, etc. Any help in getting the API to work on my website will be much appreciated.

<button id="google-button" onclick="return auth()" style="color:white;background-color:royalblue;margin:20px;padding:5px 10px;border-style:solid;font-weight:bold">Share to all of your Google contacts</button>
                    <script>                        
                        function auth() {
                            const fs = require('fs');
                            const readline = require('readline');
                            const { google } = require('googleapis');

                            // If modifying these scopes, delete token.json.
                            const SCOPES = ['https://www.googleapis.com/auth/contacts.readonly'];
                            // The file token.json stores the user's access and refresh tokens, and is
                            // created automatically when the authorization flow completes for the first
                            // time.
                            const TOKEN_PATH = 'token.json';

                            // Load client secrets from a local file.
                            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 Google Tasks API.
                                authorize(JSON.parse(content), listConnectionNames);
                            });

                            function authorize(credentials, callback) {
                                const { client_secret, client_id, redirect_uris } = credentials.installed;
                                const 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);
                                    oAuth2Client.setCredentials(JSON.parse(token));
                                    callback(oAuth2Client);
                                });
                            }

                            function getNewToken(oAuth2Client, callback) {
                                const authUrl = oAuth2Client.generateAuthUrl({
                                    access_type: 'offline',
                                    scope: SCOPES,
                                });
                                console.log('Authorize this app by visiting this url:', authUrl);
                                const rl = readline.createInterface({
                                    input: process.stdin,
                                    output: process.stdout,
                                });
                                rl.question('Enter the code from that page here: ', (code) => {
                                    rl.close();
                                    oAuth2Client.getToken(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);
                                        });
                                        callback(oAuth2Client);
                                    });
                                });
                            }

                            function listConnectionNames(auth) {
                                const service = google.people({ version: 'v1', auth });
                                service.people.connections.list({
                                    resourceName: 'people/me',
                                    pageSize: 10,
                                    personFields: 'names,emailAddresses',
                                }, (err, res) => {
                                    if (err) return console.error('The API returned an error: ' + err);
                                    const connections = res.data.connections;
                                    if (connections) {
                                        console.log('Connections:');
                                        connections.forEach((person) => {
                                            if (person.names && person.names.length > 0) {
                                                console.log(person.names[0].displayName);
                                            } else {
                                                console.log('No display name found for connection.');
                                            }
                                        });
                                    } else {
                                        console.log('No connections found.');
                                    }
                                });
                            }
                        }
                    </script>

1 answer

  • answered 2019-08-13 16:54 IronFlare

    The code in your <script> tag is server-side Node.js code, not client-side JavaScript. It will not function in the browser because:

    • require('fs') imports the filesystem module, but no such thing exists outside of Node.js.
    • readline and googleapis are also Node-specific modules, so they have no meaning in client-side JS and will probably throw errors if require hasn't already.
    • fs.readFile(...) attempts to use the fs module (see above) to read a file at a certain path, but client-side JavaScript doesn't have access to the filesystem.
    • Fundamentally, OAuth negotiation should be handled on the server, not on the client. Typically, a privileged request will use data from your database, which it cannot do if the token is stored on the client.

    It seems like the main problem here is confusion about what OAuth is and how it works. Here's a simplified step-by-step walkthrough of the process:

    1. The user logs into an external service from their client.
    2. The external service generates a code (a token) and sends it to the client.
    3. The client receives the token (in a callback, hash param, etc.) and sends it to your server. Most often the external service will simply redirect the client to a URL on your server with the token in the query string, allowing you to grab it out of the request.
    4. Your server stores the token for a specific user account in your database.
    5. For privileged actions, your server sends the request to the external service and includes the token.
    6. The server receives your request with the token and performs an action on the user's behalf.

    When the external service receives a request with a token, it looks up that token and sees that it belongs to a specific user. Because that user must have logged in and authorized your app in order to create the token, the service knows that it should proceed with the action.

    OAuth tokens may be permanent, but much more often they will expire after a set period of time and have to be regenerated. This means that you should never be using the token as a primary key to identify a user. As for how to regenerate an expired token, the exact details vary by provider. The service you're using (Google, in this case) will have more information on how their auth flow works, and how refreshing should be handled.