Jelajahi Sumber

docs: Add Facebook auth example

Michael Bromley 3 tahun lalu
induk
melakukan
3cbd0fac82
1 mengubah file dengan 166 tambahan dan 0 penghapusan
  1. 166 0
      docs/content/developer-guide/authentication.md

+ 166 - 0
docs/content/developer-guide/authentication.md

@@ -155,6 +155,172 @@ export class GoogleAuthenticationStrategy implements AuthenticationStrategy<Goog
 }
 ```
 
+## Example: Facebook authentication
+
+This example demonstrates how to implement a Facebook login flow.
+
+### Storefront setup
+
+In this example we are assuming the use of the [Facebook SDK for JavaScript](https://developers.facebook.com/docs/javascript/) in the storefront.
+
+An implementation in React might look like this:
+
+```tsx
+/**
+ * Renders a Facebook login button.
+ */
+export const FBLoginButton = () => {
+  const fnName = `onFbLoginButtonSuccess`;
+  const router = useRouter();
+  const [error, setError] = useState('');
+  const [socialLoginMutation] = useMutation(AuthenticateDocument);
+
+  useEffect(() => {
+    (window as any)[fnName] = function () {
+      FB.getLoginStatus(login);
+    };
+    return () => {
+      delete (window as any)[fnName];
+    };
+  }, []);
+
+  useEffect(() => {
+    window?.FB?.XFBML.parse();
+  }, []);
+
+  const login = async (response: any) => {
+    const { status, authResponse } = response;
+    if (status === 'connected') {
+      const result = await socialLoginMutation({ variables: { token: authResponse.accessToken } });
+      if (result.data?.authenticate.__typename === 'CurrentUser') {
+        // The user has logged in, refresh the browser
+        trackLogin('facebook');
+        router.reload();
+        return;
+      }
+    }
+    setError('An error occurred!');
+  };
+
+  return (
+    <div className="text-center" style={{ width: 188, height: 28 }}>
+      <FacebookSDK />
+      <div
+        className="fb-login-button"
+        data-width=""
+        data-size="medium"
+        data-button-type="login_with"
+        data-layout="default"
+        data-auto-logout-link="false"
+        data-use-continue-as="false"
+        data-scope="public_profile,email"
+        data-onlogin={`${fnName}();`}
+      />
+      {error && <div className="text-sm text-red-500">{error}</div>}
+    </div>
+  );
+};
+```
+
+```TypeScript
+import {
+  AuthenticationStrategy,
+  ExternalAuthenticationService,
+  Injector,
+  Logger,
+  RequestContext,
+  User,
+  UserService,
+} from '@vendure/core';
+
+import { DocumentNode } from 'graphql';
+import gql from 'graphql-tag';
+import fetch from 'node-fetch';
+
+export type FacebookAuthData = {
+  token: string;
+};
+
+export type FacebookAuthConfig = {
+  appId: string;
+  appSecret: string;
+  clientToken: string;
+};
+
+export class FacebookAuthenticationStrategy implements AuthenticationStrategy<FacebookAuthData> {
+  readonly name = 'facebook';
+  private externalAuthenticationService: ExternalAuthenticationService;
+  private userService: UserService;
+
+  constructor(private config: FacebookAuthConfig) {}
+
+  init(injector: Injector) {
+    // The ExternalAuthenticationService is a helper service which encapsulates much
+    // of the common functionality related to dealing with external authentication
+    // providers.
+    this.externalAuthenticationService = injector.get(ExternalAuthenticationService);
+    this.userService = injector.get(UserService);
+  }
+
+  defineInputType(): DocumentNode {
+    // Here we define the expected input object expected by the `authenticate` mutation
+    // under the "google" key.
+    return gql`
+      input FacebookAuthInput {
+        token: String!
+      }
+    `;
+  }
+
+  private async getAppAccessToken() {
+    const resp = await fetch(
+      `https://graph.facebook.com/oauth/access_token?client_id=${this.config.appId}&client_secret=${this.config.appSecret}&grant_type=client_credentials`,
+    );
+    return await resp.json();
+  }
+
+  async authenticate(ctx: RequestContext, data: FacebookAuthData): Promise<User | false> {
+    const { token } = data;
+    const { access_token } = await this.getAppAccessToken();
+    const resp = await fetch(
+      `https://graph.facebook.com/debug_token?input_token=${token}&access_token=${access_token}`,
+    );
+    const result = await resp.json();
+
+    if (!result.data) {
+      return false;
+    }
+
+    const uresp = await fetch(`https://graph.facebook.com/me?access_token=${token}&fields=email,first_name,last_name`);
+    const uresult = (await uresp.json()) as { id?: string; email: string; first_name: string; last_name: string };
+
+    if (!uresult.id) {
+      return false;
+    }
+
+    const existingUser = await this.externalAuthenticationService.findCustomerUser(ctx, this.name, uresult.id);
+
+    if (existingUser) {
+      // This will select all the auth methods
+      return (await this.userService.getUserById(ctx, existingUser.id))!;
+    }
+
+    Logger.info(`User Create: ${JSON.stringify(uresult)}`);
+    const user = await this.externalAuthenticationService.createCustomerAndUser(ctx, {
+      strategy: this.name,
+      externalIdentifier: uresult.id,
+      verified: true,
+      emailAddress: uresult.email,
+      firstName: uresult.first_name,
+      lastName: uresult.last_name,
+    });
+
+    user.verified = true;
+    return user;
+  }
+}
+```
+
 ## Example: Keycloak authentication
 
 Here's an example of an AuthenticationStrategy intended to be used on the Admin API. The use-case is when the company has an existing identity server for employees, and you'd like your Vendure shop admins to be able to authenticate with their existing accounts.