Imagen destacada del post -WordPress Headless: Autenticación con Next.js 2-
Última actualización:

WordPress Headless: Autenticación con Next.js 2


¿Hola que tal? 🙋‍♂️. En el post anterior vimos como hacer una autenticación con Next.js aprovechando el api de WordPress pero utilizando el Pages Router de Next.js. Esta vez haremos lo mismo pero con el App Router que es la forma recomendada desde la versión 13 del framework.

Como la forma de instalar WordPress no ha cambiado nos la vamos a saltar en este post pero puedes ir a la entrada anterior y seguir el tutorial si no tienes instalado ya lo necesario.

Vamos a crear el proyecto de Next, instalar las dependencias y configurarlo. Vete a la consola y ejecuta el siguiente comando:

npx create-next-app@latest

A continuación vamos a responder a las preguntas de la siguiente manera:

 What is your project named?  wp-headless-nextjs-2
 Would you like to use TypeScript?  No
 Would you like to use ESLint?  Yes
 Would you like to use Tailwind CSS?  Yes
 Would you like to use `src/` directory?  Yes
 Would you like to use App Router? (recommended) … Yes
? Would you like to customize the default import alias (@/*)? › No

Ahora vamos a instalar las dependencias para los estilos con el siguiente comando:

npm install @headlessui/react @heroicons/react @tailwindcss/forms

En el archivo de configuración de Tailwind tailwind.config.js añadimos la carga del plugin de formularios así:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [
    require('@tailwindcss/forms')
  ],
}

Bien una vez instalado lo necesario para los estilos vamos a pararnos para tener algo en consideración con la librería que vamos a utilizar de autenticación next-auth. A día de hoy hay dos formas de hacer este proceso, una es con la versión 4 y otra con la versión 5@beta. Como puede que en el futuro te encuentres con código de diferentes maneras de hacerlo, en este tutorial trataremos la versión 4 y en el próximo la 5. Lo único que tendrás que tener en cuenta es a la hora de instalar la librería el comando a utilizar, en este momento para instalar la versión 4 es npm install next-auth si esto cambia en el futuro utiliza npm install next-auth@4.

Antes de empezar necesitas tener claro la diferencia principal entre el App Router y el anterior Pages Router. Con la nueva forma de rutas se introduce un concepto de React llamado Server Components, esto significaría que por defecto si no le indicamos lo contrario (ya veremos cómo más adelante) nuestros componentes saldrán ya renderizados desde el servidor a diferencia de antes que este proceso ocurría parcialmente en el cliente. Para ejemplificar el concepto “parcialmente” digamos que anteriormente cuando hacíamos Server Side Rendering aparte de escribir el componente declarábamos la función getServerSideProps para pasar los datos del servidor por propiedades del componente, esto en la práctica lo que hace es un pre-render del HTML en el servidor y luego hace lo que llamamos proceso de hidratación con los datos que se le envía por props.

export default function Page({ data }) {
  // Render data...
}
 
// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()
 
  // Pass data to the page via props
  return { props: { data } }
}

Una de las ventajas de este nuevo sistema es que podemos decidir que componentes serán de renderizado en el cliente declarando al principio del archivo del componente la directiva 'use client', y lo mejor de todo es que podemos pasar propiedades directamente desde server components a client components sin necesidad de hacerlo desde una función aparte como lo hacíamos anteriormente. Esto en mi opinión hace que aumente no solo la performance de nuestras aplicaciones si no la experiencia de desarrollo como veremos más adelante en el tutorial.


A diferencia del tutorial anterior en vez de ir poniendo todos los archivos que tienes que crear con sus códigos te dejo el repositorio para que lo copies y te voy explicando lo importante paso a paso.

Añade el archivo .env.local y rellena las siguientes variables de entorno:

NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=mi-clave-secreta
WP_BACK_URL=http://localhost:8000

Para crear la clave secreta puedes utilizar el comando npx auth secret o openssl rand -base64 32.


Bien vamos con los archivos de las páginas base de la aplicación que como en el tutorial anterior son home, signin y dashboard, que encontraremos esta vez dentro de src/app/ donde la ruta home (/) sería la raíz y las otras en sus respectivas carpetas. A diferencia del pages router donde el punto de entrada de la página era el archivo con el nombre de la propia página o la carpeta con un archivo dentro index.js, en app router tenemos varios puntos de entrada según comportamientos o funcionalidades siendo el archivo page.js el más importante junto con layout.js. Por ejemplo para la home que es nuestro directorio raíz tanto para la navegación web como para nuestra carpeta app, el archivo page.js se comporta como el antiguo index.js pero el layout.js sería un híbrido del antiguo _app.js y _document.js envolviendo toda nuestra aplicación y si te fijas next directamente es donde declara estilos globales, metadata, etc… Esto es importante que lo tengas en cuenta porque el archivo layout.js dentro de carpetas envuelve solo a esa página en concreto pero en la raíz a toda nuestra app.


Los archivos necesarios para nuestro proceso de autenticación se encuentran en las rutas src/server/auth.js | src/services/wp-auth.js | src/app/api/auth/[..nextauth]/route.js. En el primero de los archivos declaramos las opciones de configuración y también creamos una función llamada getServerAuthSession para que en el caso de tener sesión iniciada poder consumir los datos de sesión desde nuestros server components o desde client components pasándolo directamente por propiedades o configurando el SessionProvider de next-auth, en el repositorio hay ejemplos de las dos formas ya lo veremos. El segundo de los archivos es el servicio que se conecta con el API de WordPress para hacer la autenticación este es el único que no ha cambiado desde el tutorial anterior. Y por último declaramos los api endpoints que utiliza next-auth internamente de forma dinámica para hacer las acciones de signin y signout, que a diferencia de la forma anterior (api routes) este utiliza la nueva funcionalidad de app router llamada Route Handlers pudiendo acceder desde el archivo route.js.

Como ya sabemos la finalidad de nuestro sistema de autenticación es privar o proteger algunas páginas de nuestra aplicación a usuarios no autorizados, en nuestro caso dashboard. Para ello si nos dirigimos al archivo de la propia página que esta en la ruta src/app/dashboard/page.jsx, podremos observar que estamos haciendo la protección directamente en el server component:

// src/app/dashboard/page.jsx

import Header from '@/components/layout/DashHeader';
import { getServerAuthSession } from '@/server/auth';
import { redirect } from 'next/navigation';

const DashboardPage = async () => {
  const session = await getServerAuthSession();

  if (!session?.user) {
    return redirect('/signin');
  }

  return (
    <div className="min-h-full">
      <Header session={session} />
      <main>
        <div className="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
          {/* Your content */}
        </div>
      </main>
    </div>
  );
};

