Se vería asi, si es que usas los mismos estilos que este proyecto. 👀
🔥 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.
exportconstLogin=()=>{return(<divclassName="container-auth"><h2>Login</h2><form><inputname="email"type="email"placeholder="E-mail"/><inputname="pass"type="password"placeholder="Password"/><divclassName="container-buttons"><buttontype="submit">Log In</button><buttontype="button"> Google </button></div></form></div>)}
🔥 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
exportconstRegister=()=>{return(<divclassName="container-auth"><h2>Create an account</h2><form><inputname="email"type="email"placeholder="E-mail"/><inputname="pass"type="password"placeholder="Password"/><divclassName="container-buttons"><buttontype="submit">Sign up</button></div></form></div>)}
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'
Una vez tengamos los dos diseños y el archivo barril, vamos a src/App.tsx y los agregamos:
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
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.
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.
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.
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.
import{useForm}from'../hooks/useForm';exportconstLogin=()=>{const{handleChange,pass,email}=useForm({initialState:{email:'',pass:''}})consthandleSubmit=(e:React.FormEvent<HTMLFormElement>)=>{e.preventDefault()}return(<divclassName="container-auth"><h2>Login</h2><formonSubmit={handleSubmit}><inputname="email"type="email"placeholder="E-mail"onChange={handleChange}value={email}/><inputname="pass"type="password"placeholder="Password"onChange={handleChange}value={pass}/><divclassName="container-buttons"><buttontype="submit">Log In</button><buttontype="button"> Google </button></div></form></div>)}
Asi como hicimos el Login.tsx es exactamente igual en el archivo Register.tsx
import{useForm}from"../hooks/useForm"exportconstRegister=()=>{const{handleChange,pass,email}=useForm({initialState:{email:'',pass:''}})consthandleSubmit=(e:React.FormEvent<HTMLFormElement>)=>{e.preventDefault()}return(<divclassName="container-auth"><h2>Create an account</h2><formonSubmit={handleSubmit}><inputname="email"type="email"placeholder="E-mail"onChange={handleChange}value={email}/><inputname="pass"type="password"placeholder="Password"onChange={handleChange}value={pass}/><divclassName="container-buttons"><buttontype="submit">Sign up</button></div></form></div>)}
⚠️ 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.
4 - Esperar a que tu proyecto termine de crearse y después dar click en continuar
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).
2 - Coloca el nombre a tu app y dale click en Registrar app
3 - Una vez registrada la app, nos darán nuestras credenciales, las cuales tenemos que guardar porque las vamos a usar después.
🔥 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
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.
3 - Una vez habilitados los proveedores, tendrá que aparecerte en la pestaña Sign-in method de la siguiente forma.
4 - En la pestaña Users puedes ver todos los usuarios que se registren en tu aplicación.
🔥 Configurando Firebase en nuestra aplicación de React.
De vuelta a nuestra app de React, vamos a instalar Firebase.
npm install firebase
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
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
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
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
🔥 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.
🔥 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">>>typeStateDispatch=anyexportconstonAuthStateHasChanged=(setSession:StateDispatch)=>{}
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">>>typeStateDispatch=anyexportconstonAuthStateHasChanged=(setSession:StateDispatch)=>{onAuthStateChanged(FirebaseAuth)}
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">>>typeStateDispatch=anyexportconstonAuthStateHasChanged=(setSession:StateDispatch)=>{onAuthStateChanged(FirebaseAuth,user=>{})}
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">>>typeStateDispatch=anyexportconstonAuthStateHasChanged=(setSession:StateDispatch)=>{onAuthStateChanged(FirebaseAuth,user=>{if(!user)returnsetSession({status:'no-authenticated',userId:null})setSession({status:'authenticated',userId:user!.uid})})}
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:
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.
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.
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
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.
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
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
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.
Dicha función se va a ejecutar el atributo onClick de la etiqueta button de Google.
<buttontype="button"onClick={handleLoginWithGoogle}> Google </button>
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<pclassName="loading"><span>Checking credentials, wait a moment...</span></p>
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.
exportconstHomePage=()=>{const{userId,handleLogOut}=useContext(AuthContext)return(<section><h5>Your ID is: <span>{userId}</span></h5><buttonclassName="btn-logout"onClick={handleLogOut}>Log out</button></section>)}
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.
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
El archivo App.tsx quedaría de la siguiente manera 👀:
import{useContext}from"react"import{Login,Register}from"./components"import{AuthContext}from'./context/authContext';constApp=()=>{const{status,userId}=useContext(AuthContext)if(status==='checking')return<pclassName="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>)}exportdefaultAppexportconstHomePage=()=>{const{userId,handleLogOut}=useContext(AuthContext)return(<section><h5>Your ID is: <span>{userId}</span></h5><buttonclassName="btn-logout"onClick={handleLogOut}>Log out</button></section>)}exportconstAuthPage=()=>{return(<section><Login/><Register/></section>)}
🔥 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
Franklin Martinez
@frankomtz361
Hola a todos! 👋
Mi nombre es Franklin Martinez.
Soy desarrollador Front-End y me especializo en las tecnologías de JavaScript y React JS. ⚛️ También me gusta publicar ✍ algunos artículos.