Scripts

Google Sign-In

Google Sign-In provides a secure and convenient way for users to sign in to your app using their Google Account with One Tap, personalized buttons, and automatic sign-in.

Nuxt Scripts provides a registry script composable useScriptGoogleSignIn() to easily integrate Google Sign-In in your Nuxt app with optimal performance.

Google Sign-In

View source

Nuxt Config Setup

Add this to your nuxt.config.ts to load Google Sign-In globally. Alternatively you can use the useScriptGoogleSignIn composable for more control.

export default defineNuxtConfig({
  scripts: {
    registry: {
      googleSignIn: {
        clientId: '123456789.apps.googleusercontent.com',
        trigger: 'onNuxtReady',
      }
    }
  }
})

useScriptGoogleSignIn()

The useScriptGoogleSignIn composable lets you have fine-grain control over when and how Google Sign-In is loaded on your site.

const { proxy } = useScriptGoogleSignIn()

proxy.accounts.id.prompt()

Please follow the Registry Scripts guide to learn more about advanced usage.

Live Demo

Live DemoawaitingLoad

Sign in with your Google account:

Or

Composable API

useScriptGoogleSignIn() returns the standard script context (status, proxy, onLoaded, …) plus three helpers that wrap the most common flows. Schema options passed to the composable are merged into every call so you don't have to repeat clientId, loginUri, uxMode etc.

const { initialize, renderButton, prompt, status, onLoaded, proxy } = useScriptGoogleSignIn({
  clientId: 'YOUR_CLIENT_ID',
  context: 'signin',
  useFedcmForPrompt: true,
})

initialize(config?)

Calls google.accounts.id.initialize() with schema options merged with config. Internally guarded so multiple calls (e.g. across navigations and component remounts) are safe; Google's API logs an error if initialize() runs again after a button has been rendered, so the helper only forwards the first call.

initialize({
  callback: (response) => {
    // verify response.credential server-side
  }
})

renderButton(parent, config?)

Renders the personalized button. Auto-initializes if needed and is safe to re-render on locale change or navigation.

<script setup lang="ts">
const { initialize, renderButton } = useScriptGoogleSignIn()
const buttonRef = useTemplateRef<HTMLDivElement>('buttonRef')

initialize({ callback: handleCredential })

watch(buttonRef, (el) => {
  if (el)
    renderButton(el, { text: 'continue_with', use_fedcm: true })
}, { immediate: true })
</script>

<template>
  <div ref="buttonRef" />
</template>

prompt(listener?)

Shows the One Tap prompt. Auto-initializes if needed.

prompt((notification) => {
  if (notification.isNotDisplayed())
    console.log('Not shown:', notification.getNotDisplayedReason())
})

Switching locales

The button locale is a renderButton option, not an initialize one. To change the language, clear the container and re-render:

watch([locale, buttonRef], ([newLocale, el]) => {
  if (!el)
    return
  el.innerHTML = ''
  renderButton(el, { locale: newLocale, use_fedcm: true })
}, { immediate: true })
Google may still fall back to the user's Google account language regardless of this option. This is a Google-side behavior and not configurable.

Redirect UX mode

With uxMode: 'redirect', Google POSTs the credential to your loginUri server endpoint as application/x-www-form-urlencoded (fields: credential, g_csrf_token, select_by, …). The credential does not appear as a URL fragment after the redirect; it travels in the POST body, which your server then handles before redirecting the browser.

If you need the credential client-side (e.g. SPA with a separate API), use uxMode: 'popup' with a callback instead.

const { initialize, renderButton } = useScriptGoogleSignIn({
  uxMode: 'redirect',
  loginUri: 'https://your-server.com/auth/google',
})

initialize() // no callback needed in redirect mode

Moment Notifications

Track the One Tap display state:

const { onLoaded } = useScriptGoogleSignIn()

onLoaded(({ accounts }) => {
  accounts.id.prompt((notification) => {
    if (notification.isDisplayMoment()) {
      if (notification.isDisplayed()) {
        console.log('One Tap displayed')
      }
      else {
        console.log('Not displayed:', notification.getNotDisplayedReason())
      }
    }

    if (notification.isSkippedMoment()) {
      console.log('Skipped:', notification.getSkippedReason())
    }

    if (notification.isDismissedMoment()) {
      console.log('Dismissed:', notification.getDismissedReason())
    }
  })
})

Server-Side Verification

Always verify the credential token on your server:

server/api/auth/google.post.ts
export default defineEventHandler(async (event) => {
  const { credential } = await readBody(event)

  // Verify the token with Google
  const response = await $fetch(`https://oauth2.googleapis.com/tokeninfo`, {
    params: { id_token: credential }
  })

  // Verify the client ID matches
  if (response.aud !== 'YOUR_CLIENT_ID') {
    throw createError({ statusCode: 401, message: 'Invalid token' })
  }

  // Create session with user info
  const user = {
    email: response.email,
    name: response.name,
    picture: response.picture,
    sub: response.sub
  }

  return { user }
})

Cross-Origin-Opener-Policy

