DEV Community 👩‍💻👨‍💻

Cover image for Autenticación con Firebase y React JS 🔑
Franklin Martinez
Franklin Martinez

Posted on

Autenticación con Firebase y React JS 🔑

En esta ocasión te mostrare como usar el servicio de Firebase para realizar una autenticación en tu aplicación de React JS.

Cualquier tipo de feedback es bienvenido, gracias y espero disfrutes el articulo.🤗

⚠️ Nota: Es necesario que cuentes con conocimientos básicos en React JS y hooks y TypeScript.

 

Tabla de contenido

📌 Tecnologías a utilizar.

📌 Creando el proyecto.

📌 Primeros pasos.

📌 Creando los diseños de autenticación.

📌 Diseño del inicio de sesión.

📌 Diseño del registro.

📌 Manejando los estados de los formularios.

📌 Configurar Firebase.

📌 Creando el proyecto.

📌 Creando la app.

📌 Configurando la autenticación.

📌 Configurando Firebase en nuestra aplicación de React.

📌 Creando las funciones para la autenticación.

📌 1 - Creando la función para autenticarse por Google.

📌 2 - Creando la función para autenticarse por credenciales.

📌 3 - Creando la función para observar los cambios en el estado de autenticación del usuario.

📌 4 - Creando la función para cerrar sesión.

📌 Creando un contexto de nuestra aplicación.

📌 Usando nuestro contexto.

📌 Conclusión.

📌 Código fuente.

 

🔥 Tecnologías a utilizar.

  • ▶️ React JS (version 18)
  • ▶️ Vite JS
  • ▶️ TypeScript
  • ▶️ Firebase (version 9.10)
  • ▶️ CSS vanilla (Los estilos los encuentras en el repositorio al final de este post)

🔥 Creando el proyecto.

Al proyecto le colocaremos el nombre de: auth-firebase-react (opcional, tu le puedes poner el nombre que gustes).

npm init vite@latest
Enter fullscreen mode Exit fullscreen mode

Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.

Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

cd auth-firebase-react
Enter fullscreen mode Exit fullscreen mode

Luego instalamos las dependencias.

npm install
Enter fullscreen mode Exit fullscreen mode

Después abrimos el proyecto en un editor de código (en mi caso VS code).

code .
Enter fullscreen mode Exit fullscreen mode

🔥 Primeros pasos.

Primero vamos al archivo src/App.tsx y borramos todo, vamos a crear un nuevo componente que muestre un simple titulo.

