danny@intricatecloud:~/blog$ cat << EOF

Adding Google Sign-in to your webapp in 2025

| 7 min read
Adding Google Sign-in to your webapp in 2025

Gotten stuck in a rabbit hole figuring out how to add "Log In with Google" to your web app?

This was an earlier series on my site from way back, but it looks like the landscape has changed so much - its worth trying to address how to do simple things like sign-in with google and use google oauth.

In this series now updated for 2025, I'll cover:

  • why you might want to use the Sign-In for Website JS library, and getting started with it
  • Adding the library via javascript
  • adding it to an existing project (angular & react)

Their docs give you a decent place to start if you know what you're looking for, but there's a few areas where their docs and other guides online can be confusing. There's also minimal guidance as to how to use it within existing projects.

"How do I add Log in with Google?" is a very loaded question, but we can distill it down to these 3 use cases

  • Do you just want to be able to see the users name and picture, maybe their email from an HTML page? Use the Sign-In with Google for Web (full walkthrough below).
  • If you are using a third-party authentication platform such as Firebase, Auth0, OneIdentity, use the OpenID Connect protocol
  • Do you have your own database of users, and you want to add Google as a login option? The Sign-In with Google for Web may be the right option.
  • Do you want to be able to interact with the users Google account and do things like see their calendar, or create a Google doc? You'll need to use their OAuth protocol.

For the purposes of this post, I'm going to explore the Sign-In with Google for Web GSI library which uses the OpenID Connect protocol.The goal of this authentication library is to let you identify a user with their Google account on a web page. Thats it.

So when should I Sign-In with Google for Web?

Good reasons to use it:

  • you have some content stored on a backend service that you deliver via an API only after the user has signed in
  • you only have to support Google/G-Suite users
  • you dont need to authorize users (e.g. allow some users to be admins)
  • your site is public
  • you have a single-page/static app

When not to use it:

  • you want to access a Google service on behalf of a user such as reading a doc/spreadsheet, search Drive, or send a Gmail.
  • you are using an existing authentication system with Google support such as Firebase, Keycloak, Auth0

Use cases where it's worth considering using the OAuth 2.0 Flow instead:

  • you have pre-existing user content stored on a backend service
  • you have an internal site that you want to restrict to users from a specific domain. (e.g. only users from @example.com should see it)
  • you want to prevent people from seeing your page unless they've logged in. The best you can do with this library is show/hide elements on the page if the user logged in, but thats enough if you only load data from an API after a user has logged in but not for gating static content.

These use cases require server-side handling alongside a working Sign-In with Google implementation. (Coming soon!)

The library is designed to be used with HTML/JS and only interacts with your page via the "Sign in with Google" button. You can integrate this with other frameworks like Angular/React which I'll be covering in the next part of this series.

Demo Overview

Here's what we're going to build, you can try it yourself with the interactive demo below

You can click on the sign in button, display some data about the user including their avatar, and then offer a way to sign out.

Get your Google Sign-In Client Credentials

Before adding in the button, you first need to create a client ID/secret which is a way of identifying that you, the developer, are allowing users to get the identity information from Google and delivered to your website.

  • Go to the Google API Console - https://console.developers.google.com/apis/dashboard
  • Create a new project, or use an existing project if you already have one set up.
  • Then click on Credentials -> Create Credentials -> OAuth Client ID

Create OAuth Client ID

Here's what I put in for this demo:

Name: google-auth-demo

Authorized Javascript Origins:

  • http://localhost:8000
  • http://localhost

Authorized Redirect URIs: You can ignore for the purpose of this post.

⚠️ If you have a plain HTML file that you load in your browser using a file path like /home/dperez/index.html, this won't work.

You'll need to "serve" your website so that you have a URL, even if its just localhost. You can use python -m http.server 8000 to serve up your current directory or you can use an npm package like http-server.

Add Authorized JavaScript Origins

⚠️ You must explicitly add http://localhost to your Authorized Javascript Origins, IN ADDITION TO the URL that you're actually using.

So I need to add BOTH http://localhost and http://localhost:8000

You'll then get a Client ID & Client Secret. These are 2 identifiers for your Oauth Client. At this point, you have authorized that Client ID to accept logins and return you the users information. You'll only need the Client ID for the rest of this walkthrough.

Get Client ID and Secret

Theres a warning that says as of June 2025, you can no longer view your client secret after you leave this page. For the purpose of this walkthrough, we don't need it. If you wind up needing it later, you can click on Add Secret to create a new one. However, I'm still able to get to it in the screenshot.

At this point, you have a Client ID available and you can proceed!

Add the Google Sign-In Button

Theres 2 main methods for setting it up, plain HTML and a JS implementation. I'll walk through both, whether one works better than the other depends on what you're trying to do with it.

Plain HTML

The Plain HTML method requires setting up 2 HTML elements that will initialize and dynamically replace itself with the google sign in button.

You can start with an empty index.html file with the contents below

<!DOCTYPE html>
<html>
<body>
  <h1>Welcome to the Demo</h1>
</body>
</html>

Add the library + credentials + button to your HTML

In your index.html page, add the following to your HTML page:

...
<head>
  ...
  <script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
  <h1>Welcome to the Demo</h1>
  <div id="g_id_onload"
      data-client_id="YOUR_CLIENT_ID.apps.googleusercontent.com"
      data-auto_prompt="false">
  </div>
  <div class="g_id_signin"
        data-type="standard"
        data-size="large"
        data-theme="outline"
        data-text="sign_in_with"
        data-shape="rectangular"
        data-logo_alignment="left">
</body>