export default DashboardPage;

Como vemos si no tenemos sesión iniciada redirigimos directamente a la página signin desde el componente y en el caso de que la tengamos le pasamos la sesión por props a el componente Header que es un client component de manera sencilla y a mi juicio más intuitiva que anteriormente.

De la misma manera si tenemos sesión iniciada no hace falta, y puede hasta que sea anti-producente, que algunas rutas como signin estén disponibles así que las protegemos en este caso redirigiendo a dashboard.

// src/app/signin/page.jsx

import Signin from '@/components/page/Signin';
import { cookies } from 'next/headers';
import { getServerAuthSession } from '@/server/auth';
import { redirect } from 'next/navigation';

export default async function SigninPage() {
  const session = await getServerAuthSession();

  if (session?.user) {
    return redirect('/dashboard');
  }

  const cookieStore = cookies();
  const csrfToken = cookieStore.get('next-auth.csrf-token');

  return <Signin csrfToken={csrfToken?.value} />;
}

La otra forma de consumir los datos de la sesión en un client component que habíamos comentado es con el SessionProvider de next-auth. Para esto hemos creado un wrapper o componente envoltorio al que le pasamos la sesión por props:

// src/components/providers/SessionWrapper.jsx

'use client';

import { SessionProvider } from 'next-auth/react';

const SessionWrapper = ({ children, session }) => {
  return <SessionProvider session={session}>{children}</SessionProvider>;
};

export default SessionWrapper;

De esta forma lo que ofrece el proveedor de next-auth es acceder a un objeto de sesión con información mas completa utilizando su hook useSession. Para ver esto en acción dirígete a los archivos src/app/page.js y src/components/layout/HomeHeader.jsx.


Para resumir como has podido comprobar la diferencia más significativa es la forma en la que le pasamos la información de sesión a los client components que volviéndome a repetir es una manera más limpia e intuitiva.

¡Enhorabuena has llegado al final de tutorial 🥳! Para la próxima entrada veremos el ejemplo de hacer lo mismo con la versión 5 de next-auth esta vez con un concepto también nuevo que son las Server Actions, que es una forma de ejecutar código de servidor incluso desde nuestros client components.