Angular 13 Firebase Authentication Tutorial With AngularFire 7

La autenticación es clave para proteger nuestras aplicaciones de usuarios no registrados. En este tutorial, aprenderá a implementar tanto un inicio de sesión de correo electrónico/contraseña tradicional como un inicio de sesión social de Google en Firebase, con el nuevo SDK modular AngularFire v.7.0 que se puede sacudir en árbol, lo que nos permite aprovechar al máximo el nuevo árbol. SDK de Firebase JS movible (v9) .

También aprenderá cómo proteger las rutas con AngularFire Auth Guard y cómo redirigir a los usuarios a ciertas páginas en función de si están autenticados o no.

Tenga en cuenta que, al momento de escribir este tutorial, la API de Angular Fire v.7.0 todavía está en desarrollo y no tiene la función completa ; y los documentos aún no están actualizados a la última versión. AngularFire proporciona una capa de compatibilidad que le permitirá utilizar la última versión de la biblioteca, al mismo tiempo que es totalmente compatible con la API de AngularFire v6.0. Dicho esto, si tienes tanta curiosidad como yo y disfrutas probando las últimas versiones de todas tus bibliotecas, sigue adelante y disfruta de este tutorial.

Aquí hay una vista previa de la aplicación que construiremos hoy:

Vista previa de la aplicación de autenticación

Índice

  • Configuración de un proyecto de base de fuego.
  • Configuración de un proyecto Angular con AngularFire.
  • Construcción de la aplicación.
  • Creación del tablero.
  • Creación del módulo de autenticación.
  • Adición de inicio de sesión de Google.
  • Protección de las rutas.

 

Configuración del proyecto de Firebase

Para crear un nuevo proyecto de Firebase, debemos iniciar sesión en la consola de Firebase. Una vez que hayamos hecho eso, podemos hacer clic en Agregar proyecto para crear un nuevo proyecto:

Firebase — Creando un nuevo proyecto

Tendremos que elegir un nombre para nuestro proyecto:

Firebase: elegir un nombre

Decida si queremos o no Google Analytics en nuestro proyecto (usted elige, me quedo con no):

Firebase: habilitar Google Analytics

¡Hemos terminado! Ahora tenemos un hermoso proyecto nuevo de Firebase.

Agregar Firebase a una aplicación web

Ahora necesitamos agregar Firebase a nuestra aplicación Angular. Para hacerlo, debemos hacer clic en el botón de la aplicación web, en el tablero:

Firebase — Agregar una aplicación web

Luego tenemos que elegir un nombre:

Finalmente, ahora tenemos la configuración que se usará más adelante para agregar Firebase a nuestra aplicación Angular:

Agregar autenticación

Es hora de agregar autenticación y elegir los métodos de inicio de sesión que queremos implementar. En este tutorial, agregaremos un inicio de sesión social de Google y un inicio de sesión de correo electrónico/contraseña tradicional. Dirígete a la página de Autenticación y elige los proveedores:

Firebase: elegir métodos de inicio de sesión

Una vez que hayamos habilitado ambos métodos de inicio de sesión, nuestro panel de Autenticación debería verse así:

Firebase: métodos de inicio de sesión habilitados

¡Eso es todo! Ahora tenemos nuestra aplicación Firebase completamente configurada y lista. Es hora de comenzar con nuestra aplicación Angular.

 

Configuración de un proyecto angular

Lo primero que vamos a hacer es crear un nuevo proyecto con Angular CLI.

Sugerencia: si no ha instalado Angular CLI, puede hacerlo ejecutando el siguiente comando:

npm i -g @angular/cli

Para crear un nuevo proyecto Angular, podemos ejecutar el siguiente comando:

ng new ngbytes-fireauth

Nota: ¡No olvide responder sí cuando se le pregunte si desea agregar enrutamiento a su nueva aplicación!

Una vez que la CLI ha hecho su magia, podemos abrir el proyecto recién creado con nuestro IDE favorito (sugiero VSCode , que es el que normalmente uso).

Agregar Firebase y AngularFire

Agreguemos Firebase y Angularfire a nuestro proyecto. Para ello, utilizaremos el esquema de AngularFire, que se encargará de configurarlo todo por nosotros. Ejecutemos el siguiente comando:

ng add @angular/fire

Se nos hará una serie de preguntas, como qué funciones de Firebase nos gustaría configurar. Para este tutorial, solo necesitamos agregar autenticación, así que seleccionemos eso:

Luego se nos preguntará sobre la cuenta de Firebase que nos gustaría usar y qué proyecto queremos configurar. Seleccione el proyecto que creamos anteriormente y luego seleccione la aplicación que también creamos anteriormente.

Una vez que hayamos hecho todo esto, verá que el esquema se ha ocupado de toda la configuración de Firebase por nosotros. ¡Impresionante!

Adición de material angular

También agregaremos material angular. Una vez más, usaremos un esquema:

ng add @angular/material

Desactivación de la inicialización de propiedad estricta

Tendremos que establecer la strictPropertyInitializationpropiedad en el tsconfig.jsonarchivo en falso. Estamos haciendo esto porque el modo estricto está habilitado de manera predeterminada en todas las nuevas aplicaciones de Angular a partir de la versión 12, lo que significa que TypeScript se quejará si declaramos propiedades de clase sin configurarlas en el constructor (una práctica común en Angular). tsconfig.jsonAsí es como debería verse nuestro archivo:

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "strictPropertyInitialization": false,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "module": "es2020",
    "lib": [
      "es2018",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

archivo tsconfig.json

Eliminación del texto modelo de Angular

Por último, pero no menos importante, eliminaremos el código repetitivo que Angular genera automáticamente en el archivo app.component.html. Tenga mucho cuidado y asegúrese de no eliminar las <router-outlet></router-outlet>etiquetas cuando elimine el código repetitivo, o el enrutador no funcionará.

Después de eliminar todo, app.component.htmlsolo debe contener las etiquetas de salida del enrutador:

etiquetas de salida de enrutador en app.component.html

 

Creando el tablero

Es hora de comenzar a construir nuestra aplicación Angular. Comencemos por crear un dashboardmódulo. Este es el módulo que contendrá las páginas que queremos proteger de usuarios no autenticados. Comencemos por crear un featuresdirectorio dentro del src/appdirectorio, que contendrá todos nuestros módulos de características:

mkdir features

Dentro de este nuevo directorio, crearemos nuestro dashboardmódulo con Angular CLI:

ng g m dashboard -m app --route dashboard

Sugerencia: estamos usando la --routeopción, que crea un componente en el nuevo módulo y agrega la ruta a ese componente en la matriz declarada en el módulo provisto en la opción.Routes-m

Verás que la CLI ha creado un dashboardmódulo dentro del featuresdirectorio, con su dashboard-routing.module.tscomponente correspondiente. También modificó el app-routing.module.ts, y agregó una dashboardruta, lazy loading el DashboardModule.

Agreguemos información a nuestro dashboardcomponente, modificando el dashboard.component.htmlarchivo:

<mat-toolbar></mat-toolbar>
<div class="background">
  <h1>Welcome to the NgBytes Dashboard</h1>
  <h2>Built with &lt;3 by Dottech</h2>

  <a target="_blank" class="btn" href="https://github.com/puntotech/ngbytes-fireauth">
    Click here to view the source code
  </a>
</div>

La plantilla del componente del tablero

Siéntete libre de agregar lo que quieras a este archivo.

Sugerencia: no olvides importar el MatToolbarModuleal DashboardModule, si decides usarlo también.

Agreguemos un poco de CSS para que el tablero se vea bonito:

h1 {
  color: #5eccd6;
  font-size: 50px;
  font-weight: bolder;
}

h2 {
  color: #698893;
  margin-bottom: 50px;
}

a {
  color: #5eccd6;
  border-bottom: 2px solid transparent;
  transition: 0.5s border-bottom;
}

mat-toolbar {
  background-color: transparent;
  display: flex;
  justify-content: right;
}

.background {
  align-items: center;
  background-image: url("/assets/mountains.png");
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 64px);
  padding-top: 10%;
}

.btn {
  background-color: #5eccd6;
  border-radius: 20px;
  color: #fff;
  padding: 10px 30px;
  text-decoration: none;
  transition: 0.5s background-color;
}

a:hover {
  border-bottom: 2px solid #5eccd6;
  cursor: pointer;
}

.btn:hover {
  background-color: #52b7c0;
}

El CSS del componente del tablero

Et voilà, ¡nuestro dashboardmódulo está listo! Para verlo, podemos navegar a http://localhost:4200/dashboard:

Vista previa del panel

Es hora de comenzar con nuestra autenticación.

 

Creación del módulo de autenticación

Dentro del featuresdirectorio, usaremos Angular CLI para crear un authmódulo:

ng g m auth --routing

Sugerencia: estamos usando la --routingbandera, que genera tanto un auth-routing.module.tscomo elauth.module.ts .

Una vez que hemos creado nuestro módulo, debemos agregar su ruta correspondiente al app-routing.module.tsarchivo.

import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Agregar rutas al módulo de enrutamiento de aplicaciones

Tenga en cuenta que estamos agregando una ruta comodín, que redirige a la ruta predeterminada. Hacemos esto para que, si intentamos navegar a rutas que no existen, en lugar de que se arroje un error, seremos redirigidos.

Creación de un servicio de autenticación

Todas nuestras interacciones con Firebase y AngularFire se llevarán a cabo dentro del AuthService. Para crear nuestro servicio, volveremos a utilizar la CLI de Angular. Dentro de nuestro appdirectorio:

mkdir core
cd core
mkdir services
cd services
ng g s auth

Dentro de nuestro authService, lo primero que debemos hacer es inyectar la Authinstancia de AngularFire en el constructor. Luego, crearemos un loginmétodo, que recibirá los datos de inicio de sesión (correo electrónico y contraseña), y usaremos el signInWithEmailAndPasswordmétodo AngularFire para iniciar sesión:


import { Auth, signInWithEmailAndPassword } from '@angular/fire/auth';

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: any) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }
}

AuthService con método de inicio de sesión sin tipo

Sin embargo, ¿no parece algo extraño en nuestra loginfunción? Tenemos un muy indecoroso any! Como estamos usando TypeScript y queremos tener todo bien escrito, creemos una LoginDatainterfaz para escribir nuestros datos de inicio de sesión. En nuestro coredirectorio:

mkdir interfaces
cd interfaces
touch login-data.interface.ts

Así es como se verá nuestra interfaz:

export interface LoginData {
  email: string;
  password: string;
}

La interfaz LoginData https://gist.github.com/NyaGarcia/2ec63c5fe25c1d281fcdb011722e9712

Ahora podemos usar nuestra nueva interfaz para escribir correctamente loginlos parámetros de nuestra función:

import { Auth, signInWithEmailAndPassword } from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }
}

AuthService con función de inicio de sesión escrita

Se ve mucho mejor, ¿no?

También crearemos un método de registro, que usará el createUserWithEmailAndPasswordmétodo AngularFire, para que los nuevos usuarios puedan registrarse en nuestra aplicación. También crearemos un logoutmétodo que, como probablemente pueda adivinar, permitirá a nuestros usuarios cerrar sesión en nuestra aplicación:

import {
  Auth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
} from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }

  register({ email, password }: LoginData) {
    return createUserWithEmailAndPassword(this.auth, email, password);
  }

  logout() {
    return signOut(this.auth);
  }
}

