---
title: "Google OAuth Authentication"
---

:::info
The complete source of the following example plugin can be found here: [example-plugins/google-auth-plugin](https://github.com/vendurehq/examples/tree/publish/examples/shop-google-auth)
:::

**Google OAuth authentication** allows customers to sign in using their Google accounts, providing a seamless experience that eliminates the need for password-based registration.

This is particularly valuable for **consumer-facing stores** where users prefer the convenience and security of Google's authentication system, or for **B2B platforms** where organizations use Google Workspace.

This guide shows you how to **add Google OAuth support** to your Vendure store using a custom [AuthenticationStrategy](/reference/typescript-api/auth/authentication-strategy/) and Google Identity Services.

An **AuthenticationStrategy** in Vendure defines how users can log in to your store. Learn more about [authentication in Vendure](/guides/core-concepts/auth/).

## Creating the Plugin

**First, use the Vendure CLI** to create a new plugin for Google authentication:

```bash
npx vendure add -p GoogleAuthPlugin
```

This creates a basic [plugin](/guides/developer-guide/plugins/) structure with the necessary files.

## Installing Dependencies

**Google authentication requires** the Google Auth Library for token verification:

```bash
npm install google-auth-library
```

This library handles ID token verification securely on the server side, ensuring the tokens received from Google are authentic.

## Creating the Authentication Strategy

**Now create the Google authentication strategy.** Unlike traditional OAuth flows that use authorization codes, Google Identity Services provides **ID tokens directly**, which we verify server-side:

```ts title="src/plugins/google-auth-plugin/google-auth-strategy.ts"
import {
  AuthenticationStrategy,
  ExternalAuthenticationService,
  Injector,
  Logger,
  RequestContext,
  User,
} from '@vendure/core';
import { OAuth2Client } from 'google-auth-library';
import { DocumentNode } from 'graphql';
import { gql } from 'graphql-tag';

export type GoogleAuthData = {
  token: string;
}

export interface GoogleAuthOptions {
  googleClientId: string;
  onUserCreated?: (ctx: RequestContext, injector: Injector, user: User) => void;
  onUserFound?: (ctx: RequestContext, injector: Injector, user: User) => void;
}

export class GoogleAuthStrategy implements AuthenticationStrategy<GoogleAuthData> {
  readonly name = 'google';
  private client: OAuth2Client;
  private externalAuthenticationService: ExternalAuthenticationService;
  private logger: Logger;
  private injector: Injector;

  constructor(private options: GoogleAuthOptions) {
    // Initialize Google OAuth2Client for token verification
    this.client = new OAuth2Client(options.googleClientId);
    this.logger = new Logger();
  }

  init(injector: Injector) {
    // Get services we'll use for customer management
    this.externalAuthenticationService = injector.get(ExternalAuthenticationService);
    this.injector = injector;
  }

  defineInputType(): DocumentNode {
    // Define the GraphQL input type for the authenticate mutation
    return gql`
      input GoogleAuthInput {
        token: String!
      }
    `;
  }

  async authenticate(ctx: RequestContext, data: GoogleAuthData): Promise<User | false> {
    try {
      // Step 1: Verify the Google ID token
      const ticket = await this.client.verifyIdToken({
        idToken: data.token,
        audience: this.options.googleClientId,
      });

      const payload = ticket.getPayload();

      if (!payload || !payload.email) {
        this.logger.error('Invalid Google token or missing email', 'GoogleAuthStrategy');
        return false;
      }

      // Step 2: Check if this Google user already has a Vendure account
      const existingUser = await this.externalAuthenticationService.findCustomerUser(
        ctx,
        this.name,
        payload.sub, // Google's unique user ID
      );

      if (existingUser) {
        // User exists, log them in
        this.logger.verbose(`User found: ${existingUser.identifier}`, 'GoogleAuthStrategy');
        this.options.onUserFound?.(ctx, this.injector, existingUser);
        return existingUser;
      }

      // Step 3: Create a new customer account for first-time Google users
      const createdUser = await this.externalAuthenticationService.createCustomerAndUser(ctx, {
        strategy: this.name,
        externalIdentifier: payload.sub, // Store Google user ID
        verified: payload.email_verified || false, // Use Google's verification status
        emailAddress: payload.email,
        firstName: payload.given_name || 'Google',
        lastName: payload.family_name || 'User',
      });

      this.options.onUserCreated?.(ctx, this.injector, createdUser);

      return createdUser;
    } catch (error) {
      this.logger.error(`Google authentication failed: ${error.message}`, 'GoogleAuthStrategy');
      return false;
    }
  }
}
```

The strategy uses Google's [OAuth2Client](https://googleapis.dev/nodejs/google-auth-library/latest/classes/OAuth2Client.html) to verify ID tokens and Vendure's [ExternalAuthenticationService](/reference/typescript-api/auth/external-authentication-service/) to handle customer creation.

Key differences from other OAuth flows:
- **ID Token Verification**: Google provides signed JWT tokens that we verify directly
- **No Code Exchange**: Unlike GitHub OAuth, there's no authorization code to exchange
- **Email Verification**: We respect Google's email verification status
- **Fallback Names**: Provides defaults if Google profile lacks name information

## Registering the Strategy

**Now update the generated plugin file** to register your authentication strategy:

```ts title="src/plugins/google-auth-plugin/google-auth-plugin.plugin.ts"
import { PluginCommonModule, VendurePlugin } from '@vendure/core';

import { GoogleAuthStrategy } from './google-auth-strategy';

export interface GoogleAuthPluginOptions {
  googleClientId: string;
}

@VendurePlugin({
  imports: [PluginCommonModule],
  configuration: (config) => {
    const options = GoogleAuthPlugin.options;

    if (options?.googleClientId) {
      config.authOptions.shopAuthenticationStrategy.push(
        new GoogleAuthStrategy({ googleClientId: options.googleClientId })
      );
    }
    return config;
  },
})
export class GoogleAuthPlugin {
  static options: GoogleAuthPluginOptions;

  static init(options: GoogleAuthPluginOptions) {
    this.options = options;
    return GoogleAuthPlugin;
  }
}
```

## Adding to Vendure Config

**Add the plugin** to your Vendure configuration:

```ts title="src/vendure-config.ts"
import { VendureConfig } from '@vendure/core';
import { GoogleAuthPlugin } from './plugins/google-auth-plugin/google-auth-plugin.plugin';

export const config: VendureConfig = {
  // ... other config
  plugins: [
    // ... other plugins
    GoogleAuthPlugin.init({
      googleClientId: process.env.GOOGLE_CLIENT_ID!,
    }),
  ],
  // ... rest of config
};
```

## Setting up Google OAuth App

**Before you can test the integration,** you need to create a Google OAuth 2.0 Client:

1. **Go to** the [Google Cloud Console](https://console.cloud.google.com/)
2. **Create a new project** or select an existing one
3. **Navigate to** **APIs & Services → Credentials**
4. **Click** **"Create Credentials" → "OAuth 2.0 Client ID"**
5. **Select** **"Web application"** as the application type
6. **Configure the client:**
   - **Name**: Your app name (e.g., "My Vendure Store")
   - **Authorized JavaScript origins**: `http://localhost:3001`
   - **Authorized redirect URIs**: `http://localhost:3001/sign-in`

:::note
The **localhost URLs** shown here are for **local development only.** In production, replace `localhost:3001` with your actual domain (e.g., `https://mystore.com`).
:::

7. **Click "Create"** and copy the Client ID

**Add the client ID** to your environment:

```bash title=".env"
GOOGLE_CLIENT_ID=your_google_client_id.apps.googleusercontent.com
```

## Frontend Integration

### Creating the Sign-in Component

**For the frontend,** we'll use Google's official Identity Services library, which provides a **secure and user-friendly sign-in experience:**

```typescript title="components/GoogleSignInButton.tsx"
'use client';

import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';

const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;

declare global {
  interface Window {
    google: any;
    handleCredentialResponse?: (response: any) => void;
  }
}

export function GoogleSignInButton() {
  const router = useRouter();
  const [pending, setPending] = useState(false);

  useEffect(() => {
    // Define the callback function globally
    window.handleCredentialResponse = async (response: any) => {
      setPending(true);
      try {
        const result = await authenticateWithGoogle(response.credential);

        if (result?.success) {
          router.replace('/account');
        } else {
          console.error('Authentication failed:', result?.message);
        }
      } catch (error) {
        console.error('Google authentication error:', error);
      } finally {
        setPending(false);
      }
    };

    // Load Google Identity Services
    if (!window.google && GOOGLE_CLIENT_ID) {
      const script = document.createElement('script');
      script.src = 'https://accounts.google.com/gsi/client';
      script.async = true;
      script.onload = () => {
        window.google.accounts.id.initialize({
          client_id: GOOGLE_CLIENT_ID,
          callback: window.handleCredentialResponse,
        });
      };
      document.head.appendChild(script);
    }

    return () => {
      delete window.handleCredentialResponse;
    };
  }, [router]);

  const handleGoogleSignIn = () => {
    if (!GOOGLE_CLIENT_ID) {
      console.error('Google Client ID not configured');
      return;
    }

    if (window.google) {
      window.google.accounts.id.prompt();
    } else {
      console.error('Google SDK not loaded');
    }
  };

  return (
    <button
      onClick={handleGoogleSignIn}
      disabled={pending}
      className="flex items-center justify-center w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
    >
      {pending ? (
        'Authenticating...'
      ) : (
        <>
          <svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
            <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
            <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
            <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
            <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
          </svg>
          Continue with Google
        </>
      )}
    </button>
  );
}
```

### Creating the Authentication Function

**Create a server action** to handle the Google authentication:

```typescript title="actions/auth.ts"
'use server';

import { gql } from 'graphql-request';

const AUTHENTICATE_MUTATION = gql`
  mutation AuthenticateWithGoogle($input: AuthenticationInput!) {
    authenticate(input: $input) {
      ... on CurrentUser {
        id
        identifier
        channels {
          code
          token
          permissions
        }
      }
      ... on InvalidCredentialsError {
        authenticationError
        errorCode
        message
      }
      ... on NotVerifiedError {
        errorCode
        message
      }
    }
  }
`;

export async function authenticateWithGoogle(token: string) {
  try {
    const result = await vendureClient.request(AUTHENTICATE_MUTATION, {
      input: {
        google: {
          token
        }
      }
    });

    if (result.authenticate.__typename === 'CurrentUser') {
      // Authentication successful
      return { success: true, user: result.authenticate };
    } else {
      // Handle authentication error
      return {
        success: false,
        message: result.authenticate.message
      };
    }
  } catch (error) {
    console.error('Google authentication error:', error);
    return {
      success: false,
      message: 'Authentication failed'
    };
  }
}
```

**Add your Google Client ID** to the frontend environment:

```bash title=".env.local"
NEXT_PUBLIC_GOOGLE_CLIENT_ID=your_google_client_id.apps.googleusercontent.com
VENDURE_API_ENDPOINT=http://localhost:3000/shop-api
```

**The Google Identity Services flow works as follows:**

1. **User clicks "Continue with Google"** → Google popup appears
2. **User signs in with Google** → Google returns an ID token
3. **Frontend sends the token to Vendure** → Vendure verifies token with Google
4. **If valid, Vendure creates/finds customer** → User is logged in

## Using the GraphQL API

**Once your plugin is running,** Google authentication will be **available in your shop API:**

<Tabs>
<TabItem value="Mutation" label="Mutation" default>

```graphql
mutation AuthenticateWithGoogle {
  authenticate(input: {
    google: {
      token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  }) {
    ... on CurrentUser {
      id
      identifier
      channels {
        code
        token
        permissions
      }
    }
    ... on InvalidCredentialsError {
      authenticationError
      errorCode
      message
    }
    ... on NotVerifiedError {
      errorCode
      message
    }
  }
}
```

</TabItem>
<TabItem value="Response" label="Response">

```json
{
  "data": {
    "authenticate": {
      "id": "1",
      "identifier": "user@gmail.com",
      "channels": [
        {
          "code": "__default_channel__",
          "token": "session_token_here",
          "permissions": ["Authenticated"]
        }
      ]
    }
  }
}
```

</TabItem>
</Tabs>

## Customer Data Management

**Google-authenticated customers** are managed like any other Vendure [Customer](/reference/typescript-api/entities/customer/):

- **Email**: Uses the user's actual Google email address
- **Verification**: Inherits Google's email verification status
- **External ID**: Google's unique user ID (`sub` claim) for future authentication
- **Profile**: First and last names from Google profile, with fallbacks
- **Security**: No password stored - authentication handled entirely by Google

This means **Google users work seamlessly** with Vendure's [order management](/guides/core-concepts/orders/), [promotions](/guides/core-concepts/promotions/), and all customer workflows.

## Testing the Integration

**To test your Google OAuth integration:**

1. **Start your Vendure server** with the plugin configured
2. **Navigate to your storefront** and click "Continue with Google"
3. **Complete the Google OAuth flow** when prompted
4. **Verify customer creation** in the Vendure Dashboard
5. **Test repeat logins** to ensure existing customers are found correctly