const App = () => {
  return (
    <main>
      <h1><b>Auth with</b> <span>Firebase</span> <b>and</b> <span>React</span></h1>
    </main>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Se vería asi, si es que usas los mismos estilos que este proyecto. 👀

title

🔥 Creando los diseños de autenticación.

En esta aplicación no manejaremos rutas, solamente nos enfocamos en la autenticación. Asi que los diseños de login y register pueden ponerlos en vistas separadas si gustan, en mi caso solo serán componentes.

Lo hago de esta manera para explicarlo de la manera más simple.

🔥 Diseño del inicio de sesión.

Creamos la carpeta src/components y dentro creamos el archivo Login.tsx

El inicio de sesión constara de 2 inputs:

  • Email.
  • Password.

Los input, deben de tener contener el atributo name identificando cada input.

También tendrá 2 botones:

  • Inicio de sesión normal.
  • Inicio de sesión con Google.

Cada botón debe tener su propiedad type esto es para que el botón de Google no haga el posteo del formulario, solamente el primer botón debe hacer eso.

export const Login = () => {

    return (
        <div className="container-auth">
            <h2>Login</h2>

            <form>
                <input
                    name="email"
                    type="email"
                    placeholder="E-mail"
                />
                <input
                    name="pass"
                    type="password"
                    placeholder="Password"
                />

                <div className="container-buttons">
                    <button type="submit">Log In</button>
                    <button type="button"> Google </button>
                </div>
            </form>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

🔥 Diseño del registro.

El diseño del registro sera igual al del inicio de sesión, con los mismos inputs pero solamente tiene un botón

export const Register = () => {

    return (
        <div className="container-auth">
            <h2>Create an account</h2>

            <form>
                <input
                    name="email"
                    type="email"
                    placeholder="E-mail"
                />
                <input
                    name="pass"
                    type="password"
                    placeholder="Password"
                />
                <div className="container-buttons">
                    <button type="submit">Sign up</button>
                </div>
            </form>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Ahora creamos un archivo barril, (index.ts) en la carpeta src/components para poder exportar nuestros componentes e importarlos en otro lugar de una manera mas ordenada.

export * from './Login'
export * from './Register'
Enter fullscreen mode Exit fullscreen mode

Una vez tengamos los dos diseños y el archivo barril, vamos a src/App.tsx y los agregamos:

import { Login, Register } from "./components"

const App = () => {

  return (
    <main>
      <h1><b>Auth with</b> <span>Firebase</span> <b>and</b> <span>React</span></h1>
      <section>
        <Login />
        <Register />
      </section>
    </main>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

El resultado del diseño seria el siguiente:
auth

🔥 Manejando los estados de los formularios.

Para manejar cada formulario, vamos a implementar un custom hook, lo llamaremos useForm.tsx y estará dentro de la carpeta src/hooks.

Creamos una función que recibe como parámetro un objeto que contenga el estado inicial del formulario y este tendrá un tipo genérico, esto para que sea un poco mas reutilizable (por si quieren agregar mas campos a los formularios) el hook aunque en este caso no es necesario ya que tenemos los mismos campos en ambos formularios

interface Props<T> {
    initialState: T
}

export const useForm = <T>({ initialState }: Props<T>) => {}
Enter fullscreen mode Exit fullscreen mode

Después, usaremos el estado para almacenar los valores del formulario. y con la función handleChange vamos a poder manejar los cambios del input y almacenar sus valores.

La función handleChange mandamos una propiedad computada a la función setForm y es por eso que es necesario el atributo name en el input, para identificarlo y obtener su valor.

Finalmente retornamos, mediante el operador rest esparcimos los valores del formulario, luego el formulario y finalmente la función handleChange.

import { useState } from 'react';

type eventInput = React.ChangeEvent<HTMLInputElement>;

interface Props<T> {
    initialState: T
}

export const useForm = <T>({ initialState }: Props<T>) => {

    const [form, setForm] = useState<T>(initialState)

    const handleChange = (e: eventInput) => {
        setForm(prev => ({
            ...prev,
            [e.target.name]: e.target.value
        }))
    }

    return { ...form, form, handleChange }
}
Enter fullscreen mode Exit fullscreen mode

Ahora usamos el hook en src/components/Login.tsx:

Llamamos al hook useForm le mandamos un objeto que sera el estado inicial del formulario, en este caso tendremos dos propiedades que es el email y pass, que hacen referencia a los inputs que tenemos en el HTML. Sus valores por defecto son string vacíos.

Desestructuramos las propiedades del formulario y la función handleChange.

const { handleChange, pass, email } = useForm({
    initialState: {
        email: '',
        pass: ''
    }
})
Enter fullscreen mode Exit fullscreen mode

En los inputs colocamos el atributo value con su correspondiente valor y el atributo onChange mandando la función handleChange para manejar el estado del input.

<input
    name="email"
    type="email"
    placeholder="E-mail"
    onChange={handleChange}
    value={email}
/>
<input
    name="pass"
    type="password"
    placeholder="Password"
    onChange={handleChange}
    value={pass}
/>
Enter fullscreen mode Exit fullscreen mode

Finalmente, haremos una función llamada handleSubmit que recibe el evento del formulario, y esta función por el momento solo previene el comportamiento del formulario por defecto.

La función handleSubmit se la pasamos al atributo onSubmit de la etiqueta form.

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
}
Enter fullscreen mode Exit fullscreen mode

Asi quedaría por el momento el Login.tsx

import { useForm } from '../hooks/useForm';

export const Login = () => {

    const { handleChange, pass, email } = useForm({
        initialState: {
            email: '',
            pass: ''
        }
    })

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
    }

    return (
        <div className="container-auth">
            <h2>Login</h2>

            <form onSubmit={handleSubmit}>
                <input
                    name="email"
                    type="email"
                    placeholder="E-mail"
                    onChange={handleChange}
                    value={email}
                />
                <input
                    name="pass"
                    type="password"
                    placeholder="Password"
                    onChange={handleChange}
                    value={pass}
                />

                <div className="container-buttons">
                    <button type="submit">Log In</button>
                    <button type="button"> Google </button>
                </div>
            </form>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Asi como hicimos el Login.tsx es exactamente igual en el archivo Register.tsx

import { useForm } from "../hooks/useForm"

export const Register = () => {

    const { handleChange, pass, email } = useForm({
        initialState: {
            email: '',
            pass: ''
        }
    })

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
    }

    return (
        <div className="container-auth">
            <h2>Create an account</h2>

            <form onSubmit={handleSubmit}>
                <input
                    name="email"
                    type="email"
                    placeholder="E-mail"
                    onChange={handleChange}
                    value={email}
                />
                <input
                    name="pass"
                    type="password"
                    placeholder="Password"
                    onChange={handleChange}
                    value={pass}
                />
                <div className="container-buttons">
                    <button type="submit">Sign up</button>
                </div>
            </form>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

⚠️ Nota: Incluso pueden crear un único componente reutilizable, ya que los formularios son casi exactamente iguales solo se diferencian por los botones y la acción de handleSubmit ya que esta función hará algo diferente dependiendo el formulario.

Ya tenemos el diseño y funcionalidad de los formularios, seguimos con la creación de nuestra app en Firebase.

🔥 Configurar Firebase.

Ahora nos toca configurar la aplicación en firebase para poder usar su servicio de autenticación.

🔥 Creando el proyecto.

1 - Vamos a la consola de firebase, iniciamos sesión con alguna cuenta de correo de Gmail.

2 - Si es la primera vez que usan Firebase, les aparecerá un mensaje y botón de crear proyecto el cual deben de presionar.

3 - Colocar el nuevo nombre al proyecto. En mi caso le puse auth-firebase-react.

Y al final hay un botón de continuar que debes presionar.

new name protect

4 - Esperar a que tu proyecto termine de crearse y después dar click en continuar

loading

Una vez en continuar te va a mandar a un nuevo panel.

🔥 Creando el la app.

1 - En el nuevo panel, tienes que identificar estos botones. Presiona el botón de web para crear una app (el tercer botón con fondo blanco).

buttons

2 - Coloca el nombre a tu app y dale click en Registrar app

new app

3 - Una vez registrada la app, nos darán nuestras credenciales, las cuales tenemos que guardar porque las vamos a usar después.

credentials

🔥 Configurando la autenticación.

1 - Ahora, tenemos que regresar al panel, en el menu del lado derecho hay una opción que dice compilación y dentro esta la opción de autenticación y tienes que darle click, para que te lleve a otra pantalla.

Y tienes que dar click al botón de Comenzar

auth page

2 - Después se te mostraran diversos proveedores, de los cuales vamos a seleccionar el de correo electrónico/contraseña y Google (puedes elegir los que quieras, aunque probablemente tendrás que hacer mas configuración).

  • Cuando elijas el de correo electrónico/contraseña solamente le das en Habilitar y al final viene un botón de guardar que debes presionar una vez que termines de hacer alguna modificación en el proveedor.

  • Cuando elijas el de Google harás lo mismo que el anterior, y también tendrás que seleccionar tu correo electrónico.

providers

3 - Una vez habilitados los proveedores, tendrá que aparecerte en la pestaña Sign-in method de la siguiente forma.

providers added

4 - En la pestaña Users puedes ver todos los usuarios que se registren en tu aplicación.

users

🔥 Configurando Firebase en nuestra aplicación de React.

De vuelta a nuestra app de React, vamos a instalar Firebase.

npm install firebase
Enter fullscreen mode Exit fullscreen mode

creamos una nueva carpeta src/firebase y dentro un archivo llamado config.ts y pegamos toda la configuración que nos dieron en la sección anterior

En mi caso, yo coloque los valores de cada propiedad en una variable de entorno, solamente creando en la raíz del proyecto un archivo .env.

Cada variable debe empezar con la palabra VITE_ para que funcionen.

VITE_APIKEY=1231231465
# more vars
Enter fullscreen mode Exit fullscreen mode

Y para llamar a una variable de entorno tenemos que user el import.meta.env['Nombre de la variable']

Nota: también debes notar que cambie el nombre de la variable app por FirebaseApp

import { initializeApp } from "firebase/app";

const firebaseConfig = {
    apiKey: import.meta.env.VITE_APIKEY,
    authDomain: import.meta.env.VITE_AUTHDOMAIN,
    projectId: import.meta.env.VITE_PROJECTID,
    storageBucket: import.meta.env.VITE_STORAGEBUCKET,
    messagingSenderId: import.meta.env.VITE_MESSAGINGSENDERID,
    appId: import.meta.env.VITE_APPID,
};

// Initialize Firebase
export const FirebaseApp = initializeApp(firebaseConfig)
Enter fullscreen mode Exit fullscreen mode

Ahora para utilizar el servicio de autenticación de Firebase, usamos el método getAuth y tenemos que obtenerlo de 'firebase/auth', después le mandamos la inicialización de nuestra app, o sea la constante FirebaseApp

import { getAuth } from 'firebase/auth'

export const FirebaseApp = initializeApp(firebaseConfig)
export const FirebaseAuth = getAuth(FirebaseApp)
Enter fullscreen mode Exit fullscreen mode

La configuración quedaría de esta manera 👀:

import { initializeApp } from "firebase/app";

const firebaseConfig = {
    apiKey: import.meta.env.VITE_APIKEY,
    authDomain: import.meta.env.VITE_AUTHDOMAIN,
    projectId: import.meta.env.VITE_PROJECTID,
    storageBucket: import.meta.env.VITE_STORAGEBUCKET,
    messagingSenderId: import.meta.env.VITE_MESSAGINGSENDERID,
    appId: import.meta.env.VITE_APPID,
};

export const FirebaseApp = initializeApp(firebaseConfig)
export const FirebaseAuth = getAuth(FirebaseApp)
Enter fullscreen mode Exit fullscreen mode

🔥 Creando las funciones para la autenticación.

Ahora vamos a crear un nuevo archivo dentro de src/firebase llamado services.ts

Nota: todas las funciones de firebase que vamos a usar vienen de firebase/auth

🔥 1 - Creando la función para autenticarse por Google.

Primero debemos crear una nueva instancia del proveedor que hayamos escogido, en este caso Google.

Luego creamos un método asíncrono, y dentro un try/catch porque ya sea que el usuario se equivoque o algo salga mal.

Mediante el método signInWithPopup, tendemos que mandarle nuestra instancia de FirebaseAuth, que ya habíamos creado en la sección anterior, y la instancia del proveedor.

Si todo sale correcto, de la propiedad user de la variable resultado, te dará varia información como lo puedes ver en la desestructuración, pero solo vamos a usar el uid por eso lo retornamos.

Y en el catch, de hecho en todos los catch de este archivo, solo vamos a mandar una alerta con el mensaje que nos proporciona Firebase

import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth'
import { FirebaseAuth } from './config'

const googleProvider = new GoogleAuthProvider()

export const singInWithGoogle = async () => {
    try {
        const result = await signInWithPopup(FirebaseAuth, googleProvider)

        const { displayName, email, photoURL, uid } = result.user

        return uid

    } catch (e) {
        alert((e as Error).message)
    }
}
Enter fullscreen mode Exit fullscreen mode

🔥 2 - Creando la función para autenticarse por credenciales.

La función para login y register usando credenciales son las mismas solo se diferencian por el método.

Ambas reciben un objeto que contiene el email y password y si todo sale bien, retornan el uid (estas funciones también devuelven lo mismo que el de autenticarse con google, como displayName, photoURL, etc.)

Tanto la función de createUserWithEmailAndPassword y signInWithEmailAndPassword reciben la instancia de FirebaseAuth, y un email y password.

  • createUserWithEmailAndPassword, crea el usuario en Firebase.
  • signInWithEmailAndPassword, verifica si existe el usuario en Firebase.
import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth'
import { FirebaseAuth } from './config'

interface PropsRegister { email: string, password: string }

export const signInWithCredentials = async ({ email, password }: PropsRegister) => {

    try {
        const resp = await createUserWithEmailAndPassword(FirebaseAuth, email, password);
        return resp.user.uid

    } catch (e) {
        alert((e as Error).message)
    }

}

export const loginWithCredentials = async ({ email, password }: PropsRegister) => {

    try {
        const resp = await signInWithEmailAndPassword(FirebaseAuth, email, password);
        return resp.user.uid

    } catch (e) {
        alert((e as Error).message)
    }
}
Enter fullscreen mode Exit fullscreen mode

🔥 3 - Creando la función para observar los cambios en el estado de autenticación del usuario.

¿Por que queremos observar el estado de autenticación del usuario?
Bueno, supongamos que iniciamos sesión correctamente, todo sale muy bien, estamos dentro de la application ya autenticados 🤩. Pero upss! refrescamos el navegador y se nos pierde la sesión, y tenemos que volver a iniciar sesión 😥.

Asi que como resolvemos este problema, pues observando el estado de autenticación del usuario.

Para ello necesitamos un par de cosas.
Primero crear una función, que va a recibir como parámetro un callback, o sea una función, dicha función nos ayudara a establecer el usuario autenticado o no autenticado.

Puedes notar en el código que usaremos un setter de useState y que ademas usaremos el Context API.El tipado les fallara porque aun no tenemos creado el context, asi que por el momento pueden colocar el tipo any.

Pero lo importante ahora es que recibimos la función setSession.

// type StateDispatch = React.Dispatch<React.SetStateAction<Pick<AuthStateContext, "status" | "userId">>>
type StateDispatch = any

export const onAuthStateHasChanged = (setSession: StateDispatch) => {}
Enter fullscreen mode Exit fullscreen mode

Ahora usaremos la función onAuthStateChanged, que recibe como primer parámetro el FirebaseAuth

import { onAuthStateChanged } from 'firebase/auth'
import { FirebaseAuth } from './config'

// type StateDispatch = React.Dispatch<React.SetStateAction<Pick<AuthStateContext, "status" | "userId">>>
type StateDispatch = any

export const onAuthStateHasChanged = (setSession: StateDispatch) => {
    onAuthStateChanged(FirebaseAuth)
}
Enter fullscreen mode Exit fullscreen mode

El segundo parámetro es un callback, que retorna el usuario si es que existe su sesión activa, de lo contrario retorna undefined.

import { onAuthStateChanged } from 'firebase/auth'
import { FirebaseAuth } from './config'

// type StateDispatch = React.Dispatch<React.SetStateAction<Pick<AuthStateContext, "status" | "userId">>>
type StateDispatch = any

export const onAuthStateHasChanged = (setSession: StateDispatch) => {
    onAuthStateChanged(FirebaseAuth, user => {

    })
}
Enter fullscreen mode Exit fullscreen mode

Evaluamos el usuario:

  • Si no existe, usamos el setSession para establecer el status en no-authenticated y el id del usuario en null. (no olviden colocar el return para evitar que se ejecute la siguiente linea)

  • Si existe, usamos el setSession para establecer el status en authenticated y el id del usuario.

import { onAuthStateChanged } from 'firebase/auth'
import { FirebaseAuth } from './config'

// type StateDispatch = React.Dispatch<React.SetStateAction<Pick<AuthStateContext, "status" | "userId">>>
type StateDispatch = any

export const onAuthStateHasChanged = (setSession: StateDispatch) => {
    onAuthStateChanged(FirebaseAuth, user => {

        if (!user) return setSession({ status: 'no-authenticated', userId: null })

        setSession({ status: 'authenticated', userId: user!.uid })
    })
}
Enter fullscreen mode Exit fullscreen mode

Probablemente no entiendas porque mandamos status o userId, bueno eso son los datos que necesitaremos en nuestro estado global, cuando vayamos a crear el contexto de nuestra app.

🔥 4 - Creando la función para cerrar sesión.

Ahora que pasa, gracias a que estamos observando el estado de autenticación del usuario, no podemos cambiar de usuario por un buen rato, ni aunque recargues o cierres el navegador.

Bueno para ello, debemos cerrar sesión, y e muy sencillo:

import { FirebaseAuth } from './config'

export const logoutFirebase = async () => await FirebaseAuth.signOut()
Enter fullscreen mode Exit fullscreen mode

🔥 Creando un contexto de nuestra aplicación.

Para crear un contexto primero vamos a crear una carpeta en src/context y dentro un archivo nombrado authContext.tsx

Dentro vamos a definir nuestra interfaz de las propiedades que vamos a compartir en nuestro contexto.

export interface AuthStateContext {
    userId: string | null
    status: 'checking' | 'authenticated' | 'no-authenticated'
    handleLoginWithGoogle: () => Promise<void>
    handleLoginWithCredentials: (password: string, email: string) => Promise<void>
    handleRegisterWithCredentials: (password: string, email: string) => Promise<void>
    handleLogOut: () => Promise<void>
}
Enter fullscreen mode Exit fullscreen mode

Luego vamos a crear el estado inicial de nuestro contexto.

Por defecto el status estará en checking por que al principio no sabemos si esta autenticado o no, lo sabremos una vez que se ejecuten ciertas funciones. Y también el userId sera nulo por defecto hasta comprobar el estado de autenticación del usuario.

const initialState: Pick<AuthStateContext, 'status' | 'userId'> = {
    status: 'checking',
    userId: null
}
Enter fullscreen mode Exit fullscreen mode

Creamos el contexto.

export const AuthContext = createContext({} as AuthStateContext)
Enter fullscreen mode Exit fullscreen mode

Creamos el proveedor que sera un componente funcional que recibirá el children

interface IElement { children: JSX.Element | JSX.Element[] }

export const AuthProvider = ({ children }: IElement) => {}
Enter fullscreen mode Exit fullscreen mode

Dentro del AuthProvider usaremos un estado y lo inicializamos con el objeto que establecimos con anterioridad.


export const AuthProvider = ({ children }: IElement) => {

    const [session, setSession] = useState(initialState)
}
Enter fullscreen mode Exit fullscreen mode

Ahora vamos a usar la función que creamos antes para observar el estado de autenticación del usuario.
Lo haremos en un efecto que solo se debe ejecutar la primera vez que inicie la aplicación. Y el callback que le mandaremos sera el setSession.

const [session, setSession] = useState(initialState)

useEffect(() => {
    onAuthStateHasChanged(setSession)
}, [])
Enter fullscreen mode Exit fullscreen mode

Luego haremos la función que ejecuta el cierre de sesión. Llamamos a la función logoutFirebase y establecemos la session con el userId en nulo y el status en no-authenticated

import { logoutFirebase } from '../firebase/providers'

const handleLogOut = async () => {
    logoutFirebase()
    setSession({ userId: null, status: 'no-authenticated' })
}
Enter fullscreen mode Exit fullscreen mode

Después, haremos una función que reutilizaremos en otras funciones, ya que queremos evitar repetir tanto código.

Esta función recibe el userId que puede ser un string o undefined, evaluamos si el userId es un string:

  • Si el userId es un string, significa que el usuario esta autenticado y establecemos la sesión con el userId y el status en authenticated. (no olviden colocar el return para evitar que se ejecute la siguiente linea)

  • Si el userId es undefined, llamamos a la función handleLogOut, ya que el usuario no tiene una autenticación valida, y necesitamos cerrar todas las sesiones.

const validateAuth = (userId: string | undefined) => {
    if (userId) return setSession({ userId, status: 'authenticated' })
    handleLogOut()
}
Enter fullscreen mode Exit fullscreen mode

Otra función que también vamos a reutilizar es la siguiente, es para establecer el status en checking mientras se realizar alguna validación.

const checking = () => setSession(prev => ({ ...prev, status: 'checking' }))
Enter fullscreen mode Exit fullscreen mode

Las siguientes funciones, asi como esta serán parecidas, primero hacer el checking, luego ejecutar el función especifico para esta tarea el cual nos retorna el userId o undefined y llamar el validateAuth mandando lo que retorna dicha función

1 - Función para iniciar sesión con Google.

import { singInWithGoogle } from '../firebase/providers'

const handleLoginWithGoogle = async () => {
    checking()
    const userId = await singInWithGoogle()
    validateAuth(userId)
}
Enter fullscreen mode Exit fullscreen mode

2 - Función para iniciar sesión con credenciales.

const handleLoginWithCredentials = async (password: string, email: string) => {
    checking()
    const userId = await loginWithCredentials({ email, password })
    validateAuth(userId)
}
Enter fullscreen mode Exit fullscreen mode

3 - Función para crear cuenta con credenciales.

const handleRegisterWithCredentials = async (password: string, email: string) => {
    checking()
    const userId = await signInWithCredentials({ email, password })
    validateAuth(userId)
}
Enter fullscreen mode Exit fullscreen mode

Finalmente, como es un componente funcional, debemos regresar un componente de la siguiente manera, colocando el children.

return (
    <AuthContext.Provider>
        {children}
    </AuthContext.Provider>
)
Enter fullscreen mode Exit fullscreen mode

La etiqueta AuthContext.Provider recibe un value, que son las siguientes propiedades

return (
    <AuthContext.Provider  
        value={{
            ...session,
            handleLoginWithGoogle,
            handleLoginWithCredentials,
            handleRegisterWithCredentials,
            handleLogOut
        }}
    >
        {children}
    </AuthContext.Provider>
)
Enter fullscreen mode Exit fullscreen mode

Y terminamos con nuestro contexto. Se vería asi 👀

import { createContext, useEffect, useState } from 'react'
import { loginWithCredentials, logoutFirebase, onAuthStateHasChanged, signInWithCredentials, singInWithGoogle } from '../firebase/providers'

export interface AuthStateContext {
    userId: string | null
    status: 'checking' | 'authenticated' | 'no-authenticated'
    handleLoginWithGoogle: () => Promise<void>
    handleLoginWithCredentials: (password: string, email: string) => Promise<void>
    handleRegisterWithCredentials: (password: string, email: string) => Promise<void>
    handleLogOut: () => Promise<void>
}

const initialState: Pick<AuthStateContext, 'status' | 'userId'> = {
    userId: null,
    status: 'checking'
}

export const AuthContext = createContext({} as AuthStateContext)

interface IElement { children: JSX.Element | JSX.Element[] }

export const AuthProvider = ({ children }: IElement) => {

    const [session, setSession] = useState(initialState)

    useEffect(() => {
        onAuthStateHasChanged(setSession)
    }, [])

    const handleLogOut = async () => {
        logoutFirebase()
        setSession({ userId: null, status: 'no-authenticated' })
    }

    const validateAuth = (userId: string | undefined) => {
        if (userId) return setSession({ userId, status: 'authenticated' })
        handleLogOut()
    }

    const checking = () => setSession(prev => ({ ...prev, status: 'checking' }))

    const handleLoginWithGoogle = async () => {
        checking()
        const userId = await singInWithGoogle()
        validateAuth(userId)
    }

    const handleLoginWithCredentials = async (password: string, email: string) => {
        checking()
        const userId = await loginWithCredentials({ email, password })
        validateAuth(userId)
    }

    const handleRegisterWithCredentials = async (password: string, email: string) => {
        checking()
        const userId = await signInWithCredentials({ email, password })
        validateAuth(userId)
    }

    return (
        <AuthContext.Provider
            value={{
                ...session,
                handleLoginWithGoogle,
                handleLoginWithCredentials,
                handleRegisterWithCredentials,
                handleLogOut
            }}
        >
            {children}
        </AuthContext.Provider>
    )
}
Enter fullscreen mode Exit fullscreen mode

Ahora necesitamos envolver nuestra app con el proveedor. Para ello vamos al punto mas alto de nuestra app que es el archivo src/main.tsx y agregamos el AuthProvider

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { AuthProvider } from './context/authContext'
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>
)
Enter fullscreen mode Exit fullscreen mode

🔥 Usando nuestro contexto.

Ahora nos vamos a el archivo src/components/Register.tsx y usamos nuestro contexto de la siguiente manera:
Importamos el hook useContext y le mandamos el AuthContext y obtenemos la función handleRegisterWithCredentials

Dicha función la ejecutamos dentro de handleSubmit y le mandamos el email y password.

import { useContext } from 'react';
import { AuthContext } from "../context/authContext";

export const Register = () => {

    const { handleRegisterWithCredentials } = useContext(AuthContext)

    const { handleChange, pass, email } = useForm({
        initialState: {
            email: 'test@test2.com',
            pass: '123456'
        }
    })

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        handleRegisterWithCredentials(pass, email)
    }

// ...
Enter fullscreen mode Exit fullscreen mode

Lo mismo hacemos en src/components/Login.tsx.

import { useContext } from 'react';
import { AuthContext } from '../context/authContext';

export const Login = () => {

    const { handleLoginWithCredentials } = useContext(AuthContext)

    const { handleChange, pass, email } = useForm({
        initialState: {
            email: 'test@test1.com',
            pass: '123456'
        }
    })

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        handleLoginWithCredentials(pass, email)
    }

// ...
Enter fullscreen mode Exit fullscreen mode

Pero también necesitamos la función handleLoginWithGoogle

const { handleLoginWithGoogle, handleLoginWithCredentials } = useContext(AuthContext)
Enter fullscreen mode Exit fullscreen mode

Dicha función se va a ejecutar el atributo onClick de la etiqueta button de Google.

<button type="button" onClick={handleLoginWithGoogle}> Google </button>
Enter fullscreen mode Exit fullscreen mode

Finalmente en nuestro archivo src/App.tsx

Vamos a usar el contexto extrayendo el status y el userID.
Evaluamos el status y si es checking, mostramos un loading.

  const { status, userId } = useContext(AuthContext)

  if (status === 'checking') return <p className="loading"><span>Checking credentials, wait a moment...</span></p>
Enter fullscreen mode Exit fullscreen mode

loading

Ahora al final del archivo crearemos dos componentes.

El primero es el HomePage (para simular que es un pagina diferente).

Este componente solo sera visible cuando el usuario este autenticado.

Y se mostrara el userID y un botón que ejecuta el cerrar session.

export const HomePage = () => {
  const { userId, handleLogOut } = useContext(AuthContext)

  return (
    <section>
      <h5>Your ID is: <span>{userId}</span></h5>
      <button className="btn-logout" onClick={handleLogOut}>Log out</button>
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode

authenticated

El segundo componente es el AuthPage, (simula otra pagina diferente).

Este componente solo sera visible cuando el usuario NO este autenticado.

Solo se muestran los componentes Login y Register que teníamos en nuestro componente App.

export const AuthPage = () => {
  return (
    <section>
      <Login />
      <Register />
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode

Ahora en el componente App, vamos hacer una validación. Donde si el status, es authenticated y existe el userId, mostramos el HomePage, de lo contrario que muestre el AuthPage


return (
    <main>
        <h1><b>Auth with</b> <span>Firebase</span> <b>and</b> <span>React</span></h1>
        {
            (status === 'authenticated' && userId)
                ? <HomePage />
                : <AuthPage />
        }
    </main>
)
Enter fullscreen mode Exit fullscreen mode

El archivo App.tsx quedaría de la siguiente manera 👀:

import { useContext } from "react"
import { Login, Register } from "./components"
import { AuthContext } from './context/authContext';

const App = () => {

  const { status, userId } = useContext(AuthContext)

  if (status === 'checking') return <p className="loading"><span>Checking credentials, wait a moment...</span></p>

  return (
    <main>
      <h1><b>Auth with</b> <span>Firebase</span> <b>and</b> <span>React</span></h1>
      {
        (status === 'authenticated' && userId)
          ? <HomePage />
          : <AuthPage />
      }
    </main>
  )
}
export default App

export const HomePage = () => {
  const { userId, handleLogOut } = useContext(AuthContext)

  return (
    <section>
      <h5>Your ID is: <span>{userId}</span></h5>
      <button className="btn-logout" onClick={handleLogOut}>Log out</button>
    </section>
  )
}

export const AuthPage = () => {
  return (
    <section>
      <Login />
      <Register />
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode

🔥 Conclusión.

Sin duda usar un servio como Firebase que nos ayuda mucho en ahorrar tiempo a comparación de construir nuestros propios servicios como la autenticación.

Espero que te haya gustado esta publicación y que te haya ayudada a entender más sobre como realizar la autenticación de usuarios en tus aplicación con React. 🤗

Si es que conoces alguna otra forma distinta o mejor de realizar esta funcionalidad con gusto puedes comentarla 🙌.

Te invito a que revises mi portafolio en caso de que estés interesado en contactarme para algún proyecto! Franklin Martinez Lucas

🔵 No olvides seguirme también en twitter: @Frankomtz361

🔥 Código fuente.

GitHub logo Franklin361 / auth-firebase-react

Application to show how to do authentication with Firebase and React JS 🔑

React app authentication using Firebase. 🔑

This time, we are going to implement authentication using React JS and Firebase!

demo

 

Features ⚙️

  1. Log in.
  2. Log in with Google.
  3. Sign in
  4. Log out.

 

Technologies 🧪

  • ▶️ React JS (v 18)
  • ▶️ Vite JS
  • ▶️ TypeScript
  • ▶️ Firebase (v 9.10)
  • ▶️ CSS vanilla

 

Installation 🧰

  1. Clone the repository (you need to have Git installed).
    git clone https://github.com/Franklin361/auth-firebase-react
Enter fullscreen mode Exit fullscreen mode
  1. Install dependencies of the project.
    npm install
Enter fullscreen mode Exit fullscreen mode
  1. Run the project.
    npm run dev
Enter fullscreen mode Exit fullscreen mode

 

Article links ⛓️

Here's the link to the tutorial in case you'd like to take a look at it! eyes 👀

  • 🇲🇽 🔗

  • 🇺🇲 🔗

Top comments (0)

Become a Moderator Do you want us to help make DEV a better place?

Fill out this survey and help us by becoming a tag moderator here at DEV.