No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Navigate y redirects: protegiendo rutas privadas

13/30
Recursos

Aportes 16

Preguntas 5

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

Navigate y redirects: protegiendo rutas privadas

Como mencionamos anteriormente, si un usuario intenta acceder a una ruta privada por medio del historial o la barra de b煤squeda a煤n si esta no esta renderizada o no puede acceder lo que va a causar es que nuestra aplicaci贸n este en blanco, colapse y ya no se pueda interactuar hasta que recarguemos.

Para solucionar esto lo que haremos es protegerlas de verdad haciendo que si intentan acceder a una ruta parvada haga un redirect al home, a login o a profile dependiendo de la ruta donde intente acceder.

Para esto vamos primero a nuestro Profile.js:

// Importamos el componente navigate
import { useAuth } from '../../auth/auth';

function ProfilePage() {
  const auth = useAuth();

  /* Preguntamos si hay un usuario registrado, si no, redirigimos 
	a la persona */ 
  if (!auth.user) {
    /* Con este componente podemos hacer el redirect hacia donde la 
		l贸gica de nuestra aplicaci贸n lo requiera */ 
    return <Navigate to="/login" />
  }

  return (
    <>
      <h1>Perfil</h1>
      <h1>Welcome {auth.user.username}</h1>
    </>
  );
}

De esta manera si no nos hemos registrado e intentamos acceder a Profile por medio de la barra de b煤squeda nos redirigir谩 a nuestro login.

En caso de que necesitemos redireccionar varias veces a distintos sitios de nuestra aplicaci贸n veremos que hacer esto puede ser un poco repetitivo, debemos solucionar esto.

Vamos a auth.js y creemos una funci贸n que nos permita hacer esto m谩s sencillo.

import { Navigate } from 'react-router-dom';

...
/* En este compoinente podemos hacer la validaci贸n que es la misma 
que utilizamos con anterioridad */
function AuthRoute(props) {
  const auth = useAuth();

  if (!auth.user) {
    return <Navigate to="/login" />
  }

  return props.children
}

export {
	...
  AuthRoute,
}

Luego simplemente implementamos este componente en el ProfilePage.js:

import { AuthRoute, useAuth } from '../../auth/auth';

function ProfilePage() {
  const auth = useAuth();

  return (
    <AuthRoute>
      <h1>Perfil</h1>
      <h1>Welcome {auth.user.username}</h1>
    </AuthRoute>
  );
}

export { ProfilePage }

Si lo ejecutamos tendremos un error debido a que al renderizar el componente a煤n as铆 lo redireccionemos como auth.user.username no existe y esto nos dar谩 un error ya que en el componente lo estamos solicitando y esto nos genera un erro bloqueante al redireccionar a login.

Pero para que funcione esto en nuestro llamado desde App.js debemos importar a este componente ProfilePage dentro de un componente AuthRoute:

import { AuthProvider, useAuth, AuthRoute } from './Components/auth/auth'
...

function App() {
  return (
    <>
      <HashRouter>
        <AuthProvider>
          <Routes>
            ...
            <Route path='/profile' 
              element={
                <AuthRoute>
                  <ProfilePage /> {/* <-- */}
                </AuthRoute>
              } 
            />
          </Routes>
        </AuthProvider>

      </HashRouter>
    </>
  )
}

Y nuestro componente ProfilePage.js lo podemos dejar como estaba:

function ProfilePage() {
  const auth = useAuth();

  return (
    <>
      <h1>Perfil</h1>
      <h1>Welcome {auth.user.username}</h1>
    </>
  );
}

Ahora haremos lo mismo con el Logout en App.js:

<Route path='/logout' 
  element={
    <AuthRoute>
      <LogoutPage />
    </AuthRoute>
  } 
/>

Lo 煤nico que nos queda por hacer es que cuando estemos autenticados no podamos acceder a la p谩gina de Login, porque estamos protegiendo las rutas privadas de un usuario p煤blico pero no la ruta p煤blica de registro a un usuario privado. 隆Hagamos esto!

LoginPage.js

function LoginPage() {
	...	

  /* Como en este caso solo necesiamos hacer una validaci贸n lo 
  haremos directo en el componente preguntando si el usuario ya esta 
  registrado, si lo est谩 lo redirigiremos a la p谩gina de Profile 
  usando el Navigate */
  if(auth.user) {
    return <Navigate to="/profile" />
  }

  return (
		...
	)
}

Y hemos terminado de proteger nuestras rutas y tener una mejor l贸gica de aplicaci贸n.

鈥榩or favor vete鈥 jajajajaj

Navigate y redirects: protegiendo rutas privadas

Es muy importante poder proteger rutas que a las que no queremos que el usuario sin autenticar tenga acceso.

