# 08 Auth Email & Pass
Vamos a construir una aplicación para registrar nuevos usuarios a través de email y contraseña. Este también es un servicio de Firebase.
CURSO EN UDEMY OFERTA!
Aprende desde cero a trabajar con React.js y Firebase aquí: http://curso-react-js-udemy.bluuweb.cl/ (opens new window) Nos vemos en clases!
# Instalaciones
npx create-react-app login-udemy-1
npm i firebase
npm i react-router-dom
# Firebase
import app from 'firebase/app'
import 'firebase/firestore'
import 'firebase/auth'
const firebaseConfig = {
apiKey: "xxx",
authDomain: "xxx",
databaseURL: "xxx",
projectId: "xxx",
storageBucket: "xxx",
messagingSenderId: "xxx",
appId: "xxx"
};
app.initializeApp(firebaseConfig);
const db = app.firestore();
const auth = app.auth();
export {db, auth}
# Rutas
import React from 'react'
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
const App = () => {
return (
<Router>
<div className="container">
navbar...
<Switch>
<Route path="/login">
Ruta de login
</Route>
<Route path="/admin">
Ruta de administracion
</Route>
<Route path="/" exact>
Ruta de inicio
</Route>
</Switch>
</div>
</Router>
)
}
export default App
# Navbar
import React from 'react'
import {Link, NavLink} from 'react-router-dom'
const Navbar = () => {
return (
<div className="navbar navbar-dark bg-dark">
<Link to="/" className="navbar-brand">React Admin</Link>
<div>
<div className="d-flex">
<NavLink
className="btn btn-dark mr-2"
to="/"
exact
>
Inicio
</NavLink>
<NavLink
className="btn btn-dark mr-2"
to="/admin"
>
Admin
</NavLink>
<NavLink
className="btn btn-dark"
to="/login"
>
Login
</NavLink>
</div>
</div>
</div>
)
}
export default Navbar
# Registro estructura
import React from 'react'
const Login = () => {
return (
<div className="mt-5">
<h3 className="text-center">Acceso o Registro de usuarios</h3>
<hr/>
<div className="row justify-content-center">
<div className="col-12 col-sm-8 col-md-6 col-xl-4">
<form>
<input
type="email"
className="form-control mb-2"
placeholder="Ingrese Email"
/>
<input
type="password"
className="form-control mb-2"
placeholder="Ingrese Contraseña"
/>
<button
className="btn btn-lg btn-dark btn-block"
type="submit"
>
Ingresar
</button>
<button
className="btn btn-sm btn-info btn-block"
type="button"
>
¿No tienes cuenta?
</button>
</form>
</div>
</div>
</div>
)
}
export default Login
# Registro useState
import React from 'react'
const Login = () => {
const [email, setEmail] = React.useState('')
const [pass, setPass] = React.useState('')
const [error, setError] = React.useState(null)
const procesarDatos = e => {
e.preventDefault()
if(!email.trim() || !pass.trim()){
console.log('Datos vacíos email!')
setError('Datos vacíos email!')
return
}
if(!pass.trim()){
console.log('Datos vacíos pass!')
setError('Datos vacíos pass!')
return
}
if(pass.length < 6){
console.log('6 o más carácteres')
setError('6 o más carácteres en pass')
return
}
console.log('correcto...')
setError(null)
}
return (
<div className="mt-5">
<h3 className="text-center">Registro de usuarios</h3>
<hr/>
<div className="row justify-content-center">
<div className="col-12 col-sm-8 col-md-6 col-xl-4">
<form onSubmit={procesarDatos}>
{
error ? (
<div className="alert alert-danger">
{error}
</div>
) : null
}
<input
type="email"
className="form-control mb-2"
placeholder="Ingrese Email"
onChange={ e => setEmail(e.target.value) }
value={email}
/>
<input
type="password"
className="form-control mb-2"
placeholder="Ingrese Contraseña"
onChange={ e => setPass(e.target.value) }
value={pass}
/>
<button
className="btn btn-lg btn-dark btn-block"
type="submit"
>
Ingresar
</button>
<button
className="btn btn-sm btn-info btn-block"
type="button"
>
¿Ya tienes cuenta?
</button>
</form>
</div>
</div>
</div>
)
}
export default Login
# Registro y Login
const [esRegistro, setEsRegistro] = React.useState(true)
<h3 className="text-center">
{
esRegistro ? 'Registro' : 'Login'
}
</h3>
<hr/>
<div className="row justify-content-center">
<div className="col-12 col-sm-8 col-md-6 col-xl-4">
<form onSubmit={procesarDatos}>
{
error ? (
<div className="alert alert-danger">
{error}
</div>
) : null
}
<input
type="email"
className="form-control mb-2"
placeholder="Ingrese Email"
onChange={ e => setEmail(e.target.value) }
value={email}
/>
<input
type="password"
className="form-control mb-2"
placeholder="Ingrese Contraseña"
onChange={ e => setPass(e.target.value) }
value={pass}
/>
<button
className="btn btn-lg btn-dark btn-block"
type="submit"
>
{esRegistro ? 'Registrar' : 'Acceder'}
</button>
<button
className="btn btn-sm btn-info btn-block"
type="button"
onClick={() => setEsRegistro(!esRegistro)}
>
{esRegistro ? '¿Ya tienes cuenta?' : '¿No tienes cuenta?'}
</button>
</form>
</div>
</div>
# Firebase Registrar
https://firebase.google.com/docs/auth/web/password-auth?hl=es (opens new window)
import {db, auth} from '../firebase'
// después de pasar validaciones de datos...
if(esRegistro){
registrar()
}
// Función externa
const registrar = React.useCallback(async() => {
try {
const res = await auth.createUserWithEmailAndPassword(email, pass)
console.log(res.user)
await db.collection('usuarios').doc(res.user.uid).set({
fechaCreacion: Date.now(),
displayName: res.user.displayName,
photoURL: res.user.photoURL,
email: res.user.email,
uid: res.user.uid
})
setEmail('')
setPass('')
setError(null)
props.history.push('/admin')
} catch (error) {
console.log(error)
// setError(error.message)
if(error.code === 'auth/email-already-in-use'){
setError('Usuario ya registrado...')
return
}
if(error.code === 'auth/invalid-email'){
setError('Email no válido')
return
}
}
}, [email, pass, props.history])
# Firebase Login
if(!esRegistro){
login()
}
// Función externa
const login = React.useCallback(async() => {
try {
await auth.signInWithEmailAndPassword(email, pass)
setEmail('')
setPass('')
setError(null)
props.history.push('/admin')
} catch (error) {
if(error.code === 'auth/user-not-found'){
setError('Usuario o contraseña incorrecta')
}
if(error.code === 'auth/wrong-password'){
setError('Usuario o contraseña incorrecta')
}
console.log(error.code)
console.log(error.message)
}
}, [email, pass, props.history])
# Push rutas
// Importar
import { withRouter } from "react-router-dom";
// Envolver
export default withRouter(Login)
// Utilizar props
const Login = (props) => {...}
// Push
props.history.push('/admin')
# Ruta protegida
# Estructura
import React from 'react'
import { withRouter } from "react-router-dom";
import {auth} from '../firebase'
const Admin = (props) => {
return (
<div className="mt-5">
<h3 className="text-center">Ruta protegida</h3>
</div>
)
}
export default withRouter(Admin)
# useEffect
currentUser: nos trae la información del usuario https://firebase.google.com/docs/auth/web/manage-users#get_the_currently_signed-in_user (opens new window)
import React from 'react'
import { withRouter } from "react-router-dom";
import {auth} from '../firebase'
const Admin = (props) => {
const [user, setUser] = React.useState(null)
React.useEffect(() => {
if(auth.currentUser){
console.log('existe')
setUser(auth.currentUser)
}else{
console.log('no existe')
props.history.push('/login')
}
}, [props.history])
return (
<div className="mt-5">
<h3 className="text-center">Ruta protegida</h3>
{
user && (
<p>{user.email}</p>
)
}
</div>
)
}
export default withRouter(Admin)
# App.jsx (detectar usuario)
onAuthStateChanged: va evaluando si existe el usuario, por lo tanto si se cierra sesión se vuelve a ejecutar onAuthStateChanged()
// Recuerde hacer la importación de firebase
import {auth } from './firebase'
const [firebaseUser, setFirebaseUser] = React.useState(false)
React.useEffect(() => {
auth.onAuthStateChanged(user => {
console.log(user)
if(user){
setFirebaseUser(user)
}else{
setFirebaseUser(null)
}
})
}, [])
return firebaseUser !== false ? (
<Router>
<div className="container">
<Navbar firebaseUser={firebaseUser} />
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/admin">
<Admin />
</Route>
<Route path="/" exact>
Ruta de inicio
</Route>
</Switch>
</div>
</Router>
) : (
<div>Cargando...</div>
)
# Navbar cerrar sesión y ocultar "admin"
import React from 'react'
import {Link, NavLink, withRouter} from 'react-router-dom'
import {auth} from '../firebase'
const Navbar = (props) => {
const cerrarSesion = () => {
auth.signOut()
.then(() => {
props.history.push('/login')
})
}
return (
<div className="navbar navbar-dark bg-dark">
<Link to="/" className="navbar-brand">React Admin</Link>
<div>
<div className="d-flex">
<NavLink
className="btn btn-dark mr-2"
to="/"
exact
>
Inicio
</NavLink>
{
props.firebaseUser !== null ? (
<NavLink
className="btn btn-dark mr-2"
to="/admin"
>
Admin
</NavLink>
) : null
}
{
props.firebaseUser !== null ? (
<button
className="btn btn-dark"
onClick={() => cerrarSesion()}
>
Cerrar Sesión
</button>
): (
<NavLink
className="btn btn-dark"
to="/login"
>
Login
</NavLink>
)
}
</div>
</div>
</div>
)
}
export default withRouter(Navbar)
# Firestore
Al agregar un usuario configurar nueva collection
await db.collection(res.user.uid).add({name: 'Tarea de ejemplo #1'})
Crear un componente nuevo y agregarlo al Admin
return (
<div className="mt-5">
<h3 className="text-center">Ruta protegida</h3>
{
user && (
<Firestore user={user} />
)
}
</div>
)
Utilizar el ejercicio de CRUD Firestore en el nuevo componente
import React from 'react'
import {firebase} from '../firebase'
const Firestore = () => {
const [tareas, setTareas] = React.useState([])
const [tarea, setTarea] = React.useState('')
const [modoEdicion, setModoEdicion] = React.useState(false)
const [id, setId] = React.useState('')
React.useEffect(() => {
const obtenerDatos = async () => {
try {
const db = firebase.firestore()
const data = await db.collection('tareas').get()
const arrayData = data.docs.map(doc => ({ id: doc.id, ...doc.data() }))
console.log(arrayData)
setTareas(arrayData)
} catch (error) {
console.log(error)
}
}
obtenerDatos()
}, [])
const agregar = async (e) => {
e.preventDefault()
if(!tarea.trim()){
console.log('está vacio')
return
}
try {
const db = firebase.firestore()
const nuevaTarea = {
name: tarea,
fecha: Date.now()
}
const data = await db.collection('tareas').add(nuevaTarea)
setTareas([
...tareas,
{...nuevaTarea, id: data.id}
])
setTarea('')
} catch (error) {
console.log(error)
}
console.log(tarea)
}
const eliminar = async (id) => {
try {
const db = firebase.firestore()
await db.collection('tareas').doc(id).delete()
const arrayFiltrado = tareas.filter(item => item.id !== id)
setTareas(arrayFiltrado)
} catch (error) {
console.log(error)
}
}
const activarEdicion = (item) => {
setModoEdicion(true)
setTarea(item.name)
setId(item.id)
}
const editar = async (e) => {
e.preventDefault()
if(!tarea.trim()){
console.log('vacio')
return
}
try {
const db = firebase.firestore()
await db.collection('tareas').doc(id).update({
name: tarea
})
const arrayEditado = tareas.map(item => (
item.id === id ? {id: item.id, fecha: item.fecha, name: tarea} : item
))
setTareas(arrayEditado)
setModoEdicion(false)
setTarea('')
setId('')
} catch (error) {
console.log(error)
}
}
return (
<div>
<div className="row">
<div className="col-md-6">
<h3>Lista de tareas</h3>
<ul className="list-group">
{
tareas.map(item => (
<li className="list-group-item" key={item.id}>
{item.name}
<button
className="btn btn-danger btn-sm float-right"
onClick={() => eliminar(item.id)}
>
Eliminar
</button>
<button
className="btn btn-warning btn-sm float-right mr-2"
onClick={() => activarEdicion(item)}
>
Editar
</button>
</li>
))
}
</ul>
</div>
<div className="col-md-6">
<h3>
{
modoEdicion ? 'Editar Tarea' : 'Agregar Tarea'
}
</h3>
<form onSubmit={modoEdicion ? editar : agregar}>
<input
type="text"
placeholder="Ingrese tarea"
className="form-control mb-2"
onChange={e => setTarea(e.target.value)}
value={tarea}
/>
<button
className={
modoEdicion ? 'btn btn-warning btn-block' : 'btn btn-dark btn-block'
}
type="submit"
>
{
modoEdicion ? 'Editar' : 'Agregar'
}
</button>
</form>
</div>
</div>
</div>
)
}
export default Firestore
# Deploy Firebase
Antes compilar nuestra aplicación con:
npm run build
Instalar herramientas de firebase solo una vez por computador:
npm install -g firebase-tools
Acceder
firebase login
Iniciar
firebase init
Subir
firebase deploy
# Reglas de seguridad básicas
https://firebase.google.com/docs/rules/basics?authuser=0#all_authenticated_users (opens new window)
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
# Fechas
Agregaremos momentjs para formatear fechas de una forma sencilla. https://momentjs.com/ (opens new window)
npm install moment --save
import moment from 'moment'
import 'moment/locale/es' // Pasar a español
{item.name} - { moment(item.fecha).format('MMMM Do YYYY, h:mm:ss a') }
# Recuperar contraseña
https://firebase.google.com/docs/auth/web/manage-users?authuser=0#send_a_password_reset_email (opens new window) Agregar botón en login:
{
!esRegistro ? (
<button
className="btn btn-sm btn-info btn-danger"
type="button"
onClick={() => props.history.push('/reset')}
>
Perdí mi contraseña
</button>
): null
}
Componente Reset
import React from 'react'
import {auth} from '../firebase'
import {withRouter} from 'react-router-dom'
const ResetPass = (props) => {
const [email, setEmail] = React.useState('')
const [error, setError] = React.useState(null)
const procesarDatos = e => {
e.preventDefault()
if(!email.trim()){
console.log('Datos vacíos email!')
setError('Datos vacíos email!')
return
}
setError(null)
recuperar()
}
const recuperar = React.useCallback(
async () => {
try {
await auth.sendPasswordResetEmail(email)
props.history.push('/login')
} catch (error) {
console.log(error)
setError(error.message)
}
},
[email, props.history])
return (
<div className="mt-5">
<h3 className="text-center">
Recuperar contraseña
</h3>
<hr/>
<div className="row justify-content-center">
<div className="col-12 col-sm-8 col-md-6 col-xl-4">
<form onSubmit={procesarDatos}>
{
error ? (
<div className="alert alert-danger">
{error}
</div>
) : null
}
<input
type="email"
className="form-control mb-2"
placeholder="Ingrese Email"
onChange={ e => setEmail(e.target.value) }
value={email}
/>
<button
className="btn btn-lg btn-dark btn-block"
type="submit"
>
Recuperar
</button>
</form>
</div>
</div>
</div>
)
}
export default withRouter(ResetPass)