Google Sign-In Integration

January 10, 2022

One of my larger work projects at in 2021 was to implement Google Sign-In authentication as a method for signup (account creation) and login. This is an overview of the existing architecture and the changes and decisions made. I’m curious if you’ll have suggestions for alternative approaches.

The Setting

The business hypothesis was that signups would increase. But if you follow that logic through, the corollary is that people are giving up on signing up because they don't want to create a password. That feels unlikely?? Regardless, we thought it would be nice and make our application look more professional and trustworthy. And doesn't it, tho?

I was already familiar with the current architecture for signup (having implemented self-serve signup) but for this project I needed to get familiar with our existing authentication methods (password, API token, Guest token, SAML, and PKI), and authorizations.

Architecture

Note to the reader: The fact that I'm writing about this architecture does not necessarily mean it represents best practices. I'm open to comments and discussion. You may have or need a different architecture depending on your requirements.

Signup

When a visitor signs up, they provide a name, email, and password. Users must have a unique email address.

The self-serve signup process creates an organization for the user as all user work must be associated with an organization.

To join an existing organization, there are two invitation methods:

  • Email: An organization admin sends an invitation to the user's email. The user cannot change this email address. (Note: This is an important design decision and completely avoids a need to link existing accounts when an additional account is accidentally generated by the user.)
  • Invite URL: A potential user visits a unique signup URL that was created for the organization. The user must provide an email address which matches a configured host domain.

To prevent bot signups, a hidden Google Recaptcha creates a token which includes Google's conclusion on whether the visitor is a bot. The token is added to the signup submission data and verified serverside.

Because a user's email address represents their identity, email verification is paramount and required before the user gains access to an account. Email invitees don't need to have a verification email sent to them because they have already proven email ownership by following the invitation link emailed to them.

Login

To log in, a user must provide the correct email and password combination. The login page posts the email and password to the session creation endpoint which authenticates the data and, if valid, fetches the user's API token and additional account information. This information is encoded and stored in a session cookie.

The authentication step, in addition to verifying identity, returns the user's authorization, a definition of their access privileges such as member, owner, admin, or, in the case of guests, read_only.

Information privacy measures

To avoid leaking information about whether an email address is already associated with an account, the signup process shows everyone the same success message to check for a verification email. In fact, a user who has an existing account will receive a different email letting them know that they tried to signup and already have an account.

Similarly, when a visitor attempts to login, the same failure message is shown to everyone regardless of whether or not the email address is associated with an account.

Integration

Since I'd never worked on authentication before, I was really starting with some basic questions. How do we know someone is who they say they are? Who can we trust?

With Google Sign-In, the client application is asking Google to authenticate the user. Does this user have an account with Google? Are they currently authenticated with Google? If not, we're happy to wait while they authenticate with Google right now.

How it works

The clientside application must initialize the Google Sign-In JS with our organization's client ID. This script can be used to generate a "Sign in with Google" button that can be placed where desired.

When the user clicks on the "Sign in with Google" button, a new browser window opens where the user is asked to select a Google account that they are already logged into or allowed to log into a different account. Once they have selected and authenticated an account, they are shown a consent screen declaring the content that Google will share with your organization (in this case, just profile info like name and email).

When the user confirms, the window is closed and the clientside Google Sign-In script executes a callback with a JSON Web Token (JWT) as an argument. The JWT may be decoded (there are third-party libraries for decoding) to access data about the user.

The JWT should be sent, still encoded, to your server to complete the account creation. A serverside Google Sign-In library is very useful to decode and verify the JWT.

The process is the same for login. There are options in the clientside JS to select the label and styles of the Google Sign-In button.

Next Time

What would I do differently next time?

Well, I didn't give alternatives a serious thought. auth0, okta? Would these be easier? If you have an opinion, I'd love to hear it. I liked working with the source, Google itself, rather than an intermediary, to learn about Google Sign-In.

I knew I didn't care about adding other signup methods, like Facebook or Github, which would be the advantage of these other solutions.

I also didn't need to look into handling Google authorizations beyond user profile information. Connecting a user's Google Calendar or Google Docs data is an additional set of concerns.

PS: Dear Product Team

One of the improvements I think could be made to Google Sign-In, as a user, would be some indication of which account I had signed in with before, if at all.

I'm usually authenticated in 3 different Google accounts at a time and I have many more.

When asked to authenticate with Google, I forget which Google account I used to login and accidentally create a new account (depending on the implementation of said app).

This is the main reason I don't use Google Sign-In unless required. I don't like accidentally creating multiple accounts. What's an extra account? It's not like the stack of loyalty coffee cards that stretch out my wallet which in turn wears a hole in my jean pocket.

To deal with this, this little pet peeve, I create 1Password logins for sites even when I use Google Sign-In so that I can leave a note to myself about which Google account I used. Dear 1Password product team…

Resources

  • If you're doing a Google Sign-In integration, start with the official docs.
  • Documentation: Google Auth Library: Node.js Client
  • Example JWT

    Here's an example JWT from the docs:

     1// header
     2{
     3  "alg": "RS256",
     4  "kid": "f05415b13acb9590f70df862765c655f5a7a019e", // JWT signature
     5  "typ": "JWT"
     6}
     7// payload
     8{
     9  "iss": "[https://accounts.google.com](https://accounts.google.com/)", // The JWT's issuer
    10  "nbf":  161803398874,
    11  "aud": "[314159265-pi.apps.googleusercontent.com](http://314159265-pi.apps.googleusercontent.com/)", // Your server's client ID
    12  "sub": "3141592653589793238", // The unique ID of the user's Google Account
    13  "hd": "[gmail.com](http://gmail.com/)", // If present, the host domain of the user's GSuite email address
    14  "email": "[elisa.g.beckett@gmail.com](mailto:elisa.g.beckett@gmail.com)", // The user's email address
    15  "email_verified": true, // true, if Google has verified the email address
    16  "azp": "[314159265-pi.apps.googleusercontent.com](http://314159265-pi.apps.googleusercontent.com/)",
    17  "name": "Elisa Beckett",
    18  // If present, a URL to user's profile picture
    19  "picture": "[https://lh3.googleusercontent.com/a-/e2718281828459045235360uler](https://lh3.googleusercontent.com/a-/e2718281828459045235360uler)",
    20  "given_name": "Elisa",
    21  "family_name": "Beckett",
    22  "iat": 1596474000, // Unix timestamp of the assertion's creation time
    23  "exp": 1596477600, // Unix timestamp of the assertion's expiration time
    24  "jti": "abc161803398874def"
    25}