Una forma para solucionar esto seria la utilizaci贸n del componente 鈥淣avigate鈥 (Con N may煤scula), por medio de un if, donde evaluamos si el usuario est谩 autenticado o no, vamos a indicarle a nuestro navegador que queremos redireccionar al usuario hacia 鈥/login鈥

const auth = useAuth()

  if(!auth.user){
    return <Navigate to="/login"/>
  }

Esto ya es una soluci贸n, pero existe otra forma m谩s simple de trabajar:

Dentro de nuestro archivo auth vamos a crear un componente, se podr铆a llamar 鈥淎utenticadorDeRutas鈥 o 鈥淎uthRoute鈥, y dentro de este vamos a incluir la l贸gica que aprendimos agregando que, en el caso de que si se pueda ver el contenido, se retorne el 鈥減rops.children鈥

function AuthRoute(props) {
  const auth = useAuth();

  if (!auth.user) {
    return <Navigate to="/login" />
  }

  return props.children
}

Por ultimo, tengo que decir que el componente AuthRouter es mejor llamarlo, justamente, en nuestro Router. De esta manera nos ahorramos problemas con valores que, asumimos, ya deber铆an existir en nuestros componentes si son renderizados.

<Route path='/profile' 
	element={
	  <AuthRoute>
	   <ProfilePage/>
	  </AuthRoute>
}/>

()= parentesis
{}=corchetes
[]=llaves

Es la segunda vez que veo la clase en semanas diferentes y todavia me sorprendo de lo buena que es !!!

Felicitaciones!!

C贸digo de la clase en TypeScript
(Yo separe las partes relacionados al auth en useAuth, AuthStore y AuthRoute)
AuthRoute.tsx

import { Navigate } from "react-router-dom";
import { useAuth } from "./useAuth"
import { FC, PropsWithChildren } from "react";

const AuthRoute:FC<PropsWithChildren> = ({ children }) =>{

    const auth = useAuth();

    if(!auth.user) return <Navigate to="/login" />

    return  children;
}

export { AuthRoute }

LoginPage.tsx

const LoginPage = () => {

    const auth = useAuth();
    const [username, setUsername] = useState('');

    const login = (e:FormEvent<HTMLFormElement>)=>{
        e.preventDefault();
        auth.login({username});
    }

    if(auth.user) return <Navigate to="/profile" />

    return(
        <>
            <h1>Login</h1>

            <form onSubmit={login}>
                <label>Escribe tu nombre de usuario</label>
                <input
                value={username}
                onChange={ e => setUsername(e.target.value)}
                />
                <button type="submit">Entrar</button>
            </form>
        </>
    )
}

App.tsx

function App() {

  return (
    <>
      <HashRouter>
        <AuthProvider>
          <Menu />

          <Routes>
            <Route path='/' element={<HomePage />} />

            <Route path='/blog' element={<BlogPage />}>
              <Route path=':slug' element={<BlogPost />} />
            </Route>
            
            <Route path='/login' element={<LoginPage />} />
            <Route path='/logout' element={
              <AuthRoute>
                <LogoutPage />
              </AuthRoute>
            } />
            <Route path='/profile' element={
              <AuthRoute>
                <ProfilePage />
              </AuthRoute>
            } />


            <Route path='*'  element={<p>Not found</p>} />
          </Routes>
        </AuthProvider>
      </HashRouter>
    </>
  )
}

export default App

Nuestra principal estrategia en este caso fue el usar validaciones para permitir o no que una persona pueda acceder a diferentes paginas dependiendo de si est谩 logeada o no

esta clase es m谩gica

Navigate y redirects: protegiendo rutas privadas

.
Vamos a proteger las rutas de manera que si un usuario trata de acceder directamente por la url, entonces se le redirija al home, o la p谩gina que corresponda.
.
En primer lugar se podr铆a pensar en condicionar el renderizado del componente y mostrar un mensaje que diga que no se le permite ver dicho contenido, pero no es realmente la mejor forma de hacerlo.
.

function ProfilePage() {
  const auth = useAuth();
  
  if (auth.user) {
	  return (
		  <>
	      <h1>Perfil</h1>
	      <p>Welcome, {auth.user.username}</p>
	    </>
	  );
  } else {
	  return (
		  Por favor vete
	  );
  }
}

.
Otra forma es utilizar el componente Navigate de React Router DOM para navegar autom谩ticamente al home en el caso de que no se hayan logueado.
.

function ProfilePage() {
  const auth = useAuth();
  if (!auth.user) {
	  return <Navigate to="/login" />;
  }
  
  return (
    <>
      <h1>Perfil</h1>
      <p>Welcome, {auth.user.username}</p>
    </>
  );
}

