| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- ---
- title: "GitHub OAuth Authentication"
- ---
- import Tabs from '@theme/Tabs';
- import TabItem from '@theme/TabItem';
- :::info
- The complete source of the following example plugin can be found here: [example-plugins/github-auth-plugin](https://github.com/vendurehq/examples/tree/publish/examples/shop-github-auth)
- :::
- GitHub OAuth authentication allows customers to sign in using their GitHub accounts, eliminating the need for password-based registration.
- This is particularly valuable for developer-focused stores or B2B marketplaces.
- This guide shows you how to add GitHub OAuth support to your Vendure store using a custom [AuthenticationStrategy](/reference/typescript-api/auth/authentication-strategy/).
- ## Creating the Plugin
- First, use the Vendure CLI to create a new plugin for GitHub authentication:
- ```bash
- npx vendure add -p GitHubAuthPlugin
- ```
- This creates a basic plugin structure with the necessary files.
- ## Creating the Authentication Strategy
- Now create the GitHub authentication strategy. This handles the OAuth flow and creates customer accounts using GitHub profile data:
- ```ts title="src/plugins/github-auth-plugin/github-auth-strategy.ts"
- import { AuthenticationStrategy, ExternalAuthenticationService, Injector, RequestContext, User } from '@vendure/core';
- import { DocumentNode } from 'graphql';
- import gql from 'graphql-tag';
- export interface GitHubAuthData {
- code: string;
- state: string;
- }
- export interface GitHubAuthOptions {
- clientId: string;
- clientSecret: string;
- }
- export class GitHubAuthenticationStrategy implements AuthenticationStrategy<GitHubAuthData> {
- readonly name = 'github';
- private externalAuthenticationService: ExternalAuthenticationService;
- constructor(private options: GitHubAuthOptions) {}
- init(injector: Injector) {
- // Get the service we'll use to create/find customer accounts
- this.externalAuthenticationService = injector.get(ExternalAuthenticationService);
- }
- defineInputType(): DocumentNode {
- // Define the GraphQL input type for the authenticate mutation
- return gql`
- input GitHubAuthInput {
- code: String!
- state: String!
- }
- `;
- }
- async authenticate(ctx: RequestContext, data: GitHubAuthData): Promise<User | false> {
- const { code, state } = data;
- // Step 1: Exchange the authorization code for an access token
- const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
- method: 'POST',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- client_id: this.options.clientId,
- client_secret: this.options.clientSecret,
- code,
- state,
- }),
- });
- const tokenData = await tokenResponse.json();
- if (tokenData.error) {
- throw new Error(`GitHub OAuth error: ${tokenData.error_description}`);
- }
- // Step 2: Use the access token to get user info from GitHub
- const userResponse = await fetch('https://api.github.com/user', {
- headers: {
- 'Authorization': `Bearer ${tokenData.access_token}`,
- 'Accept': 'application/vnd.github.v3+json',
- },
- });
- const user = await userResponse.json();
- if (!user.login) {
- throw new Error('Unable to retrieve user information from GitHub');
- }
- // Step 3: Check if this GitHub user already has a Vendure account
- const existingCustomer = await this.externalAuthenticationService.findCustomerUser(
- ctx,
- this.name,
- user.login, // GitHub username as external identifier
- );
- if (existingCustomer) {
- // User exists, log them in
- return existingCustomer;
- }
- // Step 4: Create a new customer account for first-time GitHub users
- const newCustomer = await this.externalAuthenticationService.createCustomerAndUser(ctx, {
- strategy: this.name,
- externalIdentifier: user.login, // Store GitHub username
- verified: true, // GitHub accounts are pre-verified
- emailAddress: `${user.login}-github@vendure.io`, // Unique email to avoid conflicts
- firstName: user.name?.split(' ')[0] || user.login,
- lastName: user.name?.split(' ').slice(1).join(' ') || '',
- });
- return newCustomer;
- }
- }
- ```
- The strategy uses Vendure's [ExternalAuthenticationService](/reference/typescript-api/auth/external-authentication-service/) to handle customer creation.
- It generates a unique email address for each GitHub user to avoid conflicts, and stores the GitHub username as the external identifier for future logins.
- ## Registering the Strategy
- Now update the generated plugin file to register your authentication strategy:
- ```ts title="src/plugins/github-auth-plugin/github-auth-plugin.plugin.ts"
- import { PluginCommonModule, VendurePlugin } from '@vendure/core';
- import { GitHubAuthenticationStrategy, GitHubAuthOptions } from './github-auth-strategy';
- @VendurePlugin({
- imports: [PluginCommonModule],
- configuration: config => {
- config.authOptions.shopAuthenticationStrategy.push(new GitHubAuthenticationStrategy(GitHubAuthPlugin.options));
- return config;
- },
- })
- export class GitHubAuthPlugin {
- static options: GitHubAuthOptions;
- static init(options: GitHubAuthOptions) {
- this.options = options;
- return GitHubAuthPlugin;
- }
- }
- ```
- ## Adding to Vendure Config
- Add the plugin to your Vendure configuration:
- ```ts title="src/vendure-config.ts"
- import { VendureConfig } from '@vendure/core';
- import { GitHubAuthPlugin } from './plugins/github-auth-plugin/github-auth-plugin.plugin';
- export const config: VendureConfig = {
- // ... other config
- plugins: [
- // ... other plugins
- GitHubAuthPlugin.init({
- clientId: process.env.GITHUB_CLIENT_ID!,
- clientSecret: process.env.GITHUB_CLIENT_SECRET!,
- }),
- ],
- // ... rest of config
- };
- ```
- ## Setting up GitHub OAuth App
- Before you can test the integration, you need to create a GitHub OAuth App:
- 1. Go to [GitHub Settings → Developer settings → OAuth Apps](https://github.com/settings/developers)
- 2. Click "New OAuth App"
- 3. Fill in the required fields:
- - **Application name**: Your app name (e.g., "My Vendure Store")
- - **Homepage URL**: `http://localhost:3001` (your storefront URL)
- - **Authorization callback URL**: `http://localhost:3001/auth/github/callback`
- 4. Click "Register application"
- 5. Copy the Client ID and generate a Client Secret
- :::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`).
- :::
- Add these credentials to your environment:
- ```bash title=".env"
- GITHUB_CLIENT_ID=your_github_client_id
- GITHUB_CLIENT_SECRET=your_github_client_secret
- ```
- ## Frontend Integration
- ### Creating the Sign-in URL
- In your storefront, create a function to generate the GitHub authorization URL:
- ```typescript title="utils/github-auth.ts"
- export function createGitHubSignInUrl(): string {
- const clientId = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID;
- const redirectUri = encodeURIComponent('http://localhost:3001/auth/github/callback');
- const state = Math.random().toString(36).substring(2);
- // Store state for CSRF protection
- sessionStorage.setItem('github_oauth_state', state);
- return `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=read:user&state=${state}`;
- }
- ```
- ### Handling the Callback
- Create a callback handler to process the GitHub response and authenticate with Vendure:
- ```typescript title="pages/auth/github/callback.ts"
- import { gql } from 'graphql-request';
- const AUTHENTICATE_MUTATION = gql`
- mutation Authenticate($input: GitHubAuthInput!) {
- authenticate(input: { github: $input }) {
- ... on CurrentUser {
- id
- identifier
- channels {
- code
- token
- permissions
- }
- }
- ... on InvalidCredentialsError {
- authenticationError
- errorCode
- message
- }
- }
- }
- `;
- export async function handleGitHubCallback(code: string, state: string) {
- // Verify CSRF protection
- const storedState = sessionStorage.getItem('github_oauth_state');
- if (state !== storedState) {
- throw new Error('Invalid state parameter');
- }
- sessionStorage.removeItem('github_oauth_state');
- // Authenticate with Vendure
- const result = await vendureClient.request(AUTHENTICATE_MUTATION, {
- input: { code, state }
- });
- if (result.authenticate.__typename === 'CurrentUser') {
- // Authentication successful - redirect to account page
- return result.authenticate;
- } else {
- // Handle authentication error
- throw new Error(result.authenticate.message);
- }
- }
- ```
- The OAuth flow follows these steps:
- 1. User clicks "Sign in with GitHub" → redirected to GitHub
- 2. User authorizes your app → GitHub redirects back with code and state
- 3. Your callback exchanges the code for user data → creates Vendure session
- ## Using the GraphQL API
- Once your plugin is running, the GitHub authentication will be available in your shop API:
- <Tabs>
- <TabItem value="Mutation" label="Mutation" default>
- ```graphql
- mutation AuthenticateWithGitHub {
- authenticate(input: {
- github: {
- code: "authorization_code_from_github",
- state: "csrf_protection_state"
- }
- }) {
- ... on CurrentUser {
- id
- identifier
- channels {
- code
- token
- permissions
- }
- }
- ... on InvalidCredentialsError {
- authenticationError
- errorCode
- message
- }
- }
- }
- ```
- </TabItem>
- <TabItem value="Response" label="Response">
- ```json
- {
- "data": {
- "authenticate": {
- "id": "1",
- "identifier": "github-user-github@vendure.io",
- "channels": [
- {
- "code": "__default_channel__",
- "token": "session_token_here",
- "permissions": ["Authenticated"]
- }
- ]
- }
- }
- }
- ```
- </TabItem>
- </Tabs>
- ## Customer Data Management
- GitHub-authenticated customers are managed like any other Vendure [Customer](/reference/typescript-api/entities/customer/):
- - **Email**: Generated as `{username}-github@vendure.io` to avoid conflicts
- - **Verification**: Automatically verified (GitHub handles email verification)
- - **External ID**: GitHub username stored for future authentication
- - **Profile**: Name extracted from GitHub profile when available
- This means GitHub users work seamlessly with Vendure's [order management](/guides/core-concepts/orders/), [promotions](/guides/core-concepts/promotions/), and customer workflows.
- ## Testing the Integration
- To test your GitHub OAuth integration:
- 1. Start your Vendure server with the plugin configured
- 2. Navigate to your storefront and click the GitHub sign-in link
- 3. Authorize your GitHub app when prompted
- 4. Verify that a new customer is created in the Vendure Dashboard
- 5. Check that subsequent logins find the existing customer account
|