I recently worked on a project adding authentication to all the internal tools at a company. Because the company used Google Suite, and every employee and contractor had a company google email, I decided it would be a good idea to leverage Google SignIn as a form of authentication. The alternative solutions would be to use an Authentication-as-a-Service such as Auth0 or Okta, but based on the company’s budget and available resources, Google was very appropriate. It was also one less vendor for the company to deal with.

Here is a boiler plate for implementing Authorization Code Flow by using Google SignIn with React and Lambda Authorizer.

Authorization Code Flow Authorization Code Flow. Image created by me using Lucid Chart.

Login

  1. User clicks on the HTML button on the Web App.
  2. HTML button redirects user to Google login page for the application (registered in console developer.google.com).
  3. Google returns to the OAuth2Callback (which is a url on the web app).
  4. Web App passes authorization code to Auth App Login Endpoint via javascript.
  5. Auth App Login Endpoint exchanges the code for an access token on Google’s oauth endpoint. a. Auth App Login Endpoint uses the access token to query Google for a list of users under Cala Health’s organization. b. It saves or updates the Auth database with this list. c. It then generates a session token, which contains information on the user (id/sub, email, expiration, roles assigned to this user) and the session (session key). This session token is return to the Web App.
  6. Web App checks that user has access to the application.
  7. If user does not have access, invalidate the token, and show Not Authorized Page.
  8. If user has access, Web App saves session token to sessionStorage.

Authenticated Calls

  1. Web App gets session token from sessionStorage.
  2. Web App makes HTTP request that includes the token in the Authorization header.
  3. API Gateway receives the Authorization header, invokes the Lambda Authorizer, which validates the session token and returns a policy document. a. If the policy document is an Allow on the API resource being called, API Gateway will invoke the Lambda function. b. If the policy document is a Deny, API Gateway returns a 403.

Logout

  1. Web App sends an HTTP request to the Auth Logout Endpoint with the session token in the Authorization header.
  2. Lambda function behind the Logout endpoint clears out the session data in the database and logs the time of logout.
  3. Web App clears sessionStorage.
  4. Web App redirects to login page.

Session Expire

  1. Web App checks sessionToken “exp” field for expiration every 30 seconds.
  2. When expired, Web App clears sessionStorage and redirects to login page.

Google Signin Button

Download google’s signin button assets from: https://developers.google.com/identity/images/signin-assets.zip

import React from 'react';
import Button from 'react-bootstrap/Button';
import Constants from '../constants';
const GoogleButtonImg = require('../images/btn_google_signin_light_normal_web@2x.png');

export default class Login extends React.Component {
    constructor(props) {
        super(props);
        this.oauthSignIn = this.oauthSignIn.bind(this);
    }

    oauthSignIn() {
        // get initial session token
        // Google's OAuth 2.0 endpoint for requesting an access token
        var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
        // Create <form> element to submit parameters to OAuth 2.0 endpoint.
        var form = document.createElement('form');
        form.setAttribute('method', 'GET'); // Send as a GET request.
        form.setAttribute('action', oauth2Endpoint); // Parameters to pass to OAuth 2.0 endpoint.
        var params = {
            'client_id': GOOGLE_CLIENT_ID,
            'redirect_uri': REDIRECT_URI,
            'response_type': 'code',
            'scope': 'email profile openid
            https://www.googleapis.com/auth/admin.directory.user.readonly',
            'include_granted_scopes': false,
            'prompt': 'select_account',
        };
        // Add form parameters as hidden input values.
        for (var p in params) {
            var input = document.createElement('input');
            input.setAttribute('type', 'hidden');
            input.setAttribute('name', p);
            input.setAttribute('value', params[p]);
            form.appendChild(input);
        }
        // Add form to page and submit it to open the OAuth 2.0
        endpoint.
        document.body.appendChild(form);
        form.submit();
    }

    render() {
        return (
        <Button variant="link" onClick={this.oauthSignIn} size="lg">
            <img alt='google login button' width="250"
            src={String(GoogleButtonImg)} />
        </Button>
        );
    }
}

Note: Not using state parameter for google’s oauth2 endpoint.

Storing Session Token

Store the token in the sessionStorage. Do not store in localStorage.

Getting Data from Session Token

Javascript function for parsing the token.

function parseJwt (token) {
    var base64Url = token.split('.')[1];
    var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    var jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
    return JSON.parse(jsonPayload);
};

Making API Calls With Token

API Gateway uses standard Bearer token in the Authorization field of the HTTP header. Below is an XMLHttpRequest with standard bearer token.

var url = "https://SOME-API/data";
var http = new XMLHttpRequest();
http.open('GET', url, true);
http.onreadystatechange = function() {
    if(http.readyState === 4 && http.status === 200) {
        console.log(http.responseText);
    }
}
// get token from storage
let token = window.sessionStorage.getItem('token');

http.setRequestHeader('Accept', 'application/json');
http.setRequestHeader('Authorization', 'Bearer ' + token);
http.send(params);

Check On / Logout On Session Expire

Periodically check for session expiration.

setInterval(checkSessionTimeout, 30000);
checkSessionTimeout() {
    let now_milliseconds = (new Date).getTime(); // milliseconds
    let now = now_milliseconds / 1000; // seconds

    // get token from storage
    let token = window.sessionStorage.getItem('token');
    let data = parseJWT(token);
    let exp = data["user"]["exp"];

    if (now > exp) {
        // expired
        logout();
    }
}

Clear sessionStorage and redirect to login page.

logout() {
    window.sessionStorage.clear();

    // redirect to login page
}

Redirect Page On Logout

React specific. The header is always rendered, whether the user is logged in or not. Use the header to manage logins and logouts.

Notes

Google Auth Implementation

There are several libraries available for adding SignIn with Google button to the app. HTTP Request was chosen because it was the most lightweight and versatile option.

Google SDK

A javascript library for frontend. Documentation is outdated.

React-Google-Login

This library seemed great for react apps, but it is only able to handle pop-up UX. It does not handle redirect UX.

HTTP Request

Simple, applies to all, no need for downloading packages.