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.
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
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 })
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:
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:
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>
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:
- Go to Google Cloud Console → Credentials
- Create or select an OAuth 2.0 Client ID (Web application type)
- Under Authorized JavaScript origins, add:
http://localhosthttp://localhost:3000(or your dev server port)
- Save and copy your Client ID
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:
NUXT_PUBLIC_SCRIPTS_GOOGLE_SIGN_IN_CLIENT_ID=your-client-id.apps.googleusercontent.com
Guides
clientIdstring required Your Google API client ID.
autoSelectbooleanAuto-select credentials when only one Google account is available.
context'signin' | 'signup' | 'use'The context text for the One Tap prompt.
useFedcmForPromptbooleanEnable FedCM (Federated Credential Management) API support. Mandatory from August 2025.
cancelOnTapOutsideboolean = trueCancel the One Tap prompt if the user clicks outside.
uxMode'popup' | 'redirect'The UX mode for the sign-in flow.
loginUristringThe URI to redirect to after sign-in when using redirect UX mode.
itpSupportbooleanEnable Intelligent Tracking Prevention (ITP) support for Safari.
allowedParentOriginstring | string[]Allowed parent origin(s) for iframe embedding.
hdstringRestrict 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>