Browse Source

docs: Add Facebook auth example

Michael Bromley 3 years ago
parent
commit
3cbd0fac82
1 changed files with 166 additions and 0 deletions
  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
 ## 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.
 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.