Skip to content

Build a Custom Button

Google’s button is drawn in an iframe, so its props are the only way to change it. For a button in your own design, use one of Google’s OAuth2 flows behind your own <button>:

  • useTokenClient, the token model: Google gives the browser an access token.
  • useCodeClient, the code model: Google gives the browser a code, which your server exchanges for tokens.

Neither gives the browser an ID token, so finding out who signed in takes one more step on your server, described below. If all you need is sign-in, Google’s button is less work.

Both composables return isReady and login:

  • isReady is a ref that turns true once Google’s script has loaded and the client is ready. Disable your button until then.
  • login() opens Google’s popup. Call it from a click, or the browser blocks the popup.

The example below also disables the button while the popup is open. If the user closes the popup, or the browser blocks it, onNonOAuthError re-enables it.

<script setup lang="ts">
import { ref } from "vue";
import {
useTokenClient,
type TokenClientSuccessResponse,
type TokenClientErrorResponse,
} from "vue3-google-signin";
const pending = ref(false);
const handleOnSuccess = (response: TokenClientSuccessResponse) => {
pending.value = false;
console.log("Access Token: ", response.access_token);
};
const handleOnError = (errorResponse: TokenClientErrorResponse) => {
pending.value = false;
console.log("Error: ", errorResponse);
};
const { isReady, login } = useTokenClient({
onSuccess: handleOnSuccess,
onError: handleOnError,
// The user closed the popup, or it was blocked
onNonOAuthError: () => (pending.value = false),
// other options
});
const signIn = () => {
pending.value = true;
login();
};
</script>
<template>
<button :disabled="!isReady || pending" @click="signIn">
Sign in with Google
</button>
</template>

The access token is ready to call Google APIs from the browser: Call Google APIs shows how to ask for scopes, check what the user granted and handle expiry.

Usage is the same. onSuccess receives a code, which you post to your server with an X-Requested-With header:

<script setup lang="ts">
import {
useCodeClient,
type CodeClientSuccessResponse,
type CodeClientErrorResponse,
} from "vue3-google-signin";
const handleOnSuccess = async (response: CodeClientSuccessResponse) => {
// Send the code to your server, which exchanges it for tokens
await fetch("/api/google/connect", {
method: "POST",
headers: {
"Content-Type": "application/json",
// Google asks your server to check this header in popup mode
"X-Requested-With": "XmlHttpRequest",
},
body: JSON.stringify({ code: response.code }),
});
};
const handleOnError = (errorResponse: CodeClientErrorResponse) => {
console.log("Error: ", errorResponse);
};
const { isReady, login } = useCodeClient({
onSuccess: handleOnSuccess,
onError: handleOnError,
// other options
});
</script>
<template>
<button :disabled="!isReady" @click="() => login()">
Sign in with Google
</button>
</template>

Your server exchanges the code for an access token and a refresh token with your client secret. Call Google APIs has the server code for Express and for a Nuxt server route.

  • With a code, the exchange on your server also returns tokens.id_token, because openid is always requested. Check it with verifyIdToken as in Verify users on your server.
  • With an access token, send it to your server, which asks Google about it. getTokenInfo from google-auth-library returns the user’s sub and email, and aud, the client ID the token was issued for. Check that aud is your client ID, or a token issued to another app would sign someone in:
import { OAuth2Client } from "google-auth-library";
const google = new OAuth2Client();
const info = await google.getTokenInfo(accessToken);
if (info.aud !== process.env.GOOGLE_CLIENT_ID) throw new Error("Wrong client");
// info.sub is the user's Google account ID, info.email their email address

Both composables pass Google’s options through:

  • login_hint: the user’s email address or sub, to skip the account chooser for a user you already know.
  • hd: limit the chooser to a Google Workspace domain.
  • select_account: true (code client) or login({ prompt: "select_account" }) (token client): always show the account chooser, for “Use another account”.