The script tag grabs the GSI library from Google using async defer which means that the rest of your application can load while this library is downloaded to your browser and will not prevent the page from loading.

Once that library is loaded, it will automatically search for 2 elements with ID g_id_onload and g_id_signin.

The g_id_onload div element is where you will add the Client ID that you previously generated in the console. There are other options that you can set, more described here

The g_id_signin div element lets you define additional attributes to control the look and feel of the button. It also lets you "connect" your button to any of your Javascript code. A listing of all the available attributes to define are described here

At this point, if you refresh your page, you should see a pretty Sign-In button. If you click it, you'll go through your Google Login flow in a popup.

Now, lets add a callback - this is how you can tap into the sign-in flow and get info about the user.

<div id="g_id_onload"
      data-client_id="YOUR_CLIENT_ID.apps.googleusercontent.com"
      data-callback="handleCredentialResponse"
      data-auto_prompt="false">
...
<script>
  function handleCredentialResponse(response) {
    console.log("Encoded JWT ID token: " + response.credential);
  }
</script>

🎉 That works. I am able to see the token in the console

Troubleshooting:

  • Loading the page, I get through a successful sign-in and I see the log statement with the token information. However, I don't see the button change appearance.

I came across this Stack Overflow issue - https://stackoverflow.com/questions/68438293/the-given-origin-is-not-allowed-for-the-given-client-id-gsi which states that in the Google Client ID settings, you need to

  • include both http://localhost and http://localhost:8000.
  • I was using http://127.0.0.1:8000 so the origin didn't match.
  • Once I accessed the site using http://localhost:8000 instead, it saw that I was already signed in. And updated the state of the button. Win!

HTML + JS Method

We'll try the Javascript implementation now. In this method, you add the HTML element that you want to be replaced, and then you initialize it using javascript.

<body>
  <h1>Welcome to the Demo</h1>
  <div id="gsi_button"></div>
  <script>
    function handleCredentialResponse(response) {
        console.log("Encoded JWT ID token: " + response.credential);
      }
      window.onload = function () {
        google.accounts.id.initialize({
          client_id: "YOUR_CLIENT_ID.apps.googleusercontent.com",
          callback: handleCredentialResponse
        });
        google.accounts.id.renderButton(
          document.getElementById("gsi_button"),
          { theme: "outline", size: "large" }  // customization attributes
        );
      }
  </script>
</body>

When the window loads, we'll run google.accounts.id.initialize() with the Client ID we created earlier. We'll also run renderButton() which will replace #gsi_button with the official google one.

Show some user data

In the 2 examples above, we defined a callback called handleCredentialResponse, this allows us to get the details about the user after signing in.

function handleCredentialResponse(response) {
  console.log("Encoded JWT ID token: " + response.credential);
}

An ID token is usually a string of some kind usually containing json that is encoded using base64 to make it easy to share around as a string. If we add jwt_decode to the project, we can easily read the info in the token

For the purposes of the demo, I'm pulling it in from unpkg

<head>
  <script src="https://unpkg.com/jwt-decode@3.1.2/build/jwt-decode.js" async defer></script>
  ...
</head>
const decoded = jwt_decode(response.credential);
console.log("Decoded payload:", decoded);

This shows the result

{
    "iss": "https://accounts.google.com",
    "azp": "1006632388683-oe9a58l64grlf5cdecdjjna9rvu7vliv.apps.googleusercontent.com",
    "aud": "1006632388683-oe9a58l64grlf5cdecdjjna9rvu7vliv.apps.googleusercontent.com",
    "sub": "108716981921401766503",
    "email": "user@example.com",
    "email_verified": true,
    "nbf": 1757556093,
    "name": "danny perez",
    "picture": "https://lh3.googleusercontent.com/a/ACg8ocKXJnZr6LWNfdACAQGFvVVNSaWYS4_Ghq7Ap-JM-7O-dzLkhNqQ=s96-c",
    "given_name": "danny",
    "family_name": "perez",
    "iat": 1757556393,
    "exp": 1757559993,
    "jti": "6cb3411828a7c3bd2fa21816ba94a6f385419e1d"
}

Aha! There is the link to my picture that I can display on my page now!

// Display user information
const userInfo = document.getElementById('user-info');
const userAvatar = document.getElementById('user-avatar');
const userName = document.getElementById('user-name');

userAvatar.src = decoded.picture;
userName.textContent = decoded.name;
userInfo.classList.add('visible');

// Hide the sign-in button
document.getElementById('gsi_button').style.display = 'none';

Sign out

With the Google Sign-In button, theres no such thing as "Sign out".

If you want to sign out of your Google account, you would do that from a Google site and that will log you out of all your browser sessions.

We can then make our sign-out button just update the state on the page to show the button again, allowing you to switch accounts.

Add a button after the user has signed in by adding a button in your index.html and the click handler in your javascript.

<body>
...
  <button onclick="signOut()">Sign out</button>
</body>
function signOut() {
  // This prevents a redirect loop if you have
  // auto-select enabled
  google.accounts.id.disableAutoSelect();

  // Hide user info
  const userInfo = document.getElementById('user-info');
  userInfo.classList.remove('visible');

  // Show the sign-in button again
  document.getElementById('gsi_button').style.display = 'flex';

  // Clear user data
  document.getElementById('user-avatar').src = '';
  document.getElementById('user-name').textContent = '';

  console.log('User signed out');
}

At this point, we've added a Sign-In with Google button and we can identify the user by name/email. Since there's no sign-in, we just reset the state of the page.

...but what about basic things like saving users data to the backend? or showing an admin page?! Why isn't this in the docs?! That'll be covered on the next post!