0.4.1 • Published 3 years ago

simple-google-openid v0.4.1

Weekly downloads
6
License
MIT
Repository
github
Last release
3 years ago

node-simple-google-openid

This is a simple library providing two Express.js middlewares for using Google as the authenticator of your application's users.

All authentication workflows are done in the client's browser; to authenticate with a server, the browser sends a JSON Web Token to your server's API.

In the server, this package will add req.user structured like a Passport User Profile.

This package makes use of Google Auth Library.

Status

  • works for me, does what I need
  • let me know if it doesn't work for you or if you'd like any new functionality

Installation

npm install simple-google-openid

Usage

Express middleware for authentication

First, this package provides an Express.js middleware that will take an ID token from the URL query (parameter id_token) or from a bearer token (HTTP header Authorization: Bearer TOKEN).

To add the middleware to your app, you need to give it your CLIENT_ID (see Create a Google Developers Console project and client ID). A full working example is included below.

const GoogleAuth = require('simple-google-openid');

…

app.use(GoogleAuth(CLIENT_ID));

If an ID token is found and successfully parsed, the middleware will add req.user like Passport User Profile.

How to require authentication

To require authentication for a part of your app (e.g. /api/*), you can use the guardMiddleware; this will ensure that the app returns 401 Unauthorized on requests without an authentication token.

app.use('/api', GoogleAuth.guardMiddleware());

This package does not provide any authorization – guardMiddleware lets in any signed-in Google user. Your app needs to provide authorization logic.

Use outside of Express.js

To get a token from the Authorization header in an HTTP request, you can use the getAuthToken() function:

const token = getAuthToken(req);

Then to verify it, read on.

Verifying tokens

If you get ID tokens with getAuthToken() or some other way outside of the Authorization header (e.g. as part of WebSocket messages), you can verify them and get the user information using the verifyToken() function that returns a Promise.

Using await/async:

const auth = GoogleAuth(CLIENT_ID);

…

const token = ...; // get token from somewhere
const user = await auth.verifyToken(token);
if (!user) ...; // token was not valid (e.g. expired)

Using .then():

const auth = GoogleAuth(CLIENT_ID);

…

const token = ...; // get token from somewhere
auth.verifyToken(token)
  .then((user) => {
    if (!user) ...; // token was not valid (e.g. expired)
  });

Minimal skeleton of an authenticated web page

Here's what we need to do in a web page to get the user authenticated. This follows a guide from Google: Integrating Google Sign-In into your web app. A full working example is included further down on this page.

<!doctype html>
<title>TITLE</title>

<!-- this loads google libraries -->
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="CLIENT_ID">

<!-- this puts a sign-in button, and a sign-out link, in the page -->
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<p><a href="#" onclick="signOut();">Sign out</a></p>

<!-- this shows how the page can use the information of the authenticated user -->
<script>
function onSignIn(googleUser) {
  // do something with the user profile
}

async function signOut() {
  await gapi.auth2.getAuthInstance().signOut();
  // update your page to show the user's logged out, or redirect elsewhere
}

// example that uses a server API and passes it a bearer token
async function callServer() {
  const id_token = gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse().id_token;

  const fetchOptions = {
    credentials: 'same-origin',
    method: 'GET',
    headers: { 'Authorization': 'Bearer ' + id_token },
  };
  const response = await fetch(API_ENDPOINT_URL, fetchOptions);
  if (!response.ok) {
    // handle the error
    return;
  }
  // handle the response
}

// see the complete example below for an extra function that refreshes the token when the computer wakes up from a sleep
</script>

Example

Here's a full working example. First, the server that implements an API that needs to securely know users' email addresses; second, the client side.

Server-side

A full working server (server.js) follows:

const express = require('express');
const app = express();

const GoogleAuth = require('simple-google-openid');

// you can put your client ID here
app.use(GoogleAuth(process.env.GOOGLE_CLIENT_ID));

// return 'Not authorized' if we don't have a user
app.use('/api', GoogleAuth.guardMiddleware());

app.get('/api/hello', (req, res) => {
  res.send('Hello ' + (req.user.displayName || 'user without a name') + '!');

  console.log('successful authenticated request by ' + req.user.emails[0].value);
});

// this will serve the HTML file shown below
app.use(express.static('static'));

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Example app listening on port ${PORT}!`);
});

Save this file as server.js and run it with your client ID like this:

npm init -y
npm install express simple-google-openid
GOOGLE_CLIENT_ID='XXXX...' node server.js

Client-side – the web page in a browser

Now let's make a Web page (static/index.html) that authenticates with Google and uses the API above. Save this file in static/index.html, start the server above, and go to http://localhost:8080/.

This follows a guide from Google: Integrating Google Sign-In into your web app.

Don't forget to replace CLIENT_ID (on line 4) with your own client ID.

<!doctype html>
<title>Simple Google Auth test</title>
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="CLIENT_ID">


<h1>Simple Google Auth test <span id="greeting"></span></h1>

<p>Press the button below to sign in:</p>
<div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div>
<p><a href="#" onclick="signOut();">Sign out</a></p>

<p>Below is the response from the server API: <button onclick="callServer()">refresh</button>
<pre id="server-response" style="border: 1px dashed black; min-width: 10em; min-height: 1em; padding: .5em;"></pre>

<script>
function onSignIn(googleUser) {
  const profile = googleUser.getBasicProfile();
  const el = document.getElementById('greeting');
  el.textContent = '– Hello ' + profile.getName() + '!';

  setTimeout(callServer, 100);
}
async function signOut() {
  await gapi.auth2.getAuthInstance().signOut();
  console.log('User signed out.');
  const el = document.getElementById('greeting');
  el.textContent = 'Bye!';
}

async function callServer() {
  const token = gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse().id_token;

  const el = document.getElementById('server-response');
  el.textContent = 'loading…';

  const fetchOptions = {
    credentials: 'same-origin',
    method: 'GET',
    headers: { 'Authorization': 'Bearer ' + token },
  };
  const response = await fetch('/api/hello', fetchOptions);
  if (!response.ok) {
    // handle the error
    el.textContent = "Server error:\n" + response.status;
    return;
  }

  // handle the response
  const data = await response.text();
  console.log('setting text content: ' + data);
  el.textContent = data;
}

// react to computer sleeps, get a new token; gapi doesn't do this reliably
// adapted from http://stackoverflow.com/questions/4079115/can-any-desktop-browsers-detect-when-the-computer-resumes-from-sleep/4080174#4080174
(function () {
  const CHECK_DELAY = 2000;
  let lastTime = Date.now();

  setInterval(() => {
    const currentTime = Date.now();
    if (currentTime > (lastTime + CHECK_DELAY*2)) {  // ignore small delays
      gapi.auth2.getAuthInstance().currentUser.get().reloadAuthResponse();
    }
    lastTime = currentTime;
  }, CHECK_DELAY);
}());
</script>

Logging

To enable logging into console output, set the environment variable DEBUG to a non-empty string.

TODO

Author

Jacek Kopecky

License

This project is licensed under the MIT license. See the LICENSE file for more info.

0.4.1

3 years ago

0.4.0

3 years ago

0.3.2

3 years ago

0.3.1

3 years ago

0.3.0

5 years ago

0.2.3

5 years ago

0.2.2

6 years ago

0.2.1

6 years ago

0.2.0

6 years ago

0.1.0

6 years ago

0.0.12

6 years ago

0.0.11

6 years ago

0.0.10

6 years ago

0.0.9

6 years ago

0.0.8

6 years ago

0.0.7

6 years ago

0.0.6

7 years ago

0.0.5

8 years ago

0.0.4

8 years ago

0.0.3

8 years ago

0.0.2

8 years ago

0.0.1

8 years ago