Autenticaci贸n con Firebase y React JS 馃攽

franklin030601

Franklin Martinez

Posted on November 11, 2022

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 馃憖

  • 馃嚥馃嚱 馃敆

  • 馃嚭馃嚥 馃敆






馃挅 馃挭 馃檯 馃毄
franklin030601
Franklin Martinez

Posted on November 11, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related