Agregar las funciones de registro y cierre de sesión al AuthService

Eso es todo lo que tenemos que hacer en nuestro AuthService(agregaremos la opción de inicio de sesión social de Google más adelante) por ahora.

Ahora es el momento de crear los formularios de registro e inicio de sesión, para que nuestros usuarios puedan autenticarse.

Creación de la página de inicio de sesión

Dentro del authdirectorio, crearemos un login-pagecomponente, que mostrará un mensaje de bienvenida, el formulario de inicio de sesión para que los usuarios puedan iniciar sesión y, más adelante, el botón de inicio de sesión de Google:

mkdir pages
cd pages
ng g c login-page

Una vez que hayamos creado nuestra página, vayamos al login-page.component.htmlarchivo y agreguemos un título simple:

<h1>Welcome to NgBytes Login</h1>

Dado que las páginas son componentes enrutados, debemos agregar una ruta a nuestro auth-routing.module.tsarchivo que apunte al LoginPageComponent, así:

import { RouterModule, Routes } from '@angular/router';

import { LoginPageComponent } from './pages/login-page/login-page.component';
import { NgModule } from '@angular/core';

const routes: Routes = [{ path: '', component: LoginPageComponent }];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class AuthRoutingModule {}

Enrutamiento de LoginPageComponent en AuthRoutingModule

Ejecutemos el npm startscript y verifiquemos que todo esté enrutado correctamente. Si abre su navegador a localhost:4200, debería estar viendo esto:

Vista previa de LoginPageComponent

¡Impresionante! Es hora de crear un formulario de inicio de sesión, para que nuestros usuarios puedan iniciar sesión en nuestra aplicación:

Crear el formulario de inicio de sesión

Ahora crearemos un login-formcomponente, dentro de nuestro authdirectorio:

mkdir components
cd components
ng g c login-form

Sugerencia: dado que no escribiremos ninguna prueba en este tutorial, no dude en eliminar el login-form.component.spec.tsarchivo.

Para crear nuestro formulario, usaremos el módulo Formularios reactivos. También usaremos los módulos de material angular para diseñar nuestro formulario. Dado que no es el objetivo de este tutorial, no entraré en detalles sobre cómo usar formularios reactivos.

Primero, importaremos los módulos ReactiveFormsModule, the MatInputModule, the MatFormFieldModuley the en nuestro :MatButtonModuleAuthModule

import { AuthRoutingModule } from './auth-routing.module';
import { CommonModule } from '@angular/common';
import { LoginFormComponent } from './components/login-form/login-form.component';
import { LoginPageComponent } from './pages/login-page/login-page.component';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [LoginPageComponent, LoginFormComponent],
  imports: [
    CommonModule,
    AuthRoutingModule,
    ReactiveFormsModule,
    MatInputModule,
    MatFormFieldModule,
    MatButtonModule,
  ],
})
export class AuthModule {}

Importación de módulos en AuthModule

Luego, crearemos el formulario en nuestro login-form.component.tsarchivo:

import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'ngbytes-login-form',
  templateUrl: './login-form.component.html',
  styleUrls: ['./login-form.component.css'],
})
export class LoginFormComponent implements OnInit {
  @Output() formData: EventEmitter<{
    email: string;
    password: string;
  }> = new EventEmitter();

  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.form = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required],
    });
  }

  get email() {
    return this.form.get('email');
  }

  get password() {
    return this.form.get('password');
  }

  onSubmit() {
    this.formData.emit(this.form.value);
  }
}

La lógica de LoginFormComponent

Como puede ver, hemos agregado una validación básica a nuestro formulario: tanto el campo de correo electrónico como el de contraseña son obligatorios, y el campo de correo electrónico solo permite direcciones de correo electrónico válidas.

Sugerencia: ¿Le parece extraño que estemos usando un EventEmittercorreo electrónico para emitir los datos de nuestro formulario al enviarlo? Esto se debe a que estamos usando el patrón de componente inteligente/tonto, que desvincula la lógica comercial de los componentes puramente de presentación.

Por último, en nuestro login-form.component.htmlarchivo:

<form class="form" [formGroup]="form" (ngSubmit)="onSubmit()">
  <mat-form-field class="form-control">
    <mat-label>Email</mat-label>
    <input matInput formControlName="email" type="text" required />
    <mat-error *ngIf="email?.hasError('required')">
      Email is required
    </mat-error>
    <mat-error *ngIf="email?.hasError('email')">
      Email isn't valid. Please enter a valid email.
    </mat-error>
  </mat-form-field>
  <mat-form-field class="form-control">
    <mat-label>Password</mat-label>
    <input matInput formControlName="password" type="password" required />
    <mat-error *ngIf="password?.hasError('required')">
      Password is required
    </mat-error>
  </mat-form-field>
  <div class="form-footer">
    <button [disabled]="form.invalid" mat-raised-button type="submit">
      Submit
    </button>
  </div>
</form>

La plantilla LoginFormComponent

Nota: ¿Ves cómo estamos deshabilitando el botón de enviar si el formulario no es válido? Esa es una mala práctica (UX deficiente), y la única razón por la que lo hago es por brevedad, ya que este tutorial debe centrarse en AngularFire y no en las mejores prácticas en formularios Angular. Sin embargo, tenga cuidado, ¡no haga esto en casa, amigos!

Agreguemos un poco de CSS en nuestro login-form.component.cssarchivo para que el formulario sea más bonito (puede omitir este paso si lo desea):

button {
  background-color: #5eccd6;
  border-radius: 20px;
  color: #fff;
  padding: 0 50px;
}

form {
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  height: 200px;
  width: 300px;
}

mat-form-field {
  width: 100%;
}

El CSS del componente LoginForm

¡Excelente! Ahora tenemos un hermoso formulario de inicio de sesión. Todo lo que queda es agregarlo a la login-page.component.htmlplantilla:


<div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <h5>Sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
</div>

Adición del formulario de inicio de sesión a la página de inicio de sesión

También necesitamos inyectar lo authServiceque creamos anteriormente en nuestro login-page.component.tsarchivo y crear un loginmétodo, que usará authServicepara iniciar sesión en el usuario, redirigiéndolo al tablero una vez que haya terminado. ¡No olvides detectar cualquier posible error!

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.component.html',
  styleUrls: ['./login-page.component.css'],
})
export class LoginPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  login(loginData: LoginData) {
    this.authService
      .login(loginData)
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }
}

Creación de un método de inicio de sesión en login-page.component

Probemos lo que hemos hecho hasta ahora yendo a nuestro navegador y completando el formulario. Como puede ver, si intentamos dejar el campo de correo electrónico en blanco, o ingresar un correo electrónico no válido, se mostrará un error, lo que significa que nuestra validación está funcionando espléndidamente. Sin embargo, si intentamos presionar el botón de enviar…

Estamos recibiendo un error de Usuario no encontrado en nuestra consola. Bueno, por supuesto que lo somos. Todavía no hemos registrado ningún usuario, ¿verdad? ¡Creemos un formulario de registro para que podamos hacerlo!

Creación de una página de registro

Al igual que hicimos con nuestra página de inicio de sesión, vamos a crear una página de registro, que contendrá el formulario de registro. Dentro del auth/pagesdirectorio:

ng g c register-page

Agreguemos un título a nuestra página de registro y algunos estilos:

<div class="container">
  <h1>Welcome to NgBytes Register</h1>
</div>

La plantilla RegisterPageComponent

h1 {
  color: #838383;
  margin-bottom: 50px;
}

.container {
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 80px;
}

El CSS de RegisterPageComponent https://gist.github.com/NyaGarcia/f6f3f5892f711fa31fa4acac1c886968

Por último, pero no menos importante, debemos agregar una registerruta al auth-routing.module.tsarchivo:


import { RouterModule, Routes } from '@angular/router';

import { LoginPageComponent } from './pages/login-page/login-page.component';
import { NgModule } from '@angular/core';
import { RegisterPageComponent } from './pages/register-page/register-page.component';

const routes: Routes = [
  { path: '', component: LoginPageComponent },
  { path: 'register', component: RegisterPageComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class AuthRoutingModule {}

Agregar la ruta RegisterPageComponent

Ya que nuestra página de registro está lista, todo lo que tenemos que hacer es crear un formulario de registro... ¿o sí?

Nuestro formulario de inicio de sesión tiene dos campos, un correo electrónico y un campo de contraseña. Nuestro formulario de registro debe tener dos campos, un correo electrónico y un campo de contraseña. ¿Ves a dónde estoy llegando? ¡Nuestras dos formas van a ser exactamente iguales! Puede que estés pensando que no, que no son lo mismo. Un formulario se utiliza para iniciar sesión, el otro para registrar un nuevo usuario; dos piezas de lógica completamente diferentes.

Sin embargo…

¿Recuerda cómo usamos el patrón de componente inteligente/tonto para desacoplar el formulario de inicio de sesión de la página de inicio de sesión? Nuestro formulario de inicio de sesión no está a cargo de realizar ninguna lógica en absoluto, nuestra página de inicio de sesión sí lo está. Lo que significa que somos libres de reutilizar el formulario de inicio de sesión en nuestra página de registro:

<div class="container">
  <h1>Welcome to NgBytes Register</h1>
  <ngbytes-login-form (formData)="register($event)"></ngbytes-login-form>
</div>

Agregar el formulario de inicio de sesión a la página de registro

Genial, ¿no? Ahora llega el momento de implementar la lógica para dar de alta un nuevo usuario. Así lo haremos, por supuesto, en nuestra register-page.component.tsficha:

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-register-page',
  templateUrl: './register-page.component.html',
  styleUrls: ['./register-page.component.css'],
})
export class RegisterPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  register(data: LoginData) {
    this.authService
      .register(data)
      .then(() => this.router.navigate(['/login']))
      .catch((e) => console.log(e.message));
  }
}

Agregar la lógica de registro a RegisterPageComponent

Como puede ver, estamos llamando al authService.registermétodo, que registrará al nuevo usuario en nuestra aplicación Firebase, y luego estamos navegando a la página de inicio de sesión, para que nuestro usuario recién registrado pueda iniciar sesión en nuestra aplicación.

Agreguemos algunos toques finales a la página de inicio de sesión: un botón que redirigirá a los nuevos usuarios a la página de registro y cambiará un poco el diseño para que se vea decente:

<div class="background">
  <div class="login-container">
    <div class="text-container">
      <h1>Hey Stranger</h1>
      <p>
        Want to create an awesome account to access this awesome app? Go ahead
        and click the button below!
      </p>
      <button mat-button routerLink="/register">Sign up</button>
    </div>
    <div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <h5>Sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
    </div>
  </div>
</div>

Mejorando la página de inicio de sesión

Un poco de magia CSS:

a {
  color: #838383;
  margin-top: 30px;
  text-decoration: none;
}

h1 {
  color: #5eccd6;
  font-weight: bolder;
  margin-bottom: 20px;
}

h5 {
  color: #838383;
}

button {
  border-radius: 20px;
  padding: 0 50px;
}

p {
  margin-bottom: 20px;
  text-align: center;
}

.background {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: center;
}

.login-container {
  border-radius: 10px;
  box-shadow: 0 0 20px 2px #e4e4e4;
  display: flex;
  width: 60%;
}

