Lessons

Sign in with Google using simple Node.js, HTML, & Javascript

In this lesson, we will setup the ability to sign in with Google. You need to have a basic understanding of Node.js, HTML, and Javascript. The Node.js portion will not be super indepth because we just need the endpoint to load our HTML page. Make sure you have Node.js install on your local machine. You will also need a Google account for application ownership and testing.

Setup a folder for your project. This will be our workspace.

Node Server Setup

Start by creating package.json in the root of your workspace.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "name": "lesson-demo", "version": "1.0.0", "description": "``` node index.js ```", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "Keith Weaver me@keithweaver.ca> (http://keithweaver.ca)", "license": "ISC", "dependencies": { "express": "^4.17.1" } }

You should update the name, version, descirption, author and pick a license. Once you've done all of this. Create index.js. This will be our router to the different endpoints and it will start looking like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send("Hello world"); }); app.get('/signin', (req, res) => { // res.sendFile(__dirname + '/signin.html'); res.send("Hello world 2"); }); const PORT = 3000; app.listen(PORT, () => { console.log(`server running on port ${PORT}`) });

In the code above, we have setup two endpoints. One is / and the other is /signin. As you can tell we will be loading an HTML for /signin but first we want to test everything is setup.

Run the following commands:

1 2 npm install node index.js

This should start a Node server on port 3000. You can open your browser to http://localhost:3000. You should see:

Screenshot of Github Workflow Running

If you change the URL to be http://localhost:3000/signin, you should see:

Screenshot of Github Workflow Running

Your node server is setup.

HTML Page

Next, in the root create signin.html. Inside put:

1 2 3 4 5 html> body> Hello world from HTML /body> /html>

Open the index.js, and change the sign in endpoint to be:

1 2 3 app.get('/signin', (req, res) => { res.sendFile(__dirname + '/signin.html'); });

Reload your Node server and check the endpoint /signin. This time you should see:

Screenshot of Github Workflow Running

We are now successfully loading an HTML page from the Node server. Let's make it a little more complicated. Update the HTML to be:

1 2 3 4 5 6 7 8 9 10 head> script src="https://apis.google.com/js/platform.js" async defer>/script> meta name="google-signin-client_id" content="client_id>.apps.googleusercontent.com"> /head> body> div class="g-signin2" data-onsuccess="onSignIn">/div> !-- JS scripts --> script src="./google-oauth.js">/script> /body> /html>

You will notice two things. We are missing client_id> for the script in the header and we have not create google-oauth.js. It may seem backwards, but we are going to create our final script before registering the application on the Google console.

Create google-oauth.js:

1 2 3 4 5 6 7 8 9 10 11 function onSignIn(googleUser) { var profile = googleUser.getBasicProfile(); var id_token = googleUser.getAuthResponse().id_token; console.log('id_token:', id_token); console.log('ID: ' + profile.getId()); // Do not send to your backend! Use an ID token instead. console.log('Name: ' + profile.getName()); console.log('Image URL: ' + profile.getImageUrl()); console.log('Email: ' + profile.getEmail()); // This is null if the 'email' scope is not present. // Add ability to send to your server }

This is the final script required. You will need to most likely send the information to your server with something like:

1 2 3 4 5 6 7 8 9 10 11 12 13 body['idToken'] = id_token; body['id'] = profile.getId(); body['name'] = profile.getName(); body['imageURL'] = profile.getImageUrl(); body['email'] = profile.getEmail(); var xhr = new XMLHttpRequest(); xhr.open('POST', 'http://localhost:8080/signin'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function() { console.log('Signed in as: ' + xhr.responseText); }; xhr.send(JSON.stringify(body));

But this is your call.

Google App Registration

Open Google's Identity Platform. Find the create project. You'll need to enter a project name.

Screenshot of Github Workflow Running

Head to the Credentials section on the left navbar.

Hit create credentials > OAuth Client Id.

You may need to configure the consent screen.

Screenshot of Github Workflow Running

Screenshot of Github Workflow Running

Enter in:

*Application name *Application logo *Support email *Scopes for Google API: email, profile, openId *Authorized domains: Empty for now *Application Homepage link: Empty for now *Application Privacy Policy link: Empty for now *Application Terms of Service link: Empty for now

You will be returned to the create OAuth Client id screen. Select "Web Application". Enter in a name that represents where this client id will be used. Ex. If you have a mobile and web app, maybe create two?

Authorized Javascript Origin will be http://localhost:3000. Redirect URL will be http://localhost:3000/auth. Hit "Create".

You can try out this flow, it will fail by updating the > values and pasting into your browser.

1 https://accounts.google.com/o/oauth2/v2/auth?client_id=client_id>&response_type=code&scope=https://www.googleapis.com/auth/userinfo.profile&redirect_uri=http://localhost:3000/auth

You will redirect through the consent screen and eventually end up on the http://localhost:3000/auth with a code parameter. From this auth page, we will do a POST request (via signin API endpoint) with the code attribute. This will hit https://www.googleapis.com/oauth2/v4/token using client id and client secret. It will return an access token which will be stored in our sessions.

Verifying on Server

This portion will depend on langauge that you are using but you can verify the information you were provided from the frontend is valid using a Google API. This is a required step because someone could potentially manually interface with your API and attempt a bunch of values.

This is how you do it in Java:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import com.google.gson.Gson; import com.google.gson.JsonObject; import java.io.*; import java.net.*; public class GoogleAuthUtil { public static boolean isValidAccessToken(String idToken) { try { String responseAsStr = get("https://oauth2.googleapis.com/tokeninfo?id_token=" + idToken); if (responseAsStr == null) { return false; } System.out.println(responseAsStr); JsonObject response = new Gson().fromJson(responseAsStr, JsonObject.class); if (response.has("error")) { return false; } return true; } catch (IOException e) { e.printStackTrace(); return false; } } // https://developers.google.com/identity/sign-in/web/backend-auth private static String get(String url) throws IOException { URL obj = new URL(url); HttpURLConnection con = (HttpURLConnection) obj.openConnection(); // optional default is GET con.setRequestMethod("GET"); int responseCode = con.getResponseCode(); if (responseCode != 200) { return null; } System.out.println(" Sending 'GET' request to URL : " + url); System.out.println("Response Code : " + responseCode); BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); String inputLine; StringBuffer response = new StringBuffer(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); //print result return response.toString(); } }