He estado tratando de implementar un flujo de autenticación confiable para un proyecto Next.js, pero ahora estoy completamente perdido. Ya he visto el repositorio de ejemplos de Next.js. Pero tengo muchas preguntas para una solución completa.
Tengo una API express.js y un proyecto frontend Next.js separado. Todos los datos y la autenticación son manejados por la API. Frontend solo muestra las páginas con SSR. Si solo creara un proyecto monolítico, donde la representación de las páginas y todos los datos son manejados por un solo servidor (me refiero a una opción de servidor personalizado para Next.js), solo usaría express-session y csurf. Sería una forma tradicional de administrar sesiones y crear seguridad contra CSRF.
La API de Express.js no es un requisito. Es solo un ejemplo. Podría ser una API de Django o una API de .Net Core. El punto principal es que es un servidor separado y un proyecto separado.
¿Cómo puedo tener una estructura simple pero confiable? He examinado algunos de mis sitios web favoritos (netlify, zeit.co, heroku, spectrum.chat, etc.). Algunos de ellos usan almacenamiento local para almacenar tokens de acceso y actualización (vulnerable a XSS). Algunos de ellos usan cookies y ni siquiera son HTTPOnly (tanto XSS como CSRF vulnerables). Y ejemplos como el espectro.chat usan la forma que mencioné anteriormente (sesión de cookies + prevención de csrf).
Sé que existe un gran revuelo en torno a los tokens JWT. Pero los encuentro demasiado complejos. La mayoría de los tutoriales simplemente se saltan la caducidad, la actualización de tokens, la revocación de tokens, las listas negras, las listas blancas, etc.
Y muchos de los ejemplos de cookies de sesión para Next.js casi nunca mencionan CSRF. Honestamente, la autenticación siempre es un gran problema para mí. Un día leí que solo se deben usar cookies HTTP, al día siguiente veo un sitio popular gigante que ni siquiera las usa. O dicen "nunca almacene sus tokens en localStorage", y boom algún proyecto gigante solo usa este método.
¿Alguien puede mostrarme alguna dirección para esta situación?
He tenido que pensar en esto también para mi proyecto actual. Utilizo las mismas tecnologías: una API ExpressJS y un front-end renderizado del lado del servidor NextJS.
Lo que elegí hacer es usar pasaporte.js en la API de ExpressJS. TheNetNinja en YouTube tiene una muy buena lista de reproducción con 21 episodios. Él le muestra cómo implementar Google OAuth 2.0 en su API, pero esta lógica se transfiere a cualquier otra estrategia (JWT, correo electrónico + contraseña, autenticación de Facebook, etc.).
En el front-end, literalmente redirigiría al usuario a una URL en Express API. Esta URL le mostraría al usuario la pantalla de Google OAuth, el usuario hace clic en "Permitir", la API hace algunas cosas más, crea una cookie para el usuario específico y luego lo redirige a una URL en la interfaz. Ahora, el usuario está autenticado.
Acerca de las cookies HTTPOnly: Elegí desactivar esta función porque estaba almacenando información en la cookie que necesitaba en el front-end. Si tiene esta característica habilitada, entonces el front-end (javascript) no tiene acceso a esas cookies, porque son HTTPOnly.
Aquí está el enlace a la lista de reproducción de la que estaba hablando: https://www.youtube.com/watch?v=sakQbeRjgwg&list=PL4cUxeGkcC9jdm7QX143aMLAqyM-jTZ2x
Espero haberte dado una dirección que puedas tomar.
EDITAR: No he respondido a su pregunta sobre CSURF, pero eso es porque no estoy familiarizado con él.
¡Finalmente he encontrado una solución!
Ahora estoy usando el paquete csrf npm, no csurf. csurf simplemente convierte a csrf en un middleware express.
Entonces, creo un csrfSecret en getInitialProps de _app. Crea el secreto, lo establece como una cookie httpOnly. Más tarde, crea un csrfToken y lo devuelve con pageProps. Entonces, puedo acceder a él con window. NEXT_DATA .props.csrfToken. Si el usuario actualiza la página, csrfSecret permanece igual, pero csrfToken se renueva.
Cuando realizo una solicitud a la ruta API "/api/graphql con proxy, primero obtiene el token csrf del encabezado x-xsrf-token y lo verifica con el valor de la cookie csrfSecret. Después de eso, extrae el valor de la cookie authToken y pasa a la API de GraphQL real.
La API está basada en tokens. Solo necesita un token de acceso que no caduque. (Por cierto, no es necesario que sea JWT. Se puede usar cualquier token aleatorio criptográficamente fuerte. Lo que significa un token de referencia/opaco).
La verificación CSRF no es necesaria para la API real, porque no depende de las cookies para la autenticación. Solo verifica el encabezado de autorización. Tanto authToken como csrfSecret son cookies httpOnly. Y ni siquiera los almaceno en la memoria del lado del cliente.
Creo que esto es lo más seguro que pude conseguir. Ahora estoy contento con esta solución.
Descargo de responsabilidad: soy un mantenedor del paquete gratuito de código abierto a continuación, pero creo que es apropiado aquí, ya que es una pregunta común para la que no hay una gran respuesta, ya que muchas de las soluciones populares tienen las fallas de seguridad específicas planteadas en la pregunta ( como no usar CSRF cuando corresponda y exponer tokens de sesión o tokens web al JavaScript del lado del cliente).
El paquete NextAuth.js intenta abordar los problemas planteados anteriormente, con un software gratuito de código abierto.
httpOnly con secure .__HOST- o __Secure ). p.ej. page/api/auth/[...nextauth.js]
import NextAuth from 'next-auth' import Providers from 'next-auth/providers' const options = { providers: [ // OAuth authentication providers Providers.Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }), Providers.Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }), // Sign in with email (passwordless) Providers.Email({ server: process.env.MAIL_SERVER, from: '<no-reply@example.com>' }), ], // MySQL, Postgres or MongoDB database (or leave empty) database: process.env.DATABASE_URL } export default (req, res) => NextAuth(req, res, options) por ejemplo pages/index.js
import React from 'react' import { useSession, signin, signout } from 'next-auth/client' export default () => { const [ session, loading ] = useSession() return <p> {!session && <> Not signed in <br/> <button onClick={signin}>Sign in</button> </>} {session && <> Signed in as {session.user.email} <br/> <button onClick={signout}>Sign out</button> </>} </p> }Incluso si elige no usarlo, puede encontrar el código útil como referencia (por ejemplo, cómo se manejan los tokens web JSON y cómo se rotan en las sesiones) .