.text-container {
  align-items: center;
  background-color: #5eccd6;
  border-radius: 10px 0 0 10px;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 50px;
  width: 30%;
}

.form-container {
  align-items: center;
  background-color: #fff;
  border-radius: 0 10px 10px 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 80px;
  width: 70%;
}

.text-container h1 {
  color: #fff;
}

.text-container button {
  background-color: transparent;
  border: 1px solid #fff;
}

Agregar estilos a la página de inicio de sesión

Ya que hemos hecho que nuestra página de inicio de sesión se vea bonita, tomemos un momento y hagamos lo mismo con nuestra página de registro:

<div class="background">
  <div class="login-container">
    <div class="form-container">
      <h1>Welcome to NgBytes Register</h1>
      <h5>Register with your email</h5>
      <ngbytes-login-form (formData)="register($event)"></ngbytes-login-form>
    </div>
    <div class="text-container">
      <h1>Hey Buddy</h1>
      <p>
        Already have an awesome account? Go ahead and click the button below to
        sign in!
      </p>
      <button mat-button routerLink="/">Sign in</button>
    </div>
  </div>
</div>

Mejorando la página de registro

Un poco más de CSS:

a {
  color: #838383;
  margin-top: 30px;
  text-decoration: none;
}

h1 {
  color: #5eccd6;
  font-weight: bolder;
  margin-bottom: 20px;
}

h5 {
  color: #838383;
}

button {
  border-radius: 20px;
  padding: 0 50px;
}

p {
  margin-bottom: 20px;
  text-align: center;
}

.background {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: center;
}

.login-container {
  border-radius: 10px;
  box-shadow: 0 0 20px 2px #e4e4e4;
  display: flex;
  width: 60%;
}

.text-container {
  align-items: center;
  background-color: #5eccd6;
  border-radius: 0 10px 10px 0;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 50px;
  width: 30%;
}

.form-container {
  align-items: center;
  background-color: #fff;
  border-radius: 10px 0 0 10px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 80px;
  width: 70%;
}

.text-container h1 {
  color: #fff;
}

.text-container button {
  background-color: transparent;
  border: 1px solid #fff;
}

Agregar estilos a la página de registro

Es hora de probar lo que hemos hecho hasta ahora. Dirígete a tu navegador:

Registro de un nuevo usuario e inicio de sesión

¡Impresionante! ¡Estamos iniciando sesión en nuestra aplicación! Sin embargo, todavía nos falta algo, ¿no? ¡Necesitamos una opción de cierre de sesión! Vamos a agregarlo a nuestro tablero.

Agregar una opción de cierre de sesión

Vayamos a nuestro dashboard.component.htmly creemos una opción de cierre de sesión:

<mat-toolbar><a (click)="logout()">Log out</a></mat-toolbar>
<div class="background">
  <h1>Welcome to the NgBytes Dashboard</h1>
  <h2>Built with &lt;3 by Dottech</h2>

  <a
    target="_blank"
    class="btn"
    href="https://github.com/puntotech/ngbytes-fireauth"
  >
    Click here to view the source code
  </a>
</div>

Agregar una opción de cierre de sesión al tablero

Ahora que tenemos nuestra opción de cierre de sesión, necesitamos agregar algo de lógica a nuestro archivo dashboard.component.ts. Primero, inyectaremos el AuthServiceen el constructor y luego crearemos un logoutmétodo:

import { Component, OnInit } from '@angular/core';

import { AuthService } from '../../core/services/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css'],
})
export class DashboardComponent implements OnInit {
  constructor(private authService: AuthService, private router: Router) {}

  ngOnInit(): void {}

  logout() {
    this.authService
      .logout()
      .then(() => this.router.navigate(['/']))
      .catch((e) => console.log(e.message));
  }
}

Agregar un método de cierre de sesión al componente del tablero

Como puede ver, una vez que llamemos al logoutmétodo, redirigiremos al usuario a la ruta predeterminada. ¡Eso es todo! Hemos implementado con éxito una forma para que nuestros usuarios cierren sesión en nuestra aplicación. Una vez más, probemos nuestra aplicación:

Saliendo de tu cuenta

 

Adición de inicio de sesión de Google

Ahora que nuestro inicio de sesión tradicional se ha implementado por completo, es hora de agregar nuestro inicio de sesión social de Google. Para hacerlo, primero crearemos el método necesario en el auth.service:

import {
  Auth,
  GoogleAuthProvider,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signInWithPopup,
  signOut,
} from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }

  loginWithGoogle() {
    return signInWithPopup(this.auth, new GoogleAuthProvider());
  }

  register({ email, password }: LoginData) {
    return createUserWithEmailAndPassword(this.auth, email, password);
  }

  logout() {
    return signOut(this.auth);
  }
}

Agregar un método loginWithGoogle al AuthService

Luego, necesitaremos agregar un botón de inicio de sesión de Google a la página de inicio de sesión:

<div class="background">
  <div class="login-container">
    <div class="text-container">
      <h1>Hey Stranger</h1>
      <p>
        Want to create an awesome account to access this awesome app? Go ahead
        and click the button below!
      </p>
      <button mat-button routerLink="/register">Sign up</button>
    </div>
    <div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <button class="mat-raised-button" (click)="loginWithGoogle()">
        Sign In with Google
      </button>
      <h5>Or sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
    </div>
  </div>
</div>

Agregar un botón de inicio de sesión de Google

Finalmente, necesitaremos agregar una loginWithGooglefunción a nuestro login-page.component.ts:

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.component.html',
  styleUrls: ['./login-page.component.css'],
})
export class LoginPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  login(loginData: LoginData) {
    this.authService
      .login(loginData)
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }

  loginWithGoogle() {
    this.authService
      .loginWithGoogle()
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }
}

Agregar un método loginWithGoogle al LoginPageComponent

¡Hemos terminado! Vayamos a nuestro navegador y pruébelo:

Iniciar sesión con google

 

Protegiendo las rutas

Si intenta navegar a la página del panel localhost:4200/dashboard, verá que podrá hacerlo, incluso si no está autenticado. Para resolver este problema, utilizaremos la protección de autenticación proporcionada por AngularFire para redirigir automáticamente a los usuarios no autorizados a la página de inicio de sesión. En nuestro app-routing.module.ts:

import { AuthGuard, redirectUnauthorizedTo } from '@angular/fire/auth-guard';
import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin },
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Protegiendo las rutas

Como puede ver, hemos protegido la dashboardruta con AngularFire AuthGuardy hemos construido una redirectUnauthorizedToLogincanalización, utilizando la redirectUnauthorizedTocanalización AngularFire, que luego proporcionamos a AuthGuard. Esto garantizará que cualquier usuario no autenticado que intente acceder a la página del panel de control será redirigido automáticamente a la página de inicio de sesión. Siéntase libre de dirigirse a su navegador y probarlo.

De la misma manera que no queremos que los usuarios no autorizados accedan al panel, si el usuario ya está autenticado, no debería poder navegar a la página de inicio de sesión. Vamos a crear otra canalización personalizada, una que redirigirá a los usuarios que ya iniciaron sesión al tablero:

import {
  AuthGuard,
  redirectLoggedInTo,
  redirectUnauthorizedTo,
} from '@angular/fire/auth-guard';
import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);
const redirectLoggedInToHome = () => redirectLoggedInTo(['dashboard']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectLoggedInToHome },
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin },
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Agregar el protector de ruta redirectLoggedInToHome

Ahora, si hemos iniciado sesión e intentamos acceder a la página de inicio de sesión, seremos redirigidos automáticamente al panel de control.

El ayudante canActivate

AngularFire nos proporciona un canActivateasistente, que podemos usar junto con el operador de propagación, para hacer que nuestras rutas sean más legibles:

