Facebook Auth

<< Google Auth Openlayers >>

En el caso de Facebook, vamos a hacer el mismo procedimiento que con el botón de Google. Cargaremos la librería de forma dinámica con un servicio y crearemos una directiva que convertirá cualquier botón en un botón de login con Facebook.

Lo primero que haremos será instalar los archivos de definición de tipos para TypeScript en nuestro proyecto:

npm i -D @types/facebook-js-sdk

Para importar estos tipos en el proyecto, tenemos que añadirlos en el array types del archivo tsconfig.app.json:

//...
"compilerOptions": {
  //...
  "types": [...,"facebook-js-sdk"],
},
//...

Crear credenciales OAuth2

Necesitamos crear credenciales para nuestra aplicación en la página de desarrolladores de Facebook, seleccionando la opción Mis Aplicaciones del menú superior (previo login con tu cuenta). Si te pregunta sobre crear una cuenta empresarial, puedes retroceder a la página inicial sin hacer caso al mensaje.

Selecciona el botón de Crear aplicación y después la opción de autenticar usuarios con el login de Facebook.

Una vez creada la aplicación, iremos a la sección Casos de Uso y configuraremos el inicio de sesión con Facebook. Dentro de la sección Permisos, seleccionaremos ambos: email y public_profile.

En la sección Configuración añadiremos el dominio donde se ejecuta la aplicación en la parte de Dominios admitidos para el SDK de JavaScript.

Una vez configurada la aplicación, para poder probar el login mientras está en modo desarrollo, debemos crear una aplicación de prueba derivada de la que acabamos de crear. Para ello abrimos el menú inferior derecho de la tarjeta que contiene los datos de la aplicación creada y seleccionamos crear aplicación de prueba.

Entramos en la configuración del proyecto de prueba, nos fijamos en la siguiente información que necesitaremos en nuestra aplicación cliente para utilizar la API de Facebook: Identificador de aplicación y versión de API.

Cargar la librería

Al igual que hicimos con Google, vamos a crear un token de inyección para poder configurar la id de aplicación y versión de API desde el archivo app.config.ts. Creamos el archivo facebook-login/facebook-login.config.ts:

import { InjectionToken, Provider } from '@angular/core';

export interface FBConfig {
  app_id: string;
  version: string;
}

export const FB_CONFIG = new InjectionToken<FBConfig>('fb_config');

export function provideFacebookId(appId: string, version: string): Provider {
  return { provide: FB_CONFIG, useValue: {app_id: appId, version: version} };
}

Utilizaremos la función provideFacebookId en el array providers de la aplicación. Llama a la función pasándole la ID de aplicación y la versión de la API:

export const appConfig: ApplicationConfig = {
  providers: [
    //...
    provideFacebookId('APP_ID', 'v15.0')
  ],
};

Después, crearemos un servicio que se encargará tanto de cargar la librería, como de gestionar las llamadas a la API de FaceBook para el login. Será un poco diferente al de Google ya que Facebook lo que hace es crear una variable global (FB) cuando ha terminado de cargarse y con dicho objeto podemos gestionar el login.

El servicio gestionará tanto la carga de la API (con una promesa), como las llamadas al login y logout.

ng g service facebook-login/load-fb-api

import { Injectable, inject } from '@angular/core';
import { Observable, ReplaySubject } from 'rxjs';
import { FB_CONFIG } from './facebook-login.config';

@Injectable({
  providedIn: 'root',
})
export class LoadFbApiService {
  #loader: Promise<void>;

  #fbConfig = inject(FB_CONFIG, { optional: true });

  constructor() {
    if (!this.#fbConfig) {
      throw new Error(
        'FacebookLogiService: You must call provideFacebookId in app.config.ts to pass the APP_ID and API Version'
      );
    }
    this.#loader = this.#loadApi(); // Start loading the API
  }

  async login(scopes: string): Promise<fb.StatusResponse> {
    await this.#loader;

    try {
      return await this.isLogged();
    } catch (e) {
      // Not logged in
      return new Promise((resolve, reject) => {
        FB.login(
          (respLogin: fb.StatusResponse) => {
            if (respLogin.status === 'connected') {
              resolve(respLogin);
            } else {
              reject(respLogin);
            }
          },
          { scope: scopes }
        );
      });
    }
  }

  async isLogged(): Promise<fb.StatusResponse> {
    await this.#loader;

    return new Promise((resolve, reject) => {
      FB.getLoginStatus((response) => {
        if (response.status === 'connected') {
          resolve(response);
        } else {
          reject(response);
        }
      });
    });
  }

  async logout(): Promise<void> {
    await this.#loader;

    return new Promise((resolve) => {
      FB.logout(() => resolve());
    });
  }

  #loadApi(): Promise<void> {
    const script = document.createElement('script');
    script.id = 'facebook-jssdk';
    script.src = 'https://connect.facebook.net/es_ES/sdk.js';
    script.defer = true;
    document.body.appendChild(script);

    return new Promise((resolve) => {
      window['fbAsyncInit'] = () => {
        FB.init({
          appId: this.#fbConfig!.app_id,
          xfbml: true,
          autoLogAppEvents: true,
          version: this.#fbConfig!.version,
        });
        resolve();
      };
    });
  }
}

Crear directiva para el botón de login

Vamos a crear una directiva para convertir cualquier botón en un boton de login con Facebook. En este caso la directiva se va a encargar también de gestionar el click del botón, por lo que vamos a crear 2 parámetros de salida (output) para avisar al componente padre cuando el login sea correcto o se haya producido cualquier error.

ng g directive facebook-login/fb-login

@Directive({
  selector: '[fbLogin]',
  standalone: true,
  host: {
    '(click)': 'onClick()'
  }
})
export class FbLoginDirective {
  loginOk = output<fb.StatusResponse>();
  loginError = output<string>();
  scopes = input.required<string[]>();

  platformId = inject(PLATFORM_ID);
  #loadService =  isPlatformBrowser(this.platformId) ? inject(LoadFbApiService) : null;

  async onClick(): Promise<void> {
    try {
      const resp = await this.#loadService!.login(this.scopes().join(','));
      this.loginOk.emit(resp);
    } catch {
      this.loginError.emit('Error with Facebook login!');
    }
  }
}

Al igual que hicimos con el botón de Google en la sección anterior, solo inyectamos el servicio cuando la página se renderiza en el cliente, por si acaso tenemos SSR activado en la aplicación, ya que no queremos que se cargue la librería (constructor del servicio) de Facebook cuando se renderiza en el servidor.

Por último, creamos el botón en la página de login y gestionamos desde ahí los eventos de login con Facebook. Debemos enviar a nuestro back-end el token de Facebook (accessToken) para que tenga acceso a las credenciales del usuario y pueda registrarlo en la base de datos y realizar el login interno de nuestra aplicación sin pasar por el formulario.

<button
  fbLogin
  [scopes]="['email', 'public_profile']"
  class="btn btn-primary"
  (loginOk)="loggedFacebook($event)"
  (loginError)="showError($event)"
>
  <fa-icon [icon]="iconFacebook"></fa-icon>
  Login con Facebook
</button>

Finalmente habría que crear un mecanismo que permita cerrar la sesión con Facebook para poder entrar con otra cuenta.

<< Google Auth Openlayers >>