In popup UX mode (the default), Google opens a popup that delivers the credential back to your page via window.postMessage. If your page sends a strict Cross-Origin-Opener-Policy header, the browser will block that message and the sign-in will appear to do nothing: the popup closes but no callback fires.

If you set COOP at all, use same-origin-allow-popups:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/login/**': {
      headers: { 'Cross-Origin-Opener-Policy': 'same-origin-allow-popups' },
    },
  },
})

Pages without an explicit COOP header work by default. Redirect mode (uxMode: 'redirect') and FedCM-only flows are unaffected.

FedCM API Support

Enable Privacy Sandbox FedCM API support for enhanced privacy. FedCM adoption becomes mandatory in August 2025.

export default defineNuxtConfig({
  scripts: {
    registry: {
      googleSignIn: {
        clientId: 'YOUR_CLIENT_ID',
        useFedcmForPrompt: true
      }
    }
  }
})

Cross-Origin Iframes

When using One Tap or the Sign-In button in cross-origin iframes with FedCM, add the allow attribute to all parent iframes:

<iframe src="https://your-app.com/login" allow="identity-credentials-get"></iframe>
With FedCM enabled, customizing the One Tap prompt position via prompt_parent_id is not supported.

Revoking Access

Allow users to revoke access to their Google Account:

const { onLoaded } = useScriptGoogleSignIn()

function revokeAccess(userId: string) {
  onLoaded(({ accounts }) => {
    accounts.id.revoke(userId, (response) => {
      if (response.successful) {
        console.log('Access revoked')
      }
      else {
        console.error('Revocation failed:', response.error)
      }
    })
  })
}

Best Practices

Logout Handling

Always call disableAutoSelect() when the user signs out to prevent automatic re-authentication:

function signOut() {
  // Clear your app's session
  user.value = null

  // Prevent One Tap from auto-selecting this account
  onLoaded(({ accounts }) => {
    accounts.id.disableAutoSelect()
  })
}

Hosted Domain Restriction

Restrict sign-in to a specific Google Workspace domain:

accounts.id.initialize({
  client_id: 'YOUR_CLIENT_ID',
  callback: handleCredentialResponse,
  hd: 'your-company.com' // Only allow users from this domain
})

Local Development Setup

To test Google Sign-In locally:

  1. Go to Google Cloud Console → Credentials
  2. Create or select an OAuth 2.0 Client ID (Web application type)
  3. Under Authorized JavaScript origins, add:
    • http://localhost
    • http://localhost:3000 (or your dev server port)
  4. Save and copy your Client ID
Google requires http://localhost (not 127.0.0.1) for local development. You don't need a redirect URI when using popup mode.

Then configure your environment:

.env
NUXT_PUBLIC_SCRIPTS_GOOGLE_SIGN_IN_CLIENT_ID=your-client-id.apps.googleusercontent.com

Guides

For more detailed info on how to obtain a Google Client ID and configure your OAuth consent screen, see the official Google Identity Services documentation.
clientIdstring required

Your Google API client ID.

autoSelectboolean

Auto-select credentials when only one Google account is available.

context'signin' | 'signup' | 'use'

The context text for the One Tap prompt.

useFedcmForPromptboolean

Enable FedCM (Federated Credential Management) API support. Mandatory from August 2025.

cancelOnTapOutsideboolean = true

Cancel the One Tap prompt if the user clicks outside.

uxMode'popup' | 'redirect'

The UX mode for the sign-in flow.

loginUristring

The URI to redirect to after sign-in when using redirect UX mode.

itpSupportboolean

Enable Intelligent Tracking Prevention (ITP) support for Safari.

allowedParentOriginstring | string[]

Allowed parent origin(s) for iframe embedding.

hdstring

Restrict sign-in to a specific Google Workspace hosted domain.

Example

One Tap Sign-In

The One Tap prompt provides a simplified sign-in experience:

<script setup lang="ts">
const { initialize, prompt } = useScriptGoogleSignIn({
  context: 'signin',
  useFedcmForPrompt: true,
})

async function handleCredentialResponse(response: CredentialResponse) {
  await $fetch('/api/auth/google', {
    method: 'POST',
    body: { credential: response.credential }
  })
}

initialize({ callback: handleCredentialResponse })
onMounted(() => prompt())
</script>

Personalized Button

Render Google's personalized Sign in with Google button:

<script setup lang="ts">
const { initialize, renderButton } = useScriptGoogleSignIn()
const buttonRef = useTemplateRef<HTMLDivElement>('buttonRef')

function handleCredentialResponse(response: CredentialResponse) {
  console.log('Signed in!', response.credential)
}

initialize({ callback: handleCredentialResponse })

watch(buttonRef, (el) => {
  if (el) {
    renderButton(el, {
      type: 'standard',
      theme: 'outline',
      size: 'large',
      text: 'signin_with',
      shape: 'rectangular',
      logo_alignment: 'left',
    })
  }
}, { immediate: true })
</script>

<template>
  <div ref="buttonRef" />
</template>