import { RouterModule, Routes } from '@angular/router';
import {
  canActivate,
  redirectLoggedInTo,
  redirectUnauthorizedTo,
} from '@angular/fire/auth-guard';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);
const redirectLoggedInToHome = () => redirectLoggedInTo(['dashboard']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
    ...canActivate(redirectLoggedInToHome),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    ...canActivate(redirectUnauthorizedToLogin),
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Usando el ayudante canActivate para hacer las rutas más legibles

 

Conclusión

¡Eso es todo amigos! Enhorabuena, ha creado correctamente una aplicación de autenticación con Angular 13 y el último SDK modular de AngularFire 7.

Recuerde que la nueva API no está completa, por lo que si decide seguir adelante de todos modos y usarla en producción, hágalo bajo su propio riesgo. Además, tenga en cuenta que AngularFire 7 proporciona una capa de compatibilidad para que pueda continuar usando la API anterior de AngularFire 6.

Espero que hayas encontrado útil este tutorial. Muchas gracias por leer.

Esta historia se publicó originalmente en https://betterprogramming.pub/angular-13-firebase-authentication-tutorial-with-angularfire-7-23dc8cee42c4

#angular #firebase #authentic 

What is GEEK

Buddha Community

Angular 13 Firebase Authentication Tutorial With AngularFire 7

Salman Ahmad

1588747755

Deploy Angular 10/9 App to Production with Firebase Hosting

In this tutorial, I am going to share with you How to Deploy Angular 10/9 App to Production with Firebase Hosting?.

#angular #firebase #popular tutorials #angualr firebase deploy #angular app #angular tutorial #firebase tutorial

Angular 13 Firebase Authentication Tutorial With AngularFire 7

Authentication is key to protecting our apps from unregistered users. In this tutorial, you will learn how to implement both a traditional email/password login and a Google social login in Firebase, with the new tree-shakable AngularFire v.7.0 modular SDK, which allows us to take full advantage of the new tree-shakable Firebase JS SDK (v9).

You’ll also learn how to protect routes with the AngularFire Auth Guard, and how to redirect users to certain pages based on whether they’re authenticated or not.

Please note that, at the time of writing this tutorial, the Angular fire v.7.0 API is still in development, and isn’t feature complete; and the docs aren’t yet updated to the latest version. AngularFire provides a compatibility layer that will allow you to use the latest version of the library, while fully supporting the AngularFire v6.0 API. This said, if you’re as curious as I am, and enjoy trying out the latest versions of all your libraries, go ahead and enjoy this tutorial.

Here’s a preview of the app we’ll be building today:

Preview of the authentication app

Index

  • Setting up a firebase project.
  • Setting up an Angular project with AngularFire.
  • Building the App.
  • Creating the dashboard.
  • Creating the auth module.
  • Adding Google sign-in.
  • Protecting the routes.

 

Setting up Firebase project

To create a new Firebase project, we need to log in to the Firebase console. Once we’ve done that, we can click on Add Project to create a new project:

Firebase — Creating a new project

We’ll need to choose a name for our project:

Firebase — Choosing a name

Decide whether or not we want Google Analytics in our project (your choice, I’ll go with no):

Firebase — Enabling Google Analytics

We’re done! We now have a lovely new Firebase project.

Adding Firebase to a Web app

Now we need to add Firebase to our Angular app. To do so, we need to click on the Web app button, in the dashboard:

Firebase — Adding a web app

We then need to choose a name:

Finally, we now have the configuration that will be used later on to add Firebase to our Angular application:

Adding Authentication

It’s time to add authentication and choose the sign-in methods that we want to implement. In this tutorial, we’ll add a Google social login, and a traditional email/password login. Head over to the Authentication page, and choose the providers:

Firebase — Choosing sign-in methods

Once we’ve enabled both sign-in methods, our Authentication dashboard should look like this:

Firebase — Enabled sign-in methods

That’s all! We now have our Firebase app fully set up and ready. It’s time to get started on our Angular app.

 

Setting up an Angular project

The first thing we’re going to do is create a new project with the Angular CLI.

Tip: If you haven’t installed the Angular CLI, you can do so by running the following command:

npm i -g @angular/cli

To create a new Angular project, we can run the following command:

ng new ngbytes-fireauth

Note: Don’t forget to answer yes when you’re asked if you want to add routing to your new app!

Once the CLI has worked its magic, we can open the newly created project with our favorite IDE (I suggest VSCode, which is the one I normally use).

Adding Firebase and AngularFire

Let’s add Firebase and Angularfire to our project. To do so, we’ll use the AngularFire schematic, that will take care of setting everything up for us. Let’s run the following command:

ng add @angular/fire

We’ll be asked a series of questions, like which Firebase features we’d like to setup. For this tutorial, we only need to add authentication, so let’s select that:

We’ll then be asked about the Firebase account we’d like to use, and which project we want to setup. Select the project we created previously, and then select the app we also created earlier.

Once we’ve done all this, you will see that the schematic has taken care of all the Firebase configuration for us. Awesome!

Adding Angular Material

We’ll also add Angular Material. Once again, we’ll use a schematic:

ng add @angular/material

Disabling strictPropertyInitialization

We’ll need to set the strictPropertyInitialization property in the tsconfig.json file to false. We’re doing this because the strict mode is enabled by default in all new Angular apps starting from version 12, which means that TypeScript will complain if we declare any class properties without setting them in the constructor (a common practice in Angular). Here’s what our tsconfig.json file should look like:

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "strictPropertyInitialization": false,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "module": "es2020",
    "lib": [
      "es2018",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

tsconfig.json file

Deleting the Angular boilerplate

Last, but not least, we’ll delete the boilerplate code that Angular automatically generates in the app.component.html. Be very careful and make sure that you do not delete the <router-outlet></router-outlet> tags when you delete the boilerplate code, or the router won’t work.

After deleting everything, your app.component.html should only contain the router-outlet tags:

router-outlet tags in the app.component.html

 

Creating the dashboard

It’s time to start building our Angular app. Let’s begin by creating a dashboard module. This is the module that will contain the pages which we want to protect from unauthenticated users. Let’s begin by creating a features directory inside the src/app directory, which will contain all of our feature modules:

mkdir features

Inside this new directory, we’ll create our dashboard module with the Angular CLI:

ng g m dashboard -m app --route dashboard

Tip: We’re using the --route option, which creates a component in the new module, and adds the route to that component in the Routes array declared in the module provided in the -m option.

You’ll see that the CLI has created a dashboard module inside the features directory, with its corresponding dashboard-routing.module.ts and component. It’s also modified the app-routing.module.ts, and added a dashboard route, lazy loading the DashboardModule.

Let’s add some information to our the dashboard component, by modifying the dashboard.component.html file:

<mat-toolbar></mat-toolbar>
<div class="background">
  <h1>Welcome to the NgBytes Dashboard</h1>
  <h2>Built with &lt;3 by Dottech</h2>

  <a target="_blank" class="btn" href="https://github.com/puntotech/ngbytes-fireauth">
    Click here to view the source code
  </a>
</div>

The dashboard component template

Feel free to add whatever you want to this file.

Tip: Don’t forget to import the MatToolbarModule to the DashboardModule, if you decide to use it too.

Let’s add a little CSS, to make the dashboard look pretty:

h1 {
  color: #5eccd6;
  font-size: 50px;
  font-weight: bolder;
}

h2 {
  color: #698893;
  margin-bottom: 50px;
}

a {
  color: #5eccd6;
  border-bottom: 2px solid transparent;
  transition: 0.5s border-bottom;
}

mat-toolbar {
  background-color: transparent;
  display: flex;
  justify-content: right;
}

.background {
  align-items: center;
  background-image: url("/assets/mountains.png");
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 64px);
  padding-top: 10%;
}

.btn {
  background-color: #5eccd6;
  border-radius: 20px;
  color: #fff;
  padding: 10px 30px;
  text-decoration: none;
  transition: 0.5s background-color;
}

a:hover {
  border-bottom: 2px solid #5eccd6;
  cursor: pointer;
}

.btn:hover {
  background-color: #52b7c0;
}

The dashboard component CSS

Et voilà, our dashboard module is ready! To see it, we can navigate to http://localhost:4200/dashboard:

Dashboard preview

It’s time to get started on our authentication.

 

Creating the Auth Module

Inside the features directory, we’ll use the Angular CLI to create an auth module:

ng g m auth --routing

Tip: We’re using the --routing flag, which generates an auth-routing.module.ts as well as the auth.module.ts.

Once we’ve created our module, we need to add its corresponding route to the app-routing.module.ts file.

import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Adding routes the app-routing module

Note that we’re adding a wildcard route, that redirects to the default route. We do this so that if we try to navigate to any routes that don’t exist, instead of an error being thrown, we’ll be redirected.

Creating an AuthService

All of our interactions with Firebase and AngularFire will take place within the AuthService. To create our service, we’ll once again make use of the Angular CLI. Inside our app directory:

mkdir core
cd core
mkdir services
cd services
ng g s auth

Inside our authService, the first thing we’ll need to do is inject the AngularFire Auth instance in the constructor. Then, we’ll create a login method, which will receive the login data (email and password), and use the signInWithEmailAndPassword AngularFire method to sign in:


import { Auth, signInWithEmailAndPassword } from '@angular/fire/auth';

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: any) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }
}

AuthService with untyped login method

However, doesn’t something about our login function seem a little off? We have a very unseemly any! Since we’re using TypeScript, and we want to have everything nicely typed, let’s create a LoginData interface, to type our login data. In our core directory:

mkdir interfaces
cd interfaces
touch login-data.interface.ts

Here’s what out interface will look like:

export interface LoginData {
  email: string;
  password: string;
}

The LoginData interface https://gist.github.com/NyaGarcia/2ec63c5fe25c1d281fcdb011722e9712

Now we can use our new interface to properly type our login function’s parameters:

import { Auth, signInWithEmailAndPassword } from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }
}

AuthService with typed login function

Looking much better, isn’t it?

We’ll also create a register method, which will use the AngularFire createUserWithEmailAndPassword method, so that new users can be registered in our app. We’ll also create a logout method, which, as you can probably guess, will allow our users to log out of our application:

import {
  Auth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
} from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }

  register({ email, password }: LoginData) {
    return createUserWithEmailAndPassword(this.auth, email, password);
  }

  logout() {
    return signOut(this.auth);
  }
}

Adding the register and logout functions to the AuthService

That’s all we have to do in our AuthService (we’ll be adding the Google social login option later on) for now.

It’s now time to create the register and login forms, so that our users can be authenticated.

Creating the login page

Inside the auth directory, we’ll create a login-page component, which will display a welcome message, the login form so that users can sign in, and later on, the google login button:

mkdir pages
cd pages
ng g c login-page

Once we’ve created our page, let’s head over to the login-page.component.html file and add a simple title:

<h1>Welcome to NgBytes Login</h1>

Since pages are routed components, we need to add a route to our auth-routing.module.ts file that points to the LoginPageComponent, like so:

import { RouterModule, Routes } from '@angular/router';

import { LoginPageComponent } from './pages/login-page/login-page.component';
import { NgModule } from '@angular/core';

const routes: Routes = [{ path: '', component: LoginPageComponent }];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class AuthRoutingModule {}

Routing the LoginPageComponent in the AuthRoutingModule

Let’s run the npm start script, and check that everything is routed correctly. If you open up your browser to localhost:4200, you should be seeing this:

LoginPageComponent preview

Awesome! It’s time to create a login form, so that our users can sign into our app:

Creating the login form

Now we’ll create a login-form component, inside our auth directory:

mkdir components
cd components
ng g c login-form

Tip: Since we won’t be writing any tests in this tutorial, feel free to delete the login-form.component.spec.ts file.

To create our form, we’ll be using the Reactive Forms module. We’ll also be using the Angular Material modules, to style our form. Since it isn’t the goal of this tutorial, I won’t go into any details about how to use reactive forms.

First, we’ll import the ReactiveFormsModule, the MatInputModule, the MatFormFieldModule and the MatButtonModule modules into our AuthModule:

import { AuthRoutingModule } from './auth-routing.module';
import { CommonModule } from '@angular/common';
import { LoginFormComponent } from './components/login-form/login-form.component';
import { LoginPageComponent } from './pages/login-page/login-page.component';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [LoginPageComponent, LoginFormComponent],
  imports: [
    CommonModule,
    AuthRoutingModule,
    ReactiveFormsModule,
    MatInputModule,
    MatFormFieldModule,
    MatButtonModule,
  ],
})
export class AuthModule {}

Importing modules into the AuthModule

Then, we’ll create the form in our login-form.component.ts file:

import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'ngbytes-login-form',
  templateUrl: './login-form.component.html',
  styleUrls: ['./login-form.component.css'],
})
export class LoginFormComponent implements OnInit {
  @Output() formData: EventEmitter<{
    email: string;
    password: string;
  }> = new EventEmitter();

  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.form = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required],
    });
  }

  get email() {
    return this.form.get('email');
  }

  get password() {
    return this.form.get('password');
  }

  onSubmit() {
    this.formData.emit(this.form.value);
  }
}

The LoginFormComponent logic

As you can see, we’ve added basic validation to our form: both the email and password fields are required, and the email field only allows valid email addresses.

Tip: Find it odd that we’re using an EventEmitter to emit our form’s data upon submission? This is because we’re using the smart/dumb component pattern, which decouples business logic from purely presentational components.

Lastly, in our login-form.component.html file:

<form class="form" [formGroup]="form" (ngSubmit)="onSubmit()">
  <mat-form-field class="form-control">
    <mat-label>Email</mat-label>
    <input matInput formControlName="email" type="text" required />
    <mat-error *ngIf="email?.hasError('required')">
      Email is required
    </mat-error>
    <mat-error *ngIf="email?.hasError('email')">
      Email isn't valid. Please enter a valid email.
    </mat-error>
  </mat-form-field>
  <mat-form-field class="form-control">
    <mat-label>Password</mat-label>
    <input matInput formControlName="password" type="password" required />
    <mat-error *ngIf="password?.hasError('required')">
      Password is required
    </mat-error>
  </mat-form-field>
  <div class="form-footer">
    <button [disabled]="form.invalid" mat-raised-button type="submit">
      Submit
    </button>
  </div>
</form>

The LoginFormComponent template

Note: See how we’re disabling the submit button if the form isn’t valid? That’s a bad practice (poor UX), and the only reason I’m doing it is for the sake of brevity, since this tutorial should be focused on AngularFire and not on best practices in Angular forms. However, be warned, don’t do this at home folks!

Let’s add a little CSS in our login-form.component.css file to make the form prettier (you can skip this step if you wish):

button {
  background-color: #5eccd6;
  border-radius: 20px;
  color: #fff;
  padding: 0 50px;
}

form {
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  height: 200px;
  width: 300px;
}

mat-form-field {
  width: 100%;
}

The LoginFormComponent CSS

Great! We now have a lovely login form. All that’s left is to add it to to the login-page.component.html template:


<div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <h5>Sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
</div>

