1
0

index.mdx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. ---
  2. title: "GitHub OAuth Authentication"
  3. ---
  4. import Tabs from '@theme/Tabs';
  5. import TabItem from '@theme/TabItem';
  6. :::info
  7. 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)
  8. :::
  9. GitHub OAuth authentication allows customers to sign in using their GitHub accounts, eliminating the need for password-based registration.
  10. This is particularly valuable for developer-focused stores or B2B marketplaces.
  11. This guide shows you how to add GitHub OAuth support to your Vendure store using a custom [AuthenticationStrategy](/reference/typescript-api/auth/authentication-strategy/).
  12. ## Creating the Plugin
  13. First, use the Vendure CLI to create a new plugin for GitHub authentication:
  14. ```bash
  15. npx vendure add -p GitHubAuthPlugin
  16. ```
  17. This creates a basic plugin structure with the necessary files.
  18. ## Creating the Authentication Strategy
  19. Now create the GitHub authentication strategy. This handles the OAuth flow and creates customer accounts using GitHub profile data:
  20. ```ts title="src/plugins/github-auth-plugin/github-auth-strategy.ts"
  21. import { AuthenticationStrategy, ExternalAuthenticationService, Injector, RequestContext, User } from '@vendure/core';
  22. import { DocumentNode } from 'graphql';
  23. import gql from 'graphql-tag';
  24. export interface GitHubAuthData {
  25. code: string;
  26. state: string;
  27. }
  28. export interface GitHubAuthOptions {
  29. clientId: string;
  30. clientSecret: string;
  31. }
  32. export class GitHubAuthenticationStrategy implements AuthenticationStrategy<GitHubAuthData> {
  33. readonly name = 'github';
  34. private externalAuthenticationService: ExternalAuthenticationService;
  35. constructor(private options: GitHubAuthOptions) {}
  36. init(injector: Injector) {
  37. // Get the service we'll use to create/find customer accounts
  38. this.externalAuthenticationService = injector.get(ExternalAuthenticationService);
  39. }
  40. defineInputType(): DocumentNode {
  41. // Define the GraphQL input type for the authenticate mutation
  42. return gql`
  43. input GitHubAuthInput {
  44. code: String!
  45. state: String!
  46. }
  47. `;
  48. }
  49. async authenticate(ctx: RequestContext, data: GitHubAuthData): Promise<User | false> {
  50. const { code, state } = data;
  51. // Step 1: Exchange the authorization code for an access token
  52. const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
  53. method: 'POST',
  54. headers: {
  55. 'Accept': 'application/json',
  56. 'Content-Type': 'application/json',
  57. },
  58. body: JSON.stringify({
  59. client_id: this.options.clientId,
  60. client_secret: this.options.clientSecret,
  61. code,
  62. state,
  63. }),
  64. });
  65. const tokenData = await tokenResponse.json();
  66. if (tokenData.error) {
  67. throw new Error(`GitHub OAuth error: ${tokenData.error_description}`);
  68. }
  69. // Step 2: Use the access token to get user info from GitHub
  70. const userResponse = await fetch('https://api.github.com/user', {
  71. headers: {
  72. 'Authorization': `Bearer ${tokenData.access_token}`,
  73. 'Accept': 'application/vnd.github.v3+json',
  74. },
  75. });
  76. const user = await userResponse.json();
  77. if (!user.login) {
  78. throw new Error('Unable to retrieve user information from GitHub');
  79. }
  80. // Step 3: Check if this GitHub user already has a Vendure account
  81. const existingCustomer = await this.externalAuthenticationService.findCustomerUser(
  82. ctx,
  83. this.name,
  84. user.login, // GitHub username as external identifier
  85. );
  86. if (existingCustomer) {
  87. // User exists, log them in
  88. return existingCustomer;
  89. }
  90. // Step 4: Create a new customer account for first-time GitHub users
  91. const newCustomer = await this.externalAuthenticationService.createCustomerAndUser(ctx, {
  92. strategy: this.name,
  93. externalIdentifier: user.login, // Store GitHub username
  94. verified: true, // GitHub accounts are pre-verified
  95. emailAddress: `${user.login}-github@vendure.io`, // Unique email to avoid conflicts
  96. firstName: user.name?.split(' ')[0] || user.login,
  97. lastName: user.name?.split(' ').slice(1).join(' ') || '',
  98. });
  99. return newCustomer;
  100. }
  101. }
  102. ```
  103. The strategy uses Vendure's [ExternalAuthenticationService](/reference/typescript-api/auth/external-authentication-service/) to handle customer creation.
  104. 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.
  105. ## Registering the Strategy
  106. Now update the generated plugin file to register your authentication strategy:
  107. ```ts title="src/plugins/github-auth-plugin/github-auth-plugin.plugin.ts"
  108. import { PluginCommonModule, VendurePlugin } from '@vendure/core';
  109. import { GitHubAuthenticationStrategy, GitHubAuthOptions } from './github-auth-strategy';
  110. @VendurePlugin({
  111. imports: [PluginCommonModule],
  112. configuration: config => {
  113. config.authOptions.shopAuthenticationStrategy.push(new GitHubAuthenticationStrategy(GitHubAuthPlugin.options));
  114. return config;
  115. },
  116. })
  117. export class GitHubAuthPlugin {
  118. static options: GitHubAuthOptions;
  119. static init(options: GitHubAuthOptions) {
  120. this.options = options;
  121. return GitHubAuthPlugin;
  122. }
  123. }
  124. ```
  125. ## Adding to Vendure Config
  126. Add the plugin to your Vendure configuration:
  127. ```ts title="src/vendure-config.ts"
  128. import { VendureConfig } from '@vendure/core';
  129. import { GitHubAuthPlugin } from './plugins/github-auth-plugin/github-auth-plugin.plugin';
  130. export const config: VendureConfig = {
  131. // ... other config
  132. plugins: [
  133. // ... other plugins
  134. GitHubAuthPlugin.init({
  135. clientId: process.env.GITHUB_CLIENT_ID!,
  136. clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  137. }),
  138. ],
  139. // ... rest of config
  140. };
  141. ```
  142. ## Setting up GitHub OAuth App
  143. Before you can test the integration, you need to create a GitHub OAuth App:
  144. 1. Go to [GitHub Settings → Developer settings → OAuth Apps](https://github.com/settings/developers)
  145. 2. Click "New OAuth App"
  146. 3. Fill in the required fields:
  147. - **Application name**: Your app name (e.g., "My Vendure Store")
  148. - **Homepage URL**: `http://localhost:3001` (your storefront URL)
  149. - **Authorization callback URL**: `http://localhost:3001/auth/github/callback`
  150. 4. Click "Register application"
  151. 5. Copy the Client ID and generate a Client Secret
  152. :::note
  153. The localhost URLs shown here are for local development only. In production, replace `localhost:3001` with your actual domain (e.g., `https://mystore.com`).
  154. :::
  155. Add these credentials to your environment:
  156. ```bash title=".env"
  157. GITHUB_CLIENT_ID=your_github_client_id
  158. GITHUB_CLIENT_SECRET=your_github_client_secret
  159. ```
  160. ## Frontend Integration
  161. ### Creating the Sign-in URL
  162. In your storefront, create a function to generate the GitHub authorization URL:
  163. ```typescript title="utils/github-auth.ts"
  164. export function createGitHubSignInUrl(): string {
  165. const clientId = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID;
  166. const redirectUri = encodeURIComponent('http://localhost:3001/auth/github/callback');
  167. const state = Math.random().toString(36).substring(2);
  168. // Store state for CSRF protection
  169. sessionStorage.setItem('github_oauth_state', state);
  170. return `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=read:user&state=${state}`;
  171. }
  172. ```
  173. ### Handling the Callback
  174. Create a callback handler to process the GitHub response and authenticate with Vendure:
  175. ```typescript title="pages/auth/github/callback.ts"
  176. import { gql } from 'graphql-request';
  177. const AUTHENTICATE_MUTATION = gql`
  178. mutation Authenticate($input: GitHubAuthInput!) {
  179. authenticate(input: { github: $input }) {
  180. ... on CurrentUser {
  181. id
  182. identifier
  183. channels {
  184. code
  185. token
  186. permissions
  187. }
  188. }
  189. ... on InvalidCredentialsError {
  190. authenticationError
  191. errorCode
  192. message
  193. }
  194. }
  195. }
  196. `;
  197. export async function handleGitHubCallback(code: string, state: string) {
  198. // Verify CSRF protection
  199. const storedState = sessionStorage.getItem('github_oauth_state');
  200. if (state !== storedState) {
  201. throw new Error('Invalid state parameter');
  202. }
  203. sessionStorage.removeItem('github_oauth_state');
  204. // Authenticate with Vendure
  205. const result = await vendureClient.request(AUTHENTICATE_MUTATION, {
  206. input: { code, state }
  207. });
  208. if (result.authenticate.__typename === 'CurrentUser') {
  209. // Authentication successful - redirect to account page
  210. return result.authenticate;
  211. } else {
  212. // Handle authentication error
  213. throw new Error(result.authenticate.message);
  214. }
  215. }
  216. ```
  217. The OAuth flow follows these steps:
  218. 1. User clicks "Sign in with GitHub" → redirected to GitHub
  219. 2. User authorizes your app → GitHub redirects back with code and state
  220. 3. Your callback exchanges the code for user data → creates Vendure session
  221. ## Using the GraphQL API
  222. Once your plugin is running, the GitHub authentication will be available in your shop API:
  223. <Tabs>
  224. <TabItem value="Mutation" label="Mutation" default>
  225. ```graphql
  226. mutation AuthenticateWithGitHub {
  227. authenticate(input: {
  228. github: {
  229. code: "authorization_code_from_github",
  230. state: "csrf_protection_state"
  231. }
  232. }) {
  233. ... on CurrentUser {
  234. id
  235. identifier
  236. channels {
  237. code
  238. token
  239. permissions
  240. }
  241. }
  242. ... on InvalidCredentialsError {
  243. authenticationError
  244. errorCode
  245. message
  246. }
  247. }
  248. }
  249. ```
  250. </TabItem>
  251. <TabItem value="Response" label="Response">
  252. ```json
  253. {
  254. "data": {
  255. "authenticate": {
  256. "id": "1",
  257. "identifier": "github-user-github@vendure.io",
  258. "channels": [
  259. {
  260. "code": "__default_channel__",
  261. "token": "session_token_here",
  262. "permissions": ["Authenticated"]
  263. }
  264. ]
  265. }
  266. }
  267. }
  268. ```
  269. </TabItem>
  270. </Tabs>
  271. ## Customer Data Management
  272. GitHub-authenticated customers are managed like any other Vendure [Customer](/reference/typescript-api/entities/customer/):
  273. - **Email**: Generated as `{username}-github@vendure.io` to avoid conflicts
  274. - **Verification**: Automatically verified (GitHub handles email verification)
  275. - **External ID**: GitHub username stored for future authentication
  276. - **Profile**: Name extracted from GitHub profile when available
  277. This means GitHub users work seamlessly with Vendure's [order management](/guides/core-concepts/orders/), [promotions](/guides/core-concepts/promotions/), and customer workflows.
  278. ## Testing the Integration
  279. To test your GitHub OAuth integration:
  280. 1. Start your Vendure server with the plugin configured
  281. 2. Navigate to your storefront and click the GitHub sign-in link
  282. 3. Authorize your GitHub app when prompted
  283. 4. Verify that a new customer is created in the Vendure Dashboard
  284. 5. Check that subsequent logins find the existing customer account