.
Si bien funciona bien, no es muy conveniente hacer esto cuando tenemos demasiadas rutas privadas; puede volverse tedioso.
.
Entonces, la soluci贸n es crear un componente AuthRoute en el archivo auth.js. Este componente verifica si el usuario est谩 logueado y si lo est谩 entonces lo hace navegar al login, y sino retorna a props.children.
.

...

function AuthRoute(props) {
  const auth = useAuth();

  if (!auth.user) {
    return <Navigate to="/login" />;
  }

  return props.children;
}

export {
  AuthProvider,
  AuthRoute,
  useAuth,
};

.
Utilizamos a AuthRoute para envolver los elementos de la p谩gina de perfil. Sin embargo, esto no va a funcionar porque auth.user es null, y por ende no se puede obtener la propiedad username.
.
A煤n si AuthRoute nos deber铆a hacer navegar al home, lo que pasa es que React antes de entrar al c贸digo de AuthRoute revisa primero que todas los elementos y variables de ProfilePage sean accesibles. Por lo cual al no poder acceder a username la aplicaci贸n no responde, y siquiera estar铆amos entrando al c贸digo de AuthRoute para navegar al home.
En otras palabras, cuando React intenta renderizar ProfilePage, primero ejecuta todo el c贸digo dentro de 茅l. Esto incluye la llamada a useAuth y el intento de acceder a auth.user.username.
.
Si auth.user no est谩 definido (es null), el intento de acceder a username causar谩 un error que detendr谩 la ejecuci贸n de ProfilePage. Esto significa que React no continuar谩 ejecutando el c贸digo dentro de ProfilePage despu茅s de ese punto. Como resultado, el c贸digo dentro de AuthRoute nunca se alcanza y, por lo tanto, la redirecci贸n a la p谩gina de inicio no se activa.
.

function ProfilePage() {
  const auth = useAuth();
  
  return (
    <AuthRoute>
      <h1>Perfil</h1>
      <p>Welcome, {auth.user.username}</p>
    </AuthRoute>
  );
}

.
Una soluci贸n r谩pida ser铆a utilizar el optional chaining, sin embargo sigue sin ser la mejor soluci贸n.
.

<p>Welcome, {auth.user?.username}</p>

.
La mejor forma para solucionar esto es asumir que auth.user.username siempre estar谩 disponible, y no utilizar el AuthRoute en ProfilePage.
.
Entonces, desde App vamos a envolver el componente que va renderizar la propiedad element mediante el componente AuthRoute.
.

<Routes>
<Route path="/" element={<HomePage />} />
	...
  <Route
	  path="/logout"
    element={
	    <AuthRoute>
	      <LogoutPage />
      </AuthRoute>
    }
  />
  <Route
	  path="/profile"
	  element={
		  <AuthRoute>
			  <ProfilePage />
		  </AuthRoute>
	  }
  />
  ...
</Routes>

.
De esta forma ya no es necesario modificar el ProfilePage o ning煤n otro componente envuelto en AuthRoute desde App.
.

function ProfilePage() {
  const auth = useAuth();
  
  return (
    <>
      <h1>Perfil</h1>
      <p>Welcome, {auth.user.username}</p>
    </>
  );
}

.
Por 煤ltimo, queda proteger a la p谩gina de login cuando estemos autenticados. Entonces, es suficiente crear un condicional dentro de LoginPage para navegar al profile del usuario. Lo hacemos de esta manera solo porque es un caso particular de esta ruta.
.

function LoginPage() {
	...
  if(auth.user){return<Navigate to="/profile"/>}
  
  return (
    ...
  );
}

Nota:
A los que estaban usando BrowserRouter no les va a salir la solicion del profe, tendran que cambiar por HashRouter para que salga.
Si desean que funcione con BrowserRouter tendria que hacer uso de localStorage

el problema del minuto 6:32 creo que es porque en ves de llamar a auth.user le agrega una propiedad undefined en este caso auth.user.username

Mi solucion fue esta:

Mi solucion fue esta: 
<ProtectRoute>
  {!!userName ? (
    <h2>Bienvenido {userName}</h2>
  ) : (
    <p>Porfavor logueate!</p>
  )}
</ProtectRoute>

incluso una render function me parecer铆a mas limpia para este caso

Que bueno, este error me toco en algunas paginas, ahora no recuerdo bien cuales, pero al estar logeado tenia la opcion de login

No funciona porque a pesar de que si hace el redireccionamiento hacia login, igual sigue leyendo el resto del codigo del componente y cuando un usuario no se ha logeado, ese auth.user es null y en la linea 10 est谩 intentando leer una propiedad que es nula