Adding the login form to the login page

We also need to inject the authService we created earlier into our login-page.component.ts file and create a login method, which will use the authService to log the user in, redirecting to the dashboard once it’s done. Don’t forget to catch any possible errors!

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.component.html',
  styleUrls: ['./login-page.component.css'],
})
export class LoginPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  login(loginData: LoginData) {
    this.authService
      .login(loginData)
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }
}

Creating a login method in the login-page.component

Let’s test what we’ve done so far by going over to our browser, and filling in the form. As you can see, if we try to leave the email field blank, or to enter an invalid email, an error will be shown, which means our validation is working splendidly. However, if we try to press the submit button…

We’re getting a User not found error in our console. Well, of course we are. We haven’t registered any users yet, have we? Let’s create a register form so that we can do so!

Creating a register page

Just like we did for our login page, we’re going to create a register page, which will contain the register form. Inside the auth/pages directory:

ng g c register-page

Let’s add a title to our register page, and some styles:

<div class="container">
  <h1>Welcome to NgBytes Register</h1>
</div>

The RegisterPageComponent template

h1 {
  color: #838383;
  margin-bottom: 50px;
}

.container {
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 80px;
}

The RegisterPageComponent CSS https://gist.github.com/NyaGarcia/f6f3f5892f711fa31fa4acac1c886968

Last, but not least, we need to add a register route to the auth-routing.module.ts file:


import { RouterModule, Routes } from '@angular/router';

import { LoginPageComponent } from './pages/login-page/login-page.component';
import { NgModule } from '@angular/core';
import { RegisterPageComponent } from './pages/register-page/register-page.component';

const routes: Routes = [
  { path: '', component: LoginPageComponent },
  { path: 'register', component: RegisterPageComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class AuthRoutingModule {}

Adding the RegisterPageComponent route

Since our register page is done, all that we need to do is create a register form… Or do we?

Our login form has two fields, an email and a password field. Our register form needs to have two fields, an email and a password field. See where I’m getting at? Our two forms are going to be exactly the same! You may be thinking that no, they aren’t the same. One form is used to log in, the other to register a new user; two completely different pieces of logic.

However…

Remember how we used the smart/dumb component pattern to decouple the login form, from the login page? Our login form isn’t in charge of performing any logic at all, our login page is. Which means we are free to reuse the login form in our register page:

<div class="container">
  <h1>Welcome to NgBytes Register</h1>
  <ngbytes-login-form (formData)="register($event)"></ngbytes-login-form>
</div>

Adding the login form to the register page

Neat, isn’t it? Now comes the time to implement the logic to register a new user. We will do so, of course, in our register-page.component.ts file:

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-register-page',
  templateUrl: './register-page.component.html',
  styleUrls: ['./register-page.component.css'],
})
export class RegisterPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  register(data: LoginData) {
    this.authService
      .register(data)
      .then(() => this.router.navigate(['/login']))
      .catch((e) => console.log(e.message));
  }
}

Adding the register logic to the RegisterPageComponent

As you can see, we’re calling the authService.register method, which will register the new user in our Firebase app, and then we’re navigating to the login page, so that our newly registered user can log into our app.

Let’s add some final touches to the login page: a button that will redirect new users to the register page, and change the layout a bit to make it look decent:

<div class="background">
  <div class="login-container">
    <div class="text-container">
      <h1>Hey Stranger</h1>
      <p>
        Want to create an awesome account to access this awesome app? Go ahead
        and click the button below!
      </p>
      <button mat-button routerLink="/register">Sign up</button>
    </div>
    <div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <h5>Sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
    </div>
  </div>
</div>

Improving the login page

Some CSS magic:

a {
  color: #838383;
  margin-top: 30px;
  text-decoration: none;
}

h1 {
  color: #5eccd6;
  font-weight: bolder;
  margin-bottom: 20px;
}

h5 {
  color: #838383;
}

button {
  border-radius: 20px;
  padding: 0 50px;
}

p {
  margin-bottom: 20px;
  text-align: center;
}

.background {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: center;
}

.login-container {
  border-radius: 10px;
  box-shadow: 0 0 20px 2px #e4e4e4;
  display: flex;
  width: 60%;
}

.text-container {
  align-items: center;
  background-color: #5eccd6;
  border-radius: 10px 0 0 10px;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 50px;
  width: 30%;
}

.form-container {
  align-items: center;
  background-color: #fff;
  border-radius: 0 10px 10px 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 80px;
  width: 70%;
}

.text-container h1 {
  color: #fff;
}

.text-container button {
  background-color: transparent;
  border: 1px solid #fff;
}

Adding styles to the login page

Since we’ve made our login page look pretty, let’s take a moment and do the same for our register page:

<div class="background">
  <div class="login-container">
    <div class="form-container">
      <h1>Welcome to NgBytes Register</h1>
      <h5>Register with your email</h5>
      <ngbytes-login-form (formData)="register($event)"></ngbytes-login-form>
    </div>
    <div class="text-container">
      <h1>Hey Buddy</h1>
      <p>
        Already have an awesome account? Go ahead and click the button below to
        sign in!
      </p>
      <button mat-button routerLink="/">Sign in</button>
    </div>
  </div>
</div>

Improving the register page

Some more CSS:

a {
  color: #838383;
  margin-top: 30px;
  text-decoration: none;
}

h1 {
  color: #5eccd6;
  font-weight: bolder;
  margin-bottom: 20px;
}

h5 {
  color: #838383;
}

button {
  border-radius: 20px;
  padding: 0 50px;
}

p {
  margin-bottom: 20px;
  text-align: center;
}

.background {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: center;
}

.login-container {
  border-radius: 10px;
  box-shadow: 0 0 20px 2px #e4e4e4;
  display: flex;
  width: 60%;
}

.text-container {
  align-items: center;
  background-color: #5eccd6;
  border-radius: 0 10px 10px 0;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 50px;
  width: 30%;
}

.form-container {
  align-items: center;
  background-color: #fff;
  border-radius: 10px 0 0 10px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 80px;
  width: 70%;
}

.text-container h1 {
  color: #fff;
}

.text-container button {
  background-color: transparent;
  border: 1px solid #fff;
}

Adding styles to the register page

It’s time to test what we’ve done so far. Head over to your browser:

Registering a new user and logging in

Awesome! We’re logging into our app! However, we’re still missing something, aren’t we? We need a logout option! Let’s add it to our dashboard.

Adding a logout option

Let’s head over to our dashboard.component.html and create a logout option:

<mat-toolbar><a (click)="logout()">Log out</a></mat-toolbar>
<div class="background">
  <h1>Welcome to the NgBytes Dashboard</h1>
  <h2>Built with &lt;3 by Dottech</h2>

  <a
    target="_blank"
    class="btn"
    href="https://github.com/puntotech/ngbytes-fireauth"
  >
    Click here to view the source code
  </a>
</div>

Adding a logout option to the dashboard

Now that we have our logout option, we need to add some logic to our dashboard.component.ts. First, we’ll inject the AuthServicein the constructor, and then we’ll create a logout method:

import { Component, OnInit } from '@angular/core';

import { AuthService } from '../../core/services/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css'],
})
export class DashboardComponent implements OnInit {
  constructor(private authService: AuthService, private router: Router) {}

  ngOnInit(): void {}

  logout() {
    this.authService
      .logout()
      .then(() => this.router.navigate(['/']))
      .catch((e) => console.log(e.message));
  }
}

Adding a logout method to the dashboard component

As you can see, once we call the logout method, we’ll redirect the user to the default route. That’s it! We’ve successfully implemented a way for our users to log out of our app. Once again, let’s test our app:

Logging out

 

Adding Google Sign In

Now that our traditional login has been fully implemented, it’s time to add our Google social login. To do so, we’ll first create the necessary method in the auth.service:

import {
  Auth,
  GoogleAuthProvider,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signInWithPopup,
  signOut,
} from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }

  loginWithGoogle() {
    return signInWithPopup(this.auth, new GoogleAuthProvider());
  }

  register({ email, password }: LoginData) {
    return createUserWithEmailAndPassword(this.auth, email, password);
  }

  logout() {
    return signOut(this.auth);
  }
}

Adding a loginWithGoogle method to the AuthService

Then, we’ll need to add a Google sign in button to the login page:

<div class="background">
  <div class="login-container">
    <div class="text-container">
      <h1>Hey Stranger</h1>
      <p>
        Want to create an awesome account to access this awesome app? Go ahead
        and click the button below!
      </p>
      <button mat-button routerLink="/register">Sign up</button>
    </div>
    <div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <button class="mat-raised-button" (click)="loginWithGoogle()">
        Sign In with Google
      </button>
      <h5>Or sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
    </div>
  </div>
</div>

Adding a Google sign in button

Finally, we’ll need to add a loginWithGoogle function to our login-page.component.ts:

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.component.html',
  styleUrls: ['./login-page.component.css'],
})
export class LoginPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  login(loginData: LoginData) {
    this.authService
      .login(loginData)
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }

  loginWithGoogle() {
    this.authService
      .loginWithGoogle()
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }
}

Adding a loginWithGoogle method to the LoginPageComponent

We’re done! Let’s head over to our browser and try it out:

Signing in with google

 

Protecting the routes

If you attempt to navigate to the dashboard page localhost:4200/dashboard, you’ll see that you’ll be able to do so, even if you’re unauthenticated. To solve this problem, we’ll use the auth guard provided by AngularFire, to automatically redirect unauthorized users to the login page. In our app-routing.module.ts:

import { AuthGuard, redirectUnauthorizedTo } from '@angular/fire/auth-guard';
import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin },
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Protecting the routes

As you can see, we’ve protected the dashboard route with the AngularFire AuthGuard, and we’ve built a redirectUnauthorizedToLogin pipe, using the AngularFire redirectUnauthorizedTo pipe, which we then provide to the AuthGuard. This will ensure that any unauthenticated user that tries to access the dashboard page, will automatically be redirected to the login page. Feel free to head over to your browser and try it out.

The same way that we don’t want unauthorized users to access the dashboard, if the user is already authenticated, they shouldn’t be able to navigate to the login page. Let’s create another custom pipe, one that will redirect the already logged in users to the dashboard:

import {
  AuthGuard,
  redirectLoggedInTo,
  redirectUnauthorizedTo,
} from '@angular/fire/auth-guard';
import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);
const redirectLoggedInToHome = () => redirectLoggedInTo(['dashboard']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectLoggedInToHome },
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin },
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Adding the redirectLoggedInToHome route guard

Now, if we’re logged in and we try to access the login page, we’ll be automatically redirected to the dashboard.

The canActivate helper

AngularFire provides us with a canActivate helper, which we can use together with the spread operator, to make our routes more readable:

import { RouterModule, Routes } from '@angular/router';
import {
  canActivate,
  redirectLoggedInTo,
  redirectUnauthorizedTo,
} from '@angular/fire/auth-guard';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);
const redirectLoggedInToHome = () => redirectLoggedInTo(['dashboard']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
    ...canActivate(redirectLoggedInToHome),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    ...canActivate(redirectUnauthorizedToLogin),
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Using the canActivate helper to make the routes more readable

 

Conclusion

That’s all folks! Congratulations, you’ve successfully created an authentication application with Angular 13 and the latest AngularFire 7 modular SDK.

Please remember that the new API isn’t complete, so if you decide to go ahead anyway and use it in production, do so at your own risk. Also, bear in mind that AngularFire 7 provides a compatibility layer so that you can continue using the previous AngularFire 6 API.

I hope you found this tutorial useful. Thank you very much for reading.

This story was originally published at https://betterprogramming.pub/angular-13-firebase-authentication-tutorial-with-angularfire-7-23dc8cee42c4

#angular #firebase #authentic 

Angular 13 Firebase Authentication Tutorial With AngularFire 7

La autenticación es clave para proteger nuestras aplicaciones de usuarios no registrados. En este tutorial, aprenderá a implementar tanto un inicio de sesión de correo electrónico/contraseña tradicional como un inicio de sesión social de Google en Firebase, con el nuevo SDK modular AngularFire v.7.0 que se puede sacudir en árbol, lo que nos permite aprovechar al máximo el nuevo árbol. SDK de Firebase JS movible (v9) .

También aprenderá cómo proteger las rutas con AngularFire Auth Guard y cómo redirigir a los usuarios a ciertas páginas en función de si están autenticados o no.

Tenga en cuenta que, al momento de escribir este tutorial, la API de Angular Fire v.7.0 todavía está en desarrollo y no tiene la función completa ; y los documentos aún no están actualizados a la última versión. AngularFire proporciona una capa de compatibilidad que le permitirá utilizar la última versión de la biblioteca, al mismo tiempo que es totalmente compatible con la API de AngularFire v6.0. Dicho esto, si tienes tanta curiosidad como yo y disfrutas probando las últimas versiones de todas tus bibliotecas, sigue adelante y disfruta de este tutorial.

Aquí hay una vista previa de la aplicación que construiremos hoy:

Vista previa de la aplicación de autenticación

Índice

  • Configuración de un proyecto de base de fuego.
  • Configuración de un proyecto Angular con AngularFire.
  • Construcción de la aplicación.
  • Creación del tablero.
  • Creación del módulo de autenticación.
  • Adición de inicio de sesión de Google.
  • Protección de las rutas.

 

Configuración del proyecto de Firebase

Para crear un nuevo proyecto de Firebase, debemos iniciar sesión en la consola de Firebase. Una vez que hayamos hecho eso, podemos hacer clic en Agregar proyecto para crear un nuevo proyecto:

Firebase — Creando un nuevo proyecto

Tendremos que elegir un nombre para nuestro proyecto:

Firebase: elegir un nombre

Decida si queremos o no Google Analytics en nuestro proyecto (usted elige, me quedo con no):

Firebase: habilitar Google Analytics

¡Hemos terminado! Ahora tenemos un hermoso proyecto nuevo de Firebase.

Agregar Firebase a una aplicación web

Ahora necesitamos agregar Firebase a nuestra aplicación Angular. Para hacerlo, debemos hacer clic en el botón de la aplicación web, en el tablero:

Firebase — Agregar una aplicación web

Luego tenemos que elegir un nombre:

Finalmente, ahora tenemos la configuración que se usará más adelante para agregar Firebase a nuestra aplicación Angular:

Agregar autenticación

Es hora de agregar autenticación y elegir los métodos de inicio de sesión que queremos implementar. En este tutorial, agregaremos un inicio de sesión social de Google y un inicio de sesión de correo electrónico/contraseña tradicional. Dirígete a la página de Autenticación y elige los proveedores:

Firebase: elegir métodos de inicio de sesión

Una vez que hayamos habilitado ambos métodos de inicio de sesión, nuestro panel de Autenticación debería verse así:

Firebase: métodos de inicio de sesión habilitados

¡Eso es todo! Ahora tenemos nuestra aplicación Firebase completamente configurada y lista. Es hora de comenzar con nuestra aplicación Angular.

 

Configuración de un proyecto angular

Lo primero que vamos a hacer es crear un nuevo proyecto con Angular CLI.

Sugerencia: si no ha instalado Angular CLI, puede hacerlo ejecutando el siguiente comando:

npm i -g @angular/cli

Para crear un nuevo proyecto Angular, podemos ejecutar el siguiente comando:

ng new ngbytes-fireauth

Nota: ¡No olvide responder sí cuando se le pregunte si desea agregar enrutamiento a su nueva aplicación!

Una vez que la CLI ha hecho su magia, podemos abrir el proyecto recién creado con nuestro IDE favorito (sugiero VSCode , que es el que normalmente uso).

Agregar Firebase y AngularFire

Agreguemos Firebase y Angularfire a nuestro proyecto. Para ello, utilizaremos el esquema de AngularFire, que se encargará de configurarlo todo por nosotros. Ejecutemos el siguiente comando:

ng add @angular/fire

Se nos hará una serie de preguntas, como qué funciones de Firebase nos gustaría configurar. Para este tutorial, solo necesitamos agregar autenticación, así que seleccionemos eso:

Luego se nos preguntará sobre la cuenta de Firebase que nos gustaría usar y qué proyecto queremos configurar. Seleccione el proyecto que creamos anteriormente y luego seleccione la aplicación que también creamos anteriormente.

Una vez que hayamos hecho todo esto, verá que el esquema se ha ocupado de toda la configuración de Firebase por nosotros. ¡Impresionante!

Adición de material angular

También agregaremos material angular. Una vez más, usaremos un esquema:

ng add @angular/material

Desactivación de la inicialización de propiedad estricta

Tendremos que establecer la strictPropertyInitializationpropiedad en el tsconfig.jsonarchivo en falso. Estamos haciendo esto porque el modo estricto está habilitado de manera predeterminada en todas las nuevas aplicaciones de Angular a partir de la versión 12, lo que significa que TypeScript se quejará si declaramos propiedades de clase sin configurarlas en el constructor (una práctica común en Angular). tsconfig.jsonAsí es como debería verse nuestro archivo:

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "strictPropertyInitialization": false,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "module": "es2020",
    "lib": [
      "es2018",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

archivo tsconfig.json

Eliminación del texto modelo de Angular

Por último, pero no menos importante, eliminaremos el código repetitivo que Angular genera automáticamente en el archivo app.component.html. Tenga mucho cuidado y asegúrese de no eliminar las <router-outlet></router-outlet>etiquetas cuando elimine el código repetitivo, o el enrutador no funcionará.

Después de eliminar todo, app.component.htmlsolo debe contener las etiquetas de salida del enrutador:

etiquetas de salida de enrutador en app.component.html

 

Creando el tablero

Es hora de comenzar a construir nuestra aplicación Angular. Comencemos por crear un dashboardmódulo. Este es el módulo que contendrá las páginas que queremos proteger de usuarios no autenticados. Comencemos por crear un featuresdirectorio dentro del src/appdirectorio, que contendrá todos nuestros módulos de características:

mkdir features

Dentro de este nuevo directorio, crearemos nuestro dashboardmódulo con Angular CLI:

ng g m dashboard -m app --route dashboard

Sugerencia: estamos usando la --routeopción, que crea un componente en el nuevo módulo y agrega la ruta a ese componente en la matriz declarada en el módulo provisto en la opción.Routes-m

Verás que la CLI ha creado un dashboardmódulo dentro del featuresdirectorio, con su dashboard-routing.module.tscomponente correspondiente. También modificó el app-routing.module.ts, y agregó una dashboardruta, lazy loading el DashboardModule.

Agreguemos información a nuestro dashboardcomponente, modificando el dashboard.component.htmlarchivo:

<mat-toolbar></mat-toolbar>
<div class="background">
  <h1>Welcome to the NgBytes Dashboard</h1>
  <h2>Built with &lt;3 by Dottech</h2>

  <a target="_blank" class="btn" href="https://github.com/puntotech/ngbytes-fireauth">
    Click here to view the source code
  </a>
</div>

La plantilla del componente del tablero

Siéntete libre de agregar lo que quieras a este archivo.

Sugerencia: no olvides importar el MatToolbarModuleal DashboardModule, si decides usarlo también.

Agreguemos un poco de CSS para que el tablero se vea bonito:

h1 {
  color: #5eccd6;
  font-size: 50px;
  font-weight: bolder;
}

h2 {
  color: #698893;
  margin-bottom: 50px;
}

a {
  color: #5eccd6;
  border-bottom: 2px solid transparent;
  transition: 0.5s border-bottom;
}

mat-toolbar {
  background-color: transparent;
  display: flex;
  justify-content: right;
}

.background {
  align-items: center;
  background-image: url("/assets/mountains.png");
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 64px);
  padding-top: 10%;
}

.btn {
  background-color: #5eccd6;
  border-radius: 20px;
  color: #fff;
  padding: 10px 30px;
  text-decoration: none;
  transition: 0.5s background-color;
}

a:hover {
  border-bottom: 2px solid #5eccd6;
  cursor: pointer;
}

.btn:hover {
  background-color: #52b7c0;
}

El CSS del componente del tablero

Et voilà, ¡nuestro dashboardmódulo está listo! Para verlo, podemos navegar a http://localhost:4200/dashboard:

Vista previa del panel

Es hora de comenzar con nuestra autenticación.

 

Creación del módulo de autenticación

Dentro del featuresdirectorio, usaremos Angular CLI para crear un authmódulo:

ng g m auth --routing

Sugerencia: estamos usando la --routingbandera, que genera tanto un auth-routing.module.tscomo elauth.module.ts .

Una vez que hemos creado nuestro módulo, debemos agregar su ruta correspondiente al app-routing.module.tsarchivo.

import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Agregar rutas al módulo de enrutamiento de aplicaciones

Tenga en cuenta que estamos agregando una ruta comodín, que redirige a la ruta predeterminada. Hacemos esto para que, si intentamos navegar a rutas que no existen, en lugar de que se arroje un error, seremos redirigidos.

Creación de un servicio de autenticación

Todas nuestras interacciones con Firebase y AngularFire se llevarán a cabo dentro del AuthService. Para crear nuestro servicio, volveremos a utilizar la CLI de Angular. Dentro de nuestro appdirectorio:

mkdir core
cd core
mkdir services
cd services
ng g s auth

Dentro de nuestro authService, lo primero que debemos hacer es inyectar la Authinstancia de AngularFire en el constructor. Luego, crearemos un loginmétodo, que recibirá los datos de inicio de sesión (correo electrónico y contraseña), y usaremos el signInWithEmailAndPasswordmétodo AngularFire para iniciar sesión:


import { Auth, signInWithEmailAndPassword } from '@angular/fire/auth';

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: any) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }
}

AuthService con método de inicio de sesión sin tipo

Sin embargo, ¿no parece algo extraño en nuestra loginfunción? Tenemos un muy indecoroso any! Como estamos usando TypeScript y queremos tener todo bien escrito, creemos una LoginDatainterfaz para escribir nuestros datos de inicio de sesión. En nuestro coredirectorio:

mkdir interfaces
cd interfaces
touch login-data.interface.ts

Así es como se verá nuestra interfaz:

export interface LoginData {
  email: string;
  password: string;
}

La interfaz LoginData https://gist.github.com/NyaGarcia/2ec63c5fe25c1d281fcdb011722e9712

Ahora podemos usar nuestra nueva interfaz para escribir correctamente loginlos parámetros de nuestra función:

import { Auth, signInWithEmailAndPassword } from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }
}

AuthService con función de inicio de sesión escrita

Se ve mucho mejor, ¿no?

