Version v1.1.0 Released! 🎉 — Checkout the new features at GitHub

Integrar Auth0 con NextJS

itscarloescp

Carlos Castillo / April 10, 2021

11 min read

Utilizar Auth0 con NextJS

Auth0 es un servicio que nos permite implementar funcionalidades de autenticación y autorización en nuestras aplicaciones de manera sencilla. Entre sus ventajas se encuentran la seguridad, la facilidad de utilización tanto para el usuario como para el desarrollador.

Los diferentes productos de Auth0 nos ofrecen entornos de trabajo óptimos para cada una de las opciones que deseemos utilizar, y además también existen muchas formas en las que nosotros podemos configurar dichos servicios según nuestas necesidades, sin perder los beneficios que nos proveen las opciones que ya trae por defecto.

En este post explicaré cómo implementar las funcionalidades de autenticación y autorización básica en nuestras aplicaciones web, utilizando NextJS como frontend y backend.

Podés encontrar el código finalizado de este ejemplo en mi repositorio Tutorials.

Aplicación de NextJS

NextJS es un framework basado en React, que nos facilita la creación de "single page applications". Está pensado para mejorar el rendimiento, la seguridad y SEO de nuestras aplicaciones, así como también la experiencia de desarrollo. Otra de sus ventajas es acercar la integración de frontend y backend, ya que NextJS nos permite construir serverless functions que funcionan a manera de backend, directamente desde nuestra aplicación.

Para iniciar una aplicación básica de NextJS, corremos el siguiente comando:

npx create-next-app auth0-nextjs

Esto crea nuestra aplicación dentro del folder llamado auth0-nextjs en la carpeta en la que hayamos corrido el comando. Finalmente, abrimos esta nueva carpeta dentro de nuestro editor de código preferido.

Antes de iniciar a correr la aplicación, debemos borrar algunos archivos que se nos generaron al momento de crear nuestra aplicación. Primero borramos todo lo que esté dentro de la carpeta styles a excepción del archivo globals.css.

También borramos todos los archivos que estén dentro de la carpeta /pages/api, sin borrar la carpeta, ya que la utilizaremos más adelante. Finalmente podemos borrar todo lo que está en el archivo /pages/index.js y en su lugar colocamos lo siguiente:

const index = () => <div>Tutorial Spotify - NextJS</div>

export default index

Luego vamos a agregar botones de inicio de sesión y una sección que mostrará algunos datos del usuario que se encuentra loggeado.

Configuración de Auth0

Para poder consumir los servicios de Auth0 tenemos que registrar la aplicación (o aplicaciones) desde las cuales querramos utilizarlos. Esto lo podemos hacer desde el dashboard de nuestro cuenta de Auth0 (si no tenemos cuenta podemos crearla de manera gratuita acá).

Al momento de escribir este artículo, podemos registrar una aplicación accediendo a la opción "Applications" bajo el menú del mismo nombre, ubicada en la barra de navegación principal de la pantalla de inicio de nuestro perfil de Auth0. Una vez estamos en dicha página, procedemos a registrar una nueva aplicación.

Es imporante que el tipo de aplicación que seleccionemos sea "Regular Web Application" en vez de "Single Page Application", ya que consumiremos algunos servicios desde nuestro backend.

Una vez dentro del panel de configuración de la aplicación que creamos, debemos acceder a la pestaña "Settings" de nuestra app. Ahí vamos a encontrar información importante para conectar esta aplicación registrada en Auth0 con nuestra aplicación de NextJS. De esta pestaña nos interesan tres valores: "Domain", "Client ID" y "Client Secret". Estas son llaves que nos permitirán acceder a nuestra aplicación de Auth0 de manera segura desde nuestra aplicación web.

Para guardar estas llaves, debemos crear un archivo llamado .env.local en la raíz de nuestra aplicación web. Dicho archivo almacenará las variables de entorno en nuestra aplicación, que son datos que solo pueden ser leídos por el servidor, de tal manera que escondemos sus valores al público. Creamos el archivo y colocamos las siguientes llaves:

  • SECRET: Debe ser un valor con el que vamos a encriptar nuestras request. Podemos generarla de manera aleatoria con randomkeys, o incluso solo escribir cualquier cosa.
  • BASE_URL: Su valor corresponde al URL de nuestra aplicación. En esta caso http://localhost:3000.
  • ISSUER_BASE_URL: Este es el valor llamado "Domain" que obtuvimos de nuestro dashboard en nuestra aplicación de Auth0.
  • CLIENT_ID y CLIENT_SECRET: Son los valores de "Client ID" y "Client Secret" que obtuvimos de Auth0.

Simplemente pegamos nuestros valores en el archivo:

.env.local
AUTH0_SECRET=...
AUTH0_BASE_URL=http://localhost:3000
AUTH0_ISSUER_BASE_URL=https://...
AUTH0_CLIENT_ID=...
AUTH0_CLIENT_SECRET=...

Debemos agregar https:// delante del "Domain" que nos proporciona Auth0.

Finalmente, debemos agregar dos valores para terminar de configurar nuestra aplicación. Dentro de la misma pestaña "Settings" (donde encontramos nuestras llaves privadas) nos vamos a la sección "Application Login URI". En los siguientes campos colocamos los valores:

  • Allowed Callback URLs: http://localhost:3000/api/auth/callback
  • Allowed Logout URLs: http://localhost:3000/

Recordemos guardar los cambios en Auth0, de lo contrario los perderemos.

Implementando autenticación en nuestra app

Ahora que ya tenemos configurado todo lo que necesitamos, vamos a iniciar a consumir los servicios de Auth0 a los que tenemos acceso desde NextJS. Para esto instalaremos una librería que nos va a facilitar mucho el uso de Auth0. ra Corremos el siguiente comando dentro del directorio de nuestra aplicación:

npm install @auth0/nextjs-auth0

Luego de que el paquete se instale podemos iniciar nuestro servidor local con el comando npm run dev, y podemos visualizar nuestra aplicación desde el navegador accediendo al URL localhost:3000.

La librería que acabamos de instalar nos proporciona diversas funciones y hooks que se adaptan a la forma en la que funciona NextJS, dividiéndose en funciones que podemos utilizar desde el backend y aquellas que podemos utilizar desde el lado del cliente. NextJS puede almacenar serveless functions y asignarle rutas dentro de nuestra aplicación, capacidad que Auth0 aprovecha para autogenerarnos los endpoints principales. Para esto simplemente debemos crear un nuevo archivo llamado [...auth0].js dentro de una nueva carpeta llamada auth a su vez contenida dentro de la carpeta pages, e incluímos lo siguiente:

/pages/api/auth/[...auth0].js
import { handleAuth } from '@auth0/nextjs-auth0';

export default handleAuth();

Este archivo se encarga de crear cuatro funciones y cuatro endpoints, que corresponden a las acciones de login, logout, la función callback que se ejecturá tras la llamada a los endpoints /api/auth/login o api/auth/logout, y finalmente el endpoint llamado /api/auth/me que nos proporciona información del usuario activo.

Si quisiéramos modificar de alguna forma el comportamiento por defecto de estas funciones, podemos reescribirlas y Auth0 las identificará de manera automática. Podemos referirnos al repositorio oficial para más información sobre cómo configurar estas funciones.

Una vez creadas estas rutas, podemos acceder a ellas para ejectuar sus acciones correspondientes. En el caso de nuestra aplicación, vamos a utilizar un botón para acceder a las rutas de login y logout. Para esto debemos determinar si actualmente hay un usuario en la aplicación, y decir qué botón mostrar en dependencia de esto.

Podemos obtener información de la sesión actual con el context que nos provee NextJS para aplicaciones de React, para lo que debemos envolver nuestra aplicación con el componente <UserProvider /> lo que hacemos de la siguiente manera:

/pages/_app.js
import { UserProvider } from '@auth0/nextjs-auth0'
import '../styles/globals.css'

const MyApp = ({ Component, pageProps }) => {
  return (
    <UserProvider>
      <Component {...pageProps} />
    </UserProvider>
  )
}

export default MyApp

Esto nos permite acceder a diferentes hooks desde el resto de nuestra aplicación, incluyendo userUser, que nos proporciona información básica sobre el usuario que está en nuestra aplicación, o nos devuelve null si no hay ninguno actualmente. De esta forma vamos a determinar la estructura de nuestra página principal:

/pages/index.js
import { useUser } from '@auth0/nextjs-auth0'
import Link from 'next/link'

const index = () => {
  const { user, error, isLoading } = useUser()

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>{error.message}</div>

  if (user) {
    return (
      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
          textAlign: 'center',
        }}
      >
        Bienvenido {user.name} <a href='/api/auth/logout'>Logout</a>
        <Link href='/session'>
          <a>Más información</a>
        </Link>
      </div>
    )
  }

  return <a href='/api/auth/login'>Login</a>
}

export default index

Con esto ya podemos acceder a la funcionalidad de login desde la interfaz. Podemos registrar una nueva cuenta si lo necesitamos o acceder con una cuenta ya existente, y por defecto también podemos acceder con nuestra cuenta de Google, aunque podemos agregar muchas otras cuentas de terceros a través de Auth0.

Una vez accedemos, vamos a ser redirigidos a la página principal de nuestra app y ya vamos a tener el valor correspondiente en el objeto user que nos proporciona el hook que usamos anteriormente, y por lo tanto, mostramos la información correspondiente a esta condición.

