Añadir autenticación en Nextjs es un proceso que puede llegar a confudir a muchos Desarrolladores que estan empezando con el framework, al haber varias formas de implementarlo. Esto es asi porque Next es un framework que puede ejecutar tanto logica de Servidor como logica de lado Frontend. Asi que hay muchos enfoques como poder implemtar JWT, sesiones, cookies, localstorage, etc.
Pero actualmente Gracias a la comunidad tenemos un paquete llamado next-auth, el cual nos permite añadir varios metodos de autenticación a nuestras aplicaciones de Nextjs, ya sea usando correo, o OAuth. Esto quiere decir que puedes permitirle a tus usuarios que puedan hacer login con sus cuentas de plataformas como Google, Facebook, Twitter, Instagram, Github, y muchas otras más.
Requerimientos del Tutorial
Para este tutorial es necesario que tengas bases en Nextjs
¿Que desarrollaremos?
En este tutorial vamos a crear una aplicacion de Nextjs, y permitir a nuestros usuarios que hagan login y signup con su cuenta de Github, ademas de proteger rutas usando NextAuthjs.
NextAuth.js tambien permite que crear autenticación de usarios usando tu propio backend, pero el enfoque de NextAuth.js es que uses OAuth2 principalmente para que añadas autenticación, en lugar de implementar todo desde cero, esto es asi para que te evites la complejidad y tambien porque muchos metodos auto implementados pueden llegar a ser inseguros en comparacion a OAuth https://next-auth.js.org/providers/credentials
Configuracion del Proyecto
Empecemos con nuestro ejemplo. Primero creemos un proyecto de Nextjs, usando create-next-app:
npx create-next-app next-auth-tutorial
cd next-auth-tutorial
Instalación de NextAuth.js
Luego instalaremos el modulo de NextAuth.js el cual se llama next-auth:
npm i next-auth
Configurando NextAuth.js
Una vez creado e instalado el modulo next-auth, necesitamos crear algunso archivos para configurarlo.
Crea un archivo [...nextauth].js (o [...nextauth.ts] si usas Typescript) en la siguiente ruta: pages/api/auth/. La ruta completa del archivo seria esta:
pages/api/auth/[...nextauth].js.
Dentro del archivo añade lo siguiente:
import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
export default NextAuth({
// Configure one or more authentication providers
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
// ...add more providers here
],
})
Esta porcion de código basicamente lo que hace es que intentará conectarse con github cada vez que intentemos auteticar un usuario. Pero para que esto puedan funcionar necesitamos crear una aplicacion de Github Primero y obtener un Secret key y un Secret ID:
Puedes obtener estos desde la siguiente dirección:
Las configuraciones requeridas para nuestra aplicación de Github son ls siguientes:
- Application Name: next-tutorial
- callback URL: http://localhost:3000/api/auth/callback/github
Este proceso de registrar una aplicación lo tienen todas las plataformas sociales como Facebook, Google, Twitter, etc. Asi que cada vez que quieras autenticar a tus usuarios con cada uno de estos metodos debes registrar tu aplicación en cada plataforma.
Variables de Entorno
Una vez creada tu aplicación de Github, podremos obtener un Secret Key y un Secret ID.
Y como estos son dos datos que debemos mantener seguros, vamos a definirlos en Variables de entornos, es decir vamos a añadirlos en un archivo .env.
Crea un archivo .env.local en la raiz de tu proyecto. Las variables de entorno deben lucir parecidas a esto:
GITHUB_ID=ab91d3e39cd5943fa28x
GITHUB_SECRET=2b5c75bb750df2346eaeab29d789cd63d5ef8c59
Recuerda reiniciar el servidor una vez hayas terminado de añadir tus variables de entorno.
Para saber más de Variables de Entorno en Nextjs: https://nextjs.org/docs/basic-features/environment-variables
Listo con esto ya tienes configurado tu proyecto, y ahora tu aplicación permitirá a tus usuarios hacer login con Github a traves de OAuth.
NextAuth.js REST API
Ya tenemos configurado NextAuth.js, pero ¿Como lo utilizamos?. Lo primero que debemos entender es que este paquete crea unas Rutas de servidor que se comunican con las platafomras sociales. A estas rutas de servidor se le conocen en desarrollo backend como una REST API (Representational State Transfer Application Programming Interface).
Es decir que podemos empezar a usar NextAuth.js viistando los siguientes enlaces:
- http://localhost:3000/api/auth/signin, esta URL permite mostrarte una lista de opciones de como autenticarte. En nuestro caso solo aparecerá Github. Una vez debemos en Aceptar o Declinar, nos redireccionara a nuestra aplicacion.
- http://localhost:3000/api/auth/signout, esta quita las cookies o sesiones del usuario.
- http://localhost:3000/api/auth/signin/:provider, permite autenticar a nuestro usario con una plataforma especifica (Ejemplo: Facebook, Google, Github, etc.) sin mostrar un menu de opciones.
Ahora para probar estas rutas, por lo general las REST APIs necesitan de aplicaciones como los Clientes REST (por ejemplo Postman, Imsomnia, Curl, etc.) Pero lo genial de este modulo es qu ya tiene una interfaz simple que podemos usar para empezar. Luego aprenderemos a crear nuestra propio interfaz.
Para conocer más de las otras rutas, tienes la documentación de NexAuth.js https://next-auth.js.org/getting-started/rest-api
/api/auth/signin
Si visitamos la URL /api/auth/signin esta nos mostrará una interfaz de Github en la que nos pregunta si queremos otorgar permisos a esta aplicacion. Si le damos en aceptar nos redireccionará a devuelta a nuestra aplicación. Y si revisamos nuestro sitio con las herramientas de desarrollador veremos que ahora en cookies, tenemos 3 cookies dedicadas a almacenar un token, el callback url y un crsf token. basicamente esto quiere decir que nuestra aplicacion ahora tiene los datos del usuario como su correo, displayName e imagen de perfil.
/api/auth/signout
De la misma forma que tenemos /signin, teemos /signout lo que limpia nuestra sesion o quita las cookies que antes habiamos visto.
/api/auth/signin/:provider
con esta ruta podemos autenticar un usuario directamente con una plataforma en lugar de mostrarle esas opciones iniciales.
En nuestro caso tan solo tenemos Github por lo que la ruta sera:
- /api/auth/signin/github
getSession, Usando la sesion del Usuario.
Ahora aprenderas a usar esta información del Usuario, para esto puedes usar el metodo de next-auth llamado getSession() este es un metodo que te permite como su nombre indica obtener la información de la sesion del usuario que dio permisos.
Esta sera null si no tiene datos o tendra datos como estos si un usuario dio permisos:
{
user: {
name: 'Fazt',
email: 'fazt@faztweb.com',
image: 'https://avatars.githubusercontent.com/u/13667358?v=4'
},
expires: '2022-04-02T00:00:36.362Z'
}
para poder obtener estos datos, tenemos que ejecutar la funcion de forma asincrona.
const session = await getSession();
console.log({session});
Esta funcion funciona tanto desde el lado Frontend como el lado Backend. Si se usa del lado frontend puede llamarse desde un useEffect, por ejemplo:
useEffect(() => {
(async () => {
const session = await getSession();
console.log({ session });
})();
}, []);
Y si es llamado desde el backend, puedes usar la funcion getServerSideProps y pasandole el contexto o la propieda req como argumento:
export const getServerSideProps = async (context) => {
const session = await getSession(context);
console.log(session);
return {
props: {},
};
};
Muchas funciones de NextAuth.js funcionan tanto desde el backend como desde el frontend, pero siempre es mejor asegurarse viistando la documentación.
Para aprender más de getSession https://next-auth.js.org/getting-started/client#getsession
useSession
usando el metodo getSession() se puede obtener la sesión del ususario, que si bien es fácil de obtener desde el backend, en el frontend puede llegar a ser algo tedioso tener que llamar un useEffect y un useState. Es por esto la biblioteca provee un metodo llamado useSession el cual es un hook que permite obtener la session del usuario de forma sencilla.
Este hook tan solo esta disponible desde el Frontend, No el backend.
Para poder usar este metodo primero tenemos que añadir un SessionProvider, el cual es un contexto de nuestra aplicación.
Ve en el archivo _app.js y añade lo siguiente:
import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps }) {
return (
<SessionProvider>
<Component {...pageProps} />
</SessionProvider>
);
}
export default MyApp;
Luego, podras llamar desde cualquier archivo de tu proyecto el metodo useSession, por ejemplo en index.js:
import { useSession} from "next-auth/react";
function HomePage() {
const { data } = useSession()
console.log(data)
return <div>HomePage</div>;
}
Si ves la consola del Frontend podras notar la sesion del usuario. Pero como esta funcion es de frontend y hace una peticion a un backend, demora algo en obtener la respuesta, por lo que provee tambien una propiedad de status, la cual nos va informacion del estado de peticion. Esta posee tres estados:
- loading
- authenticated
- unauthenticated
Podriamos verlo con el siguiente código:
function HomePage() {
const { data: session, status } = useSession();
console.log(session, status);
return <div>HomePage</div>;
}
Puedes notar ademas que tambien cambie el nombre de data a sesion esto es asi por la sugerencia de la propia documentación: https://next-auth.js.org/getting-started/client#usesession
Usando estos estados podemos renderizar distintas interfaces o redireccionar a distintas partes.
import { useSession } from "next-auth/react";
import Image from "next/image";
import { useRouter } from "next/router";
function IndexPage() {
const { data: session, status } = useSession();
const router = useRouter();
console.log(session, status);
if (status === "loading") {
return <p>Loading...</p>;
}
if (status === "unauthenticated") {
router.push("/login");
}
return (
<main>
<h1>Dashboard</h1>
{session ? (
<div>
<h1>{session.user.name}</h1>
<p>{session.user.email}</p>
<Image
src={session.user.image}
alt={session.user.name}
width={30}
height={30}
/>
</div>
) : (
<h1>Skeleton</h1>
)}
</main>
);
}
export default IndexPage;
o tambien puedes añadir esto para redireccionar a otra vista si el usuario no esta autorizado.
if (!session && status !== "loading") {
router.push("/login");
}
Signout Personalizado
Ahora añadamos un boton de Logout, para poder cerrar sesion:
import { signOut } from "next-auth/react";
<button onClick={() => signOut()}>Logout</button>
Signin Personalizado
en login.js
<button onClick={() => signIn()}>Login with Github</button>
pero esto es lo mismo que visitar /api/auth/signin
Providers
Los providers son los distintos metodos en los que un usuario puede autenticarse en nuestra aplicación:
import {getProviders} from 'next-auth/react'
...
useEffect(() => {
(async () => {
const providers = await getProviders();
console.log(providers);
})();
}, []);
Signin Custom
en login.js
<button onClick={() => signIn('github')}>Login with Github</button>
tambien puedes añadir este hook, para retornarlo si en caso ya se logeo, y no quieres que vea la pagina del login otra vez:
useEffect(() => {
if (data) {
router.push("/");
}
}, [data, router]);
Para saber más de la configuracion inicial:
Server Side Áuhtenitcation
el problema con el enfoque anterior es que es autenticaion del lado cliente, lo que quiere decir que cualquier usuario que visita la url, vera la interfaz y luego sera redireccionado. este esl tipico problma de ver una parpadeo con la interfaz antes de redireccionar.
Para solucionar o tener una alternativa a este enfoque podemos autenticar al usuario desde lado servidor usando la funcion getServerSideProps de next.js, la cual procesa datos antes de renderizar la pagina.
Y en cuanto a la sesion del usuario, podemos usar el metodo getSession() de next-auth para poder saber si el usuario esta autenticado o no.
export const getServerSideProps = async (context) => {
const session = await getSession(context);
console.log(session);
if (!session) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}
return {
props: {
session,
},
};
};
Api
al ser getSession un metodo de backend, tambien es posible usarlo desde la carpeta api, al ser estas codigo de backend.
api/hello.js
import { getSession } from "next-auth/react";
export default async function handler(req, res) {
const session = await getSession({ req });
console.log(session);
if (!session) return res.status(403).send("Anauthorized");
res.status(200).json(session.user);
}
Pages
/api/auth/login
/api/auth/logout
/api/auth/signup
/api/auth/callback
Adapters
En este ejemplo estamos usando un solo adapter que es el de Mongodb, pero NextAuth soporta muchos e incluso puedes añadir tus propios personalizados.
- https://next-auth.js.org/adapters/overview
- https://next-auth.js.org/tutorials/creating-a-database-adapter