También crearemos un método de registro, que usará el createUserWithEmailAndPasswordmétodo AngularFire, para que los nuevos usuarios puedan registrarse en nuestra aplicación. También crearemos un logoutmétodo que, como probablemente pueda adivinar, permitirá a nuestros usuarios cerrar sesión en nuestra aplicación:

import {
  Auth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
} from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }

  register({ email, password }: LoginData) {
    return createUserWithEmailAndPassword(this.auth, email, password);
  }

  logout() {
    return signOut(this.auth);
  }
}

Agregar las funciones de registro y cierre de sesión al AuthService

Eso es todo lo que tenemos que hacer en nuestro AuthService(agregaremos la opción de inicio de sesión social de Google más adelante) por ahora.

Ahora es el momento de crear los formularios de registro e inicio de sesión, para que nuestros usuarios puedan autenticarse.

Creación de la página de inicio de sesión

Dentro del authdirectorio, crearemos un login-pagecomponente, que mostrará un mensaje de bienvenida, el formulario de inicio de sesión para que los usuarios puedan iniciar sesión y, más adelante, el botón de inicio de sesión de Google:

mkdir pages
cd pages
ng g c login-page

Una vez que hayamos creado nuestra página, vayamos al login-page.component.htmlarchivo y agreguemos un título simple:

<h1>Welcome to NgBytes Login</h1>

Dado que las páginas son componentes enrutados, debemos agregar una ruta a nuestro auth-routing.module.tsarchivo que apunte al LoginPageComponent, así:

import { RouterModule, Routes } from '@angular/router';

import { LoginPageComponent } from './pages/login-page/login-page.component';
import { NgModule } from '@angular/core';

const routes: Routes = [{ path: '', component: LoginPageComponent }];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class AuthRoutingModule {}

Enrutamiento de LoginPageComponent en AuthRoutingModule

Ejecutemos el npm startscript y verifiquemos que todo esté enrutado correctamente. Si abre su navegador a localhost:4200, debería estar viendo esto:

Vista previa de LoginPageComponent

¡Impresionante! Es hora de crear un formulario de inicio de sesión, para que nuestros usuarios puedan iniciar sesión en nuestra aplicación:

Crear el formulario de inicio de sesión

Ahora crearemos un login-formcomponente, dentro de nuestro authdirectorio:

mkdir components
cd components
ng g c login-form

Sugerencia: dado que no escribiremos ninguna prueba en este tutorial, no dude en eliminar el login-form.component.spec.tsarchivo.

Para crear nuestro formulario, usaremos el módulo Formularios reactivos. También usaremos los módulos de material angular para diseñar nuestro formulario. Dado que no es el objetivo de este tutorial, no entraré en detalles sobre cómo usar formularios reactivos.

Primero, importaremos los módulos ReactiveFormsModule, the MatInputModule, the MatFormFieldModuley the en nuestro :MatButtonModuleAuthModule

import { AuthRoutingModule } from './auth-routing.module';
import { CommonModule } from '@angular/common';
import { LoginFormComponent } from './components/login-form/login-form.component';
import { LoginPageComponent } from './pages/login-page/login-page.component';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [LoginPageComponent, LoginFormComponent],
  imports: [
    CommonModule,
    AuthRoutingModule,
    ReactiveFormsModule,
    MatInputModule,
    MatFormFieldModule,
    MatButtonModule,
  ],
})
export class AuthModule {}

Importación de módulos en AuthModule

Luego, crearemos el formulario en nuestro login-form.component.tsarchivo:

import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'ngbytes-login-form',
  templateUrl: './login-form.component.html',
  styleUrls: ['./login-form.component.css'],
})
export class LoginFormComponent implements OnInit {
  @Output() formData: EventEmitter<{
    email: string;
    password: string;
  }> = new EventEmitter();

  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.form = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required],
    });
  }

  get email() {
    return this.form.get('email');
  }

  get password() {
    return this.form.get('password');
  }

  onSubmit() {
    this.formData.emit(this.form.value);
  }
}

La lógica de LoginFormComponent

Como puede ver, hemos agregado una validación básica a nuestro formulario: tanto el campo de correo electrónico como el de contraseña son obligatorios, y el campo de correo electrónico solo permite direcciones de correo electrónico válidas.

Sugerencia: ¿Le parece extraño que estemos usando un EventEmittercorreo electrónico para emitir los datos de nuestro formulario al enviarlo? Esto se debe a que estamos usando el patrón de componente inteligente/tonto, que desvincula la lógica comercial de los componentes puramente de presentación.

Por último, en nuestro login-form.component.htmlarchivo:

<form class="form" [formGroup]="form" (ngSubmit)="onSubmit()">
  <mat-form-field class="form-control">
    <mat-label>Email</mat-label>
    <input matInput formControlName="email" type="text" required />
    <mat-error *ngIf="email?.hasError('required')">
      Email is required
    </mat-error>
    <mat-error *ngIf="email?.hasError('email')">
      Email isn't valid. Please enter a valid email.
    </mat-error>
  </mat-form-field>
  <mat-form-field class="form-control">
    <mat-label>Password</mat-label>
    <input matInput formControlName="password" type="password" required />
    <mat-error *ngIf="password?.hasError('required')">
      Password is required
    </mat-error>
  </mat-form-field>
  <div class="form-footer">
    <button [disabled]="form.invalid" mat-raised-button type="submit">
      Submit
    </button>
  </div>
</form>

La plantilla LoginFormComponent

Nota: ¿Ves cómo estamos deshabilitando el botón de enviar si el formulario no es válido? Esa es una mala práctica (UX deficiente), y la única razón por la que lo hago es por brevedad, ya que este tutorial debe centrarse en AngularFire y no en las mejores prácticas en formularios Angular. Sin embargo, tenga cuidado, ¡no haga esto en casa, amigos!

Agreguemos un poco de CSS en nuestro login-form.component.cssarchivo para que el formulario sea más bonito (puede omitir este paso si lo desea):

button {
  background-color: #5eccd6;
  border-radius: 20px;
  color: #fff;
  padding: 0 50px;
}

form {
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  height: 200px;
  width: 300px;
}

mat-form-field {
  width: 100%;
}

El CSS del componente LoginForm

¡Excelente! Ahora tenemos un hermoso formulario de inicio de sesión. Todo lo que queda es agregarlo a la login-page.component.htmlplantilla:


<div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <h5>Sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
</div>

Adición del formulario de inicio de sesión a la página de inicio de sesión

También necesitamos inyectar lo authServiceque creamos anteriormente en nuestro login-page.component.tsarchivo y crear un loginmétodo, que usará authServicepara iniciar sesión en el usuario, redirigiéndolo al tablero una vez que haya terminado. ¡No olvides detectar cualquier posible error!

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.component.html',
  styleUrls: ['./login-page.component.css'],
})
export class LoginPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  login(loginData: LoginData) {
    this.authService
      .login(loginData)
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }
}

Creación de un método de inicio de sesión en login-page.component

Probemos lo que hemos hecho hasta ahora yendo a nuestro navegador y completando el formulario. Como puede ver, si intentamos dejar el campo de correo electrónico en blanco, o ingresar un correo electrónico no válido, se mostrará un error, lo que significa que nuestra validación está funcionando espléndidamente. Sin embargo, si intentamos presionar el botón de enviar…

Estamos recibiendo un error de Usuario no encontrado en nuestra consola. Bueno, por supuesto que lo somos. Todavía no hemos registrado ningún usuario, ¿verdad? ¡Creemos un formulario de registro para que podamos hacerlo!

Creación de una página de registro

Al igual que hicimos con nuestra página de inicio de sesión, vamos a crear una página de registro, que contendrá el formulario de registro. Dentro del auth/pagesdirectorio:

ng g c register-page

Agreguemos un título a nuestra página de registro y algunos estilos:

<div class="container">
  <h1>Welcome to NgBytes Register</h1>
</div>

La plantilla RegisterPageComponent

h1 {
  color: #838383;
  margin-bottom: 50px;
}

.container {
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 80px;
}

El CSS de RegisterPageComponent https://gist.github.com/NyaGarcia/f6f3f5892f711fa31fa4acac1c886968

Por último, pero no menos importante, debemos agregar una registerruta al auth-routing.module.tsarchivo:


import { RouterModule, Routes } from '@angular/router';

import { LoginPageComponent } from './pages/login-page/login-page.component';
import { NgModule } from '@angular/core';
import { RegisterPageComponent } from './pages/register-page/register-page.component';

const routes: Routes = [
  { path: '', component: LoginPageComponent },
  { path: 'register', component: RegisterPageComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class AuthRoutingModule {}

Agregar la ruta RegisterPageComponent

Ya que nuestra página de registro está lista, todo lo que tenemos que hacer es crear un formulario de registro... ¿o sí?

Nuestro formulario de inicio de sesión tiene dos campos, un correo electrónico y un campo de contraseña. Nuestro formulario de registro debe tener dos campos, un correo electrónico y un campo de contraseña. ¿Ves a dónde estoy llegando? ¡Nuestras dos formas van a ser exactamente iguales! Puede que estés pensando que no, que no son lo mismo. Un formulario se utiliza para iniciar sesión, el otro para registrar un nuevo usuario; dos piezas de lógica completamente diferentes.

Sin embargo…

¿Recuerda cómo usamos el patrón de componente inteligente/tonto para desacoplar el formulario de inicio de sesión de la página de inicio de sesión? Nuestro formulario de inicio de sesión no está a cargo de realizar ninguna lógica en absoluto, nuestra página de inicio de sesión sí lo está. Lo que significa que somos libres de reutilizar el formulario de inicio de sesión en nuestra página de registro:

<div class="container">
  <h1>Welcome to NgBytes Register</h1>
  <ngbytes-login-form (formData)="register($event)"></ngbytes-login-form>
</div>

Agregar el formulario de inicio de sesión a la página de registro

Genial, ¿no? Ahora llega el momento de implementar la lógica para dar de alta un nuevo usuario. Así lo haremos, por supuesto, en nuestra register-page.component.tsficha:

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-register-page',
  templateUrl: './register-page.component.html',
  styleUrls: ['./register-page.component.css'],
})
export class RegisterPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  register(data: LoginData) {
    this.authService
      .register(data)
      .then(() => this.router.navigate(['/login']))
      .catch((e) => console.log(e.message));
  }
}

Agregar la lógica de registro a RegisterPageComponent

Como puede ver, estamos llamando al authService.registermétodo, que registrará al nuevo usuario en nuestra aplicación Firebase, y luego estamos navegando a la página de inicio de sesión, para que nuestro usuario recién registrado pueda iniciar sesión en nuestra aplicación.

Agreguemos algunos toques finales a la página de inicio de sesión: un botón que redirigirá a los nuevos usuarios a la página de registro y cambiará un poco el diseño para que se vea decente:

<div class="background">
  <div class="login-container">
    <div class="text-container">
      <h1>Hey Stranger</h1>
      <p>
        Want to create an awesome account to access this awesome app? Go ahead
        and click the button below!
      </p>
      <button mat-button routerLink="/register">Sign up</button>
    </div>
    <div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <h5>Sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
    </div>
  </div>
</div>

Mejorando la página de inicio de sesión

Un poco de magia CSS:

a {
  color: #838383;
  margin-top: 30px;
  text-decoration: none;
}

h1 {
  color: #5eccd6;
  font-weight: bolder;
  margin-bottom: 20px;
}

h5 {
  color: #838383;
}

button {
  border-radius: 20px;
  padding: 0 50px;
}

p {
  margin-bottom: 20px;
  text-align: center;
}

.background {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: center;
}

.login-container {
  border-radius: 10px;
  box-shadow: 0 0 20px 2px #e4e4e4;
  display: flex;
  width: 60%;
}

.text-container {
  align-items: center;
  background-color: #5eccd6;
  border-radius: 10px 0 0 10px;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 50px;
  width: 30%;
}

.form-container {
  align-items: center;
  background-color: #fff;
  border-radius: 0 10px 10px 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 80px;
  width: 70%;
}

.text-container h1 {
  color: #fff;
}

.text-container button {
  background-color: transparent;
  border: 1px solid #fff;
}

Agregar estilos a la página de inicio de sesión

Ya que hemos hecho que nuestra página de inicio de sesión se vea bonita, tomemos un momento y hagamos lo mismo con nuestra página de registro:

<div class="background">
  <div class="login-container">
    <div class="form-container">
      <h1>Welcome to NgBytes Register</h1>
      <h5>Register with your email</h5>
      <ngbytes-login-form (formData)="register($event)"></ngbytes-login-form>
    </div>
    <div class="text-container">
      <h1>Hey Buddy</h1>
      <p>
        Already have an awesome account? Go ahead and click the button below to
        sign in!
      </p>
      <button mat-button routerLink="/">Sign in</button>
    </div>
  </div>
</div>

Mejorando la página de registro

Un poco más de CSS:

a {
  color: #838383;
  margin-top: 30px;
  text-decoration: none;
}

h1 {
  color: #5eccd6;
  font-weight: bolder;
  margin-bottom: 20px;
}

h5 {
  color: #838383;
}

button {
  border-radius: 20px;
  padding: 0 50px;
}

p {
  margin-bottom: 20px;
  text-align: center;
}

.background {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: center;
}

.login-container {
  border-radius: 10px;
  box-shadow: 0 0 20px 2px #e4e4e4;
  display: flex;
  width: 60%;
}

.text-container {
  align-items: center;
  background-color: #5eccd6;
  border-radius: 0 10px 10px 0;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 50px;
  width: 30%;
}

.form-container {
  align-items: center;
  background-color: #fff;
  border-radius: 10px 0 0 10px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 80px;
  width: 70%;
}

.text-container h1 {
  color: #fff;
}

.text-container button {
  background-color: transparent;
  border: 1px solid #fff;
}

Agregar estilos a la página de registro

Es hora de probar lo que hemos hecho hasta ahora. Dirígete a tu navegador:

Registro de un nuevo usuario e inicio de sesión

¡Impresionante! ¡Estamos iniciando sesión en nuestra aplicación! Sin embargo, todavía nos falta algo, ¿no? ¡Necesitamos una opción de cierre de sesión! Vamos a agregarlo a nuestro tablero.

Agregar una opción de cierre de sesión

Vayamos a nuestro dashboard.component.htmly creemos una opción de cierre de sesión:

<mat-toolbar><a (click)="logout()">Log out</a></mat-toolbar>
<div class="background">
  <h1>Welcome to the NgBytes Dashboard</h1>
  <h2>Built with &lt;3 by Dottech</h2>

  <a
    target="_blank"
    class="btn"
    href="https://github.com/puntotech/ngbytes-fireauth"
  >
    Click here to view the source code
  </a>
</div>

Agregar una opción de cierre de sesión al tablero

Ahora que tenemos nuestra opción de cierre de sesión, necesitamos agregar algo de lógica a nuestro archivo dashboard.component.ts. Primero, inyectaremos el AuthServiceen el constructor y luego crearemos un logoutmétodo:

import { Component, OnInit } from '@angular/core';

import { AuthService } from '../../core/services/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css'],
})
export class DashboardComponent implements OnInit {
  constructor(private authService: AuthService, private router: Router) {}

  ngOnInit(): void {}

  logout() {
    this.authService
      .logout()
      .then(() => this.router.navigate(['/']))
      .catch((e) => console.log(e.message));
  }
}

Agregar un método de cierre de sesión al componente del tablero

Como puede ver, una vez que llamemos al logoutmétodo, redirigiremos al usuario a la ruta predeterminada. ¡Eso es todo! Hemos implementado con éxito una forma para que nuestros usuarios cierren sesión en nuestra aplicación. Una vez más, probemos nuestra aplicación:

Saliendo de tu cuenta

 

Adición de inicio de sesión de Google

Ahora que nuestro inicio de sesión tradicional se ha implementado por completo, es hora de agregar nuestro inicio de sesión social de Google. Para hacerlo, primero crearemos el método necesario en el auth.service:

import {
  Auth,
  GoogleAuthProvider,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signInWithPopup,
  signOut,
} from '@angular/fire/auth';

import { Injectable } from '@angular/core';
import { LoginData } from '../interfaces/login-data.interface';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {}

  login({ email, password }: LoginData) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }

  loginWithGoogle() {
    return signInWithPopup(this.auth, new GoogleAuthProvider());
  }

  register({ email, password }: LoginData) {
    return createUserWithEmailAndPassword(this.auth, email, password);
  }

  logout() {
    return signOut(this.auth);
  }
}

Agregar un método loginWithGoogle al AuthService

Luego, necesitaremos agregar un botón de inicio de sesión de Google a la página de inicio de sesión:

<div class="background">
  <div class="login-container">
    <div class="text-container">
      <h1>Hey Stranger</h1>
      <p>
        Want to create an awesome account to access this awesome app? Go ahead
        and click the button below!
      </p>
      <button mat-button routerLink="/register">Sign up</button>
    </div>
    <div class="form-container">
      <h1>Welcome to NgBytes Login</h1>
      <button class="mat-raised-button" (click)="loginWithGoogle()">
        Sign In with Google
      </button>
      <h5>Or sign in with your email</h5>
      <ngbytes-login-form (formData)="login($event)"></ngbytes-login-form>
    </div>
  </div>
</div>

Agregar un botón de inicio de sesión de Google

Finalmente, necesitaremos agregar una loginWithGooglefunción a nuestro login-page.component.ts:

import { Component, OnInit } from '@angular/core';

import { AuthService } from 'src/app/core/services/auth.service';
import { LoginData } from 'src/app/core/interfaces/login-data.interface';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.component.html',
  styleUrls: ['./login-page.component.css'],
})
export class LoginPageComponent implements OnInit {
  constructor(
    private readonly authService: AuthService,
    private readonly router: Router
  ) {}

  ngOnInit(): void {}

  login(loginData: LoginData) {
    this.authService
      .login(loginData)
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }

  loginWithGoogle() {
    this.authService
      .loginWithGoogle()
      .then(() => this.router.navigate(['/dashboard']))
      .catch((e) => console.log(e.message));
  }
}

Agregar un método loginWithGoogle al LoginPageComponent

¡Hemos terminado! Vayamos a nuestro navegador y pruébelo:

Iniciar sesión con google

 

Protegiendo las rutas

Si intenta navegar a la página del panel localhost:4200/dashboard, verá que podrá hacerlo, incluso si no está autenticado. Para resolver este problema, utilizaremos la protección de autenticación proporcionada por AngularFire para redirigir automáticamente a los usuarios no autorizados a la página de inicio de sesión. En nuestro app-routing.module.ts:

import { AuthGuard, redirectUnauthorizedTo } from '@angular/fire/auth-guard';
import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin },
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Protegiendo las rutas

Como puede ver, hemos protegido la dashboardruta con AngularFire AuthGuardy hemos construido una redirectUnauthorizedToLogincanalización, utilizando la redirectUnauthorizedTocanalización AngularFire, que luego proporcionamos a AuthGuard. Esto garantizará que cualquier usuario no autenticado que intente acceder a la página del panel de control será redirigido automáticamente a la página de inicio de sesión. Siéntase libre de dirigirse a su navegador y probarlo.

De la misma manera que no queremos que los usuarios no autorizados accedan al panel, si el usuario ya está autenticado, no debería poder navegar a la página de inicio de sesión. Vamos a crear otra canalización personalizada, una que redirigirá a los usuarios que ya iniciaron sesión al tablero:

import {
  AuthGuard,
  redirectLoggedInTo,
  redirectUnauthorizedTo,
} from '@angular/fire/auth-guard';
import { RouterModule, Routes } from '@angular/router';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);
const redirectLoggedInToHome = () => redirectLoggedInTo(['dashboard']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectLoggedInToHome },
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin },
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Agregar el protector de ruta redirectLoggedInToHome

Ahora, si hemos iniciado sesión e intentamos acceder a la página de inicio de sesión, seremos redirigidos automáticamente al panel de control.

El ayudante canActivate

AngularFire nos proporciona un canActivateasistente, que podemos usar junto con el operador de propagación, para hacer que nuestras rutas sean más legibles:

import { RouterModule, Routes } from '@angular/router';
import {
  canActivate,
  redirectLoggedInTo,
  redirectUnauthorizedTo,
} from '@angular/fire/auth-guard';

import { NgModule } from '@angular/core';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['']);
const redirectLoggedInToHome = () => redirectLoggedInTo(['dashboard']);

const routes: Routes = [
  {
    path: '',
    loadChildren: () =>
      import('./features/auth/auth.module').then((m) => m.AuthModule),
    ...canActivate(redirectLoggedInToHome),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    ...canActivate(redirectUnauthorizedToLogin),
  },
  {
    path: '**',
    redirectTo: '',
    pathMatch: 'full',
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Usando el ayudante canActivate para hacer las rutas más legibles

 

Conclusión

¡Eso es todo amigos! Enhorabuena, ha creado correctamente una aplicación de autenticación con Angular 13 y el último SDK modular de AngularFire 7.

Recuerde que la nueva API no está completa, por lo que si decide seguir adelante de todos modos y usarla en producción, hágalo bajo su propio riesgo. Además, tenga en cuenta que AngularFire 7 proporciona una capa de compatibilidad para que pueda continuar usando la API anterior de AngularFire 6.

Espero que hayas encontrado útil este tutorial. Muchas gracias por leer.

Esta historia se publicó originalmente en https://betterprogramming.pub/angular-13-firebase-authentication-tutorial-with-angularfire-7-23dc8cee42c4

#angular #firebase #authentic 

How To Set Up Two-Factor Authentication in cPanel

What is 2FA
Two-Factor Authentication (or 2FA as it often referred to) is an extra layer of security that is used to provide users an additional level of protection when securing access to an account.
Employing a 2FA mechanism is a vast improvement in security over the Singe-Factor Authentication method of simply employing a username and password. Using this method, accounts that have 2FA enabled, require the user to enter a one-time passcode that is generated by an external application. The 2FA passcode (usually a six-digit number) is required to be input into the passcode field before access is granted. The 2FA input is usually required directly after the username and password are entered by the client.

#tutorials #2fa #access #account security #authentication #authentication method #authentication token #cli #command line #cpanel #feature manager #google authenticator #one time password #otp #otp authentication #passcode #password #passwords #qr code #security #security code #security policy #security practices #single factor authentication #time-based one-time password #totp #two factor authentication #whm

What is firebase,firebase bangla tutorial.

https://youtu.be/8BnVKuf1E5M

#firebase #firebase database #c# with firebase #c# with firebase tutorials #c# with firebase database #asp.net with firebase database