Página protegidas

Otro de los hooks que nos proporciona esta librería es withPageAuthRequired, que nos permite renderizar una página únicamente si quien la intenta acceder es un usuario válido. Este hook aprovecha una de los diferentes tipos de renderizado de NextJS, getServerSideProps, que construye cada página al momento en el que el cliente hace la request por dicha página.

Para esto vamos a crear un nuevo archivo llamado session.js dentro de la carpeta /pages/. Dentro de este archivo vamos a crear un botón que nos permita obtener información adicional de la sesión, accesible solo desde un entorno seguro en el backend. Copiamos el siguiente código y lo pegamos en el archivo que acabamos de crear:

/pages/session.js
import { useState } from 'react'
import Link from 'next/link'
import { withPageAuthRequired } from '@auth0/nextjs-auth0'

const info = () => {
  const [session, setSession] = useState({})

  const getSession = async () => {
    const res = await fetch('/api/auth/getSession')
    const data = await res.json()
    setSession(data)
  }

  return (
    <div>
      <button onClick={getSession}>Get info</button>
      <div>
        <pre>{JSON.stringify(session, null, 2)}</pre>
      </div>
      <Link href='/'>Volver</Link>
    </div>
  )
}

export const getServerSideProps = withPageAuthRequired()
export default info

La función getSession hace una llamado a una API route que crearemos a continuación. La respuesta a este llamado es almacenada en la variable session, que luego se muestra en pantalla.

Enpoints protegidos

También disponemos de una función que nos permite proteger APIs, haciendo que las mismas solo puedan recibir requests si son llamadas desde una sesión activa. Para esto, vamos a crear la función que corresponde a la ruta que llamamos en nuestro componente session.js. Debemos crear otro archivo llamado getSession.js en la carpeta /pages/api/, misma carpeta donde agregamos el archivo que hace el llamado inicial a Auth0. En este archivo agregamos lo siguiente:

/pages/auth/getSession.js
import { withApiAuthRequired , getSession } from '@auth0/nextjs-auth0'

export default (req, res) => {
  const session = getSession(req, res)
  const idToken = session.idToken.split('').splice(0, 40).join('') + '...'
  const picture =
    session.user.picture.split('').splice(0, 40).join('') + '...'

  res.json({
    ...session,
    idToken,
    user: {
      ...session.user,
      picture,
    },
  })
}

Acá estamos utilizando dos funciones de Auth0 que solo pueden ser utilizadas del lado del servidor. Por una parte withApiAuthRequired bloquea los llamados sin autenticación, de manera similar como lo hace withPageAuthRequired con las páginas. La segunda función, getSession se encarga de obtener información adicional sobre la sesión actual utilizando los objetos request y response que se pasan automáticamente al momento de hacer el llamado a esta ruta. Las variables idToken y picture corresponden a las variables del mismo nombre que nos proporciona el objeto session, sin embargo están acortadas ya que sus valores son muy largos para mostrarlos en una sola línea desde la interfaz.

Más configuración

El cliente de Auth0 para NextJS también nos proporciona algunas otras funciones que podemos utilizar, principalmente del lado del backend. Podemos echarle un vistazo a la utilidad y configuración de dichas funciones a través de la documentación oficial.

Sin embargo no estamos limitados a la funcionalidad que nos provea esta librería. Podemos utilizar las funcionalidades propias de Auth0 para modificar el funcionamiento de nuestro proceso de autenticación y autorización de cualquier manera, a nuestro gusto, como cambiar la base de datos, o crear rules que se ejecuten como serverless functions tras la autenticación de un usuario, entre muchas otras opciones.

Muchas de estas cosas se pueden configurar directamente desde nuestras consolas en el dashboard de Auth0, sin embargo, también podemos utilizar Management API, que nos permite consumir funcionalidad aún mas compleja a través de nuestra aplicaciones, como por ejemplo, tener la capacidad de crear, borrar, modificar o bloquear usuarios, administrar las cuentas registradas, obtener más información de la que nos provee la SDK para NextJS, cambiar las contraseñas de una cuenta, actualizar rules, y muchas de las otras acciones que podemos ejectuar através del dashboard, pero ahora accesibles desde nuestras aplicaciones.

Gracias a la extensiva documentación de todos los servicios que nos provee Auth0, podemos construir aplicaciones que utilicen esta funcionalidad de manera sencilla y con un gran soporte. Auth0 incorpora todos los estándares globales en cuanto a seguridad y formato en muchas de sus respuestas como sistemas de ingreso seguro sin contraseñas o respuestas según el estándar JWT. Esto convierte a Auth0 en una de las opciones más completas para implementar esta funcionalidad, y permite desarrollar de manera más sencilla tanto interfaces como APIs de mayor calidad y robustés.