tutoriales.com

¡Autenticación Segura en Next.js! Implementa OAuth con NextAuth.js y Protege tus Rutas 🔒

Este tutorial te guiará paso a paso en la implementación de autenticación segura en Next.js. Explorarás NextAuth.js para gestionar sesiones y autenticación OAuth, protegiendo tus rutas y ofreciendo una experiencia de usuario robusta.

Intermedio15 min de lectura10 views
Reportar error

La autenticación es un pilar fundamental en la mayoría de las aplicaciones web modernas. Permite a los usuarios identificarse, acceder a recursos personalizados y garantiza la seguridad de la información. En el ecosistema de Next.js, existen varias opciones para gestionar la autenticación, pero una de las más robustas y fáciles de integrar es NextAuth.js.

NextAuth.js es una biblioteca completa que simplifica la adición de autenticación a aplicaciones Next.js. Soporta múltiples proveedores OAuth (Google, GitHub, Facebook, etc.), autenticación sin contraseña (Magic Link), JWTs y gestión de sesiones, todo con una configuración mínima y máxima flexibilidad.

En este tutorial, profundizaremos en cómo integrar NextAuth.js con un proveedor OAuth (como Google) para autenticar usuarios y cómo proteger rutas específicas en tu aplicación Next.js.


🎯 ¿Qué aprenderás en este tutorial?

Al finalizar este tutorial, serás capaz de:

  • Configurar un proyecto Next.js para usar NextAuth.js.
  • Integrar un proveedor OAuth (Google) para la autenticación.
  • Gestionar sesiones de usuario y acceder a la información de la sesión.
  • Proteger rutas de tu aplicación para que solo usuarios autenticados puedan acceder a ellas.
  • Implementar botones de inicio y cierre de sesión.
🔥 Importante: Este tutorial asume que tienes conocimientos básicos de React y Next.js, así como de JavaScript/TypeScript.

🛠️ Requisitos Previos

Antes de empezar, asegúrate de tener instalado lo siguiente:

  • Node.js (versión 18 o superior).
  • npm o Yarn (gestor de paquetes).
  • Una cuenta de Google Developer Console (para configurar las credenciales OAuth).

🚀 Paso 1: Configuración Inicial del Proyecto Next.js

Si ya tienes un proyecto Next.js, puedes omitir este paso. De lo contrario, crearemos uno nuevo.

Primero, crea un nuevo proyecto Next.js utilizando create-next-app:

npx create-next-app@latest nextjs-auth-tutorial --typescript --eslint --tailwind --app

Cuando te pregunte, selecciona las opciones predeterminadas o las que prefieras. En este tutorial usaremos TypeScript y el App Router.

Navega a tu nuevo directorio de proyecto:

cd nextjs-auth-tutorial

Ahora, instalaremos NextAuth.js:

npm install next-auth
# o con yarn
yarn add next-auth
💡 Consejo: NextAuth.js es compatible tanto con el `App Router` como con el `Pages Router`, pero en este tutorial nos enfocaremos en el `App Router` debido a su naturaleza moderna y su enfoque en React Server Components.

🔑 Paso 2: Configurar las Credenciales OAuth de Google

Para usar Google como proveedor de autenticación, necesitas crear credenciales de cliente OAuth en la Consola de Desarrolladores de Google.

  1. Ve a la Google Cloud Console: Abre tu navegador y navega a console.cloud.google.com.
  2. Crea un nuevo proyecto (si es necesario): Si no tienes uno, haz clic en el selector de proyectos en la parte superior y luego en Nuevo Proyecto.
  3. Habilita la API de Google People: En el menú de navegación lateral, ve a APIs y Servicios > Biblioteca. Busca Google People API y haz clic en Habilitar.
  4. Crea credenciales OAuth:
    • En el menú de navegación, ve a APIs y Servicios > Credenciales.
    • Haz clic en Crear credenciales > ID de cliente de OAuth.
    • Si es la primera vez, se te pedirá que configures la Pantalla de consentimiento de OAuth. Sigue los pasos para configurar el tipo de usuario (Externo) y la información básica de tu aplicación.
    • Una vez configurada la pantalla de consentimiento, vuelve a Crear credenciales > ID de cliente de OAuth.
    • Selecciona Aplicación web como tipo de aplicación.
    • Dale un nombre a tu cliente OAuth (ej., Next.js Auth App).
    • En Orígenes de JavaScript autorizados, añade http://localhost:3000.
    • En URI de redireccionamiento autorizados, añade http://localhost:3000/api/auth/callback/google.
    • Haz clic en Crear. Se te mostrará el ID de Cliente y el Secreto de Cliente.
⚠️ Advertencia: El `ID de Cliente` y el `Secreto de Cliente` son credenciales sensibles. Nunca los expongas directamente en tu código frontend. Siempre deben ser accedidos desde el servidor o mediante variables de entorno.

📝 Paso 3: Configuración de Variables de Entorno

Para almacenar de forma segura tus credenciales, crea un archivo .env en la raíz de tu proyecto (si aún no existe) y añade lo siguiente:

AUTH_SECRET="SUPER_SECRET_KEY_RANDOM"
GOOGLE_CLIENT_ID="TU_ID_DE_CLIENTE_DE_GOOGLE"
GOOGLE_CLIENT_SECRET="TU_SECRETO_DE_CLIENTE_DE_GOOGLE"
NEXTAUTH_URL="http://localhost:3000"
  • AUTH_SECRET: Una cadena aleatoria y fuerte. Puedes generarla, por ejemplo, con openssl rand -base64 32 en tu terminal Linux/macOS. Esta clave se usa para firmar y cifrar tokens, garantizando la seguridad de la sesión.
  • GOOGLE_CLIENT_ID: El ID de Cliente que obtuviste de Google.
  • GOOGLE_CLIENT_SECRET: El Secreto de Cliente que obtuviste de Google.
  • NEXTAUTH_URL: La URL base de tu aplicación. En desarrollo, es http://localhost:3000.
📌 Nota: Para producción, `NEXTAUTH_URL` debe ser la URL pública de tu aplicación (ej., `https://tu-dominio.com`). Además, deberás generar un `AUTH_SECRET` diferente y más robusto en producción.

⚙️ Paso 4: Configuración de NextAuth.js con el App Router

En el App Router, la configuración de NextAuth.js se maneja a través de un Route Handler en api/auth/[...nextauth]/route.ts.

Crea el siguiente archivo:

app/api/auth/[...nextauth]/route.ts

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
  ],
  secret: process.env.AUTH_SECRET,
  pages: {
    signIn: '/auth/signin',
    signOut: '/auth/signout',
    error: '/auth/error',
    verifyRequest: '/auth/verify-request',
    newUser: '/auth/new-user', // If you want to add an account creation page
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
    async session({ session, token }) {
      if (token) {
        session.user.id = token.id as string;
      }
      return session;
    },
  },
  session: {
    strategy: "jwt",
    maxAge: 30 * 24 * 60 * 60, // 30 days
    updateAge: 24 * 60 * 60, // 24 hours
  },
});

export { handler as GET, handler as POST };

Explicación del código:

  • providers: Un array donde defines los proveedores de autenticación. Aquí usamos GoogleProvider y pasamos las credenciales de entorno.
  • secret: La clave secreta para la firma y cifrado de los tokens.
  • pages: Permite personalizar las rutas de NextAuth.js (inicio de sesión, error, etc.). Esto es útil para mantener la coherencia de UI/UX de tu aplicación.
  • callbacks: Funciones asíncronas que se ejecutan durante la autenticación. Aquí, extendemos el token JWT y el objeto de sesión para incluir el id del usuario. Esto es crucial si necesitas el ID del usuario en tu frontend o para interactuar con una base de datos.
  • session: Configuración de la estrategia de sesión. Usamos jwt para un manejo de sesiones sin estado (ideal para arquitecturas escalables) y definimos la duración máxima de la sesión.

Ahora, necesitamos instalar el proveedor de Google:

npm install @next-auth/google
# o con yarn
yarn add @next-auth/google

🌐 Paso 5: Crear un Provider Context para el Cliente

Para que los componentes de React puedan acceder a la sesión, necesitamos envolver nuestra aplicación con un SessionProvider.

Crea el archivo app/providers.tsx:

'use client';

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

interface ProvidersProps {
  children: React.ReactNode;
}

export function Providers({ children }: ProvidersProps) {
  return (
    <SessionProvider>
      {children}
    </SessionProvider>
  );
}

Ahora, envuelve tu componente RootLayout con este Providers en app/layout.tsx:

import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import { Providers } from './providers'; // Importa el proveedor

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'Next.js Auth Tutorial',
  description: 'Authentication with NextAuth.js and Google',
};

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>)
{
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers> {/* Envuelve con el proveedor */}
          {children}
        </Providers>
      </body>
    </html>
  );
);
}

Este SessionProvider hará que la sesión esté disponible en todos los componentes de la aplicación mediante los hooks de NextAuth.js.


🚪 Paso 6: Implementar Botones de Inicio y Cierre de Sesión

Ahora crearemos una página de inicio donde los usuarios podrán iniciar y cerrar sesión.

Modifica app/page.tsx para incluir la lógica de autenticación:

'use client';

import { useSession, signIn, signOut } from 'next-auth/react';

export default function HomePage() {
  const { data: session, status } = useSession();

  if (status === 'loading') {
    return <p>Cargando sesión...</p>;
  }

  if (session) {
    return (
      <div className="flex flex-col items-center justify-center min-h-screen py-2">
        <h1 className="text-4xl font-bold mb-4">Bienvenido, {session.user?.name}</h1>
        <p className="text-lg mb-6">Estás autenticado como: {session.user?.email}</p>
        <img
          src={session.user?.image || 'https://via.placeholder.com/150'}
          alt="Avatar de usuario"
          className="rounded-full w-24 h-24 mb-4"
        />
        <button
          onClick={() => signOut()}
          className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
        >
          Cerrar Sesión
        </button>
      </div>
    );
  }

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <h1 className="text-4xl font-bold mb-4">No estás autenticado</h1>
      <button
        onClick={() => signIn('google')}
        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
      >
        Iniciar Sesión con Google
      </button>
    </div>
  );
}

Explicación:

  • useSession(): Este hook del lado del cliente te proporciona el estado de la sesión (data y status).
  • signIn('google'): Inicia el flujo de autenticación de Google.
  • signOut(): Cierra la sesión del usuario.

Ahora, inicia tu servidor de desarrollo:

npm run dev

Abre tu navegador en http://localhost:3000. Deberías ver el botón para iniciar sesión con Google. ¡Pruébalo!

1. Usuario hace clic en 'Iniciar Sesión' 2. next/auth/react llama a signIn('google') 3. Servidor Next.js redirige a Google OAuth 4. Usuario autoriza el acceso en Google 5. Google redirige de vuelta /api/auth/callback/google 6. next-auth procesa callback y crea la sesión 7. Usuario redirigido a página de origen / dashboard

🔐 Paso 7: Proteger Rutas del Lado del Cliente

Para proteger rutas del lado del cliente, puedes usar el hook useSession y redirigir al usuario si no está autenticado.

Crea una nueva página app/dashboard/page.tsx:

'use client';

import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

export default function DashboardPage() {
  const { data: session, status } = useSession();
  const router = useRouter();

  useEffect(() => {
    if (status === 'loading') return; // Do nothing while loading
    if (!session) {
      router.push('/'); // Redirect to home if not authenticated
    }
  }, [session, status, router]);

  if (status === 'loading') {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <p className="text-xl">Cargando dashboard...</p>
      </div>
    );
  }

  if (!session) {
    return null; // Or a loading spinner, as the redirect will happen soon
  }

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <h1 className="text-4xl font-bold mb-4">Página Protegida del Dashboard</h1>
      <p className="text-lg">Solo los usuarios autenticados pueden ver esto.</p>
      <p className="text-md mt-4">Tu ID de usuario es: <span class="badge green">{session.user?.id}</span></p>
    </div>
  );
}

Intenta acceder a http://localhost:3000/dashboard sin iniciar sesión. Deberías ser redirigido a la página de inicio. Una vez que inicies sesión, podrás acceder al dashboard.


🔒 Paso 8: Proteger Rutas del Lado del Servidor (Server Components)

Con el App Router de Next.js, puedes proteger rutas y renderizar componentes del lado del servidor basados en el estado de autenticación. Esto es muy potente para SEO y rendimiento.

Para obtener la sesión en un Server Component, necesitas usar la función getServerSession.

Modifica app/dashboard/page.tsx para ser un Server Component y protegerlo directamente. Primero, elimina el use client del archivo app/dashboard/page.tsx.

Luego, actualiza app/dashboard/page.tsx a lo siguiente:

import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';
import { authOptions } from '../api/auth/[...nextauth]/route'; // Importa las opciones de autenticación

export default async function DashboardPage() {
  const session = await getServerSession(authOptions);

  if (!session) {
    redirect('/'); // Redirige a la página de inicio si no hay sesión
  }

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <h1 className="text-4xl font-bold mb-4">Página Protegida del Dashboard (Server)</h1>
      <p className="text-lg">¡Bienvenido, {session.user?.name}! Solo los usuarios autenticados pueden ver esto.</p>
      <p className="text-md mt-4">Tu correo es: <span class="badge blue">{session.user?.email}</span></p>
    </div>
  );
}
⚠️ Advertencia: Para usar `getServerSession` fuera del `api/auth` handler, necesitas exportar las opciones de autenticación. Abre `app/api/auth/[...nextauth]/route.ts` y añade `export const authOptions = { ... }` antes de `const handler = NextAuth(authOptions);` y luego usa `getServerSession(authOptions)` en tus Server Components.

Actualización de app/api/auth/[...nextauth]/route.ts:

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { NextAuthOptions } from 'next-auth';

export const authOptions: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
  ],
  secret: process.env.AUTH_SECRET,
  pages: {
    signIn: '/auth/signin',
    signOut: '/auth/signout',
    error: '/auth/error',
    verifyRequest: '/auth/verify-request',
    newUser: '/auth/new-user',
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
    async session({ session, token }) {
      if (token) {
        session.user.id = token.id as string;
      }
      return session;
    },
  },
  session: {
    strategy: "jwt",
    maxAge: 30 * 24 * 60 * 60, // 30 days
    updateAge: 24 * 60 * 60, // 24 hours
  },
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

Reinicia el servidor de desarrollo y prueba de nuevo http://localhost:3000/dashboard. Si no estás autenticado, deberías ser redirigido inmediatamente a la página de inicio antes de que el componente del dashboard sea siquiera renderizado en el servidor. Esto es más seguro y eficiente.

💡 Consejo: La protección de rutas con `getServerSession` en Server Components es la forma preferida para proteger datos sensibles o lógica de negocio que no debe ser accesible para usuarios no autenticados.

✨ Personalización y Consideraciones Adicionales

🎨 Páginas de Inicio de Sesión Personalizadas

NextAuth.js te permite crear tus propias páginas de inicio de sesión, error, etc. En la configuración de NextAuth (en route.ts), establecimos pages:

pages: {
  signIn: '/auth/signin',
  // ... otras páginas
},

Para crear una página de inicio de sesión personalizada, simplemente crea el archivo app/auth/signin/page.tsx:

'use client';

import { signIn, getProviders } from 'next-auth/react';
import { useEffect, useState } from 'react';
import type { ClientSafeProvider, LiteralUnion } from 'next-auth/react';
import type { BuiltInProviderType } from 'next-auth/providers';

export default function SignInPage() {
  const [providers, setProviders] = useState<Record<LiteralUnion<BuiltInProviderType>, ClientSafeProvider> | null>(null);

  useEffect(() => {
    const fetchProviders = async () => {
      const res = await getProviders();
      setProviders(res);
    };
    fetchProviders();
  }, []);

  if (!providers) {
    return <p>Cargando proveedores de autenticación...</p>;
  }

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <h1 className="text-4xl font-bold mb-8">Inicia Sesión en Tu Aplicación</h1>
      {
        Object.values(providers).map((provider) => (
          <div key={provider.name} className="mb-4">
            <button
              onClick={() => signIn(provider.id, { callbackUrl: '/' })}
              className="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg text-lg"
            >
              Iniciar Sesión con {provider.name}
            </button>
          </div>
        ))
      }
    </div>
  );
}

Ahora, cuando intentes iniciar sesión, serás redirigido a http://localhost:3000/auth/signin.

📌 Nota: En el `signIn` de la página personalizada, puedes pasar un `callbackUrl` para redirigir al usuario a una página específica después de la autenticación exitosa.

📊 Tabla Comparativa: Protección en Cliente vs. Servidor

CaracterísticaProtección del Lado del Cliente (useSession)Protección del Lado del Servidor (getServerSession)
---------
Componenteuse client (Client Component)Por defecto (Server Component)
RendimientoRedirección después del renderizado inicial del cliente.Redirección antes del renderizado del servidor. Más eficiente.
---------
SeguridadMenos segura para datos sensibles, ya que se renderiza primero.Más segura, el acceso a datos sensibles se controla en el servidor.
SEOEl contenido se oculta o redirige después del JavaScript.El contenido nunca se envía al cliente si no está autorizado.
---------
Facilidad de usoSimple para UI reactiva y protección básica.Requiere exportar authOptions y manejar async/await.
Uso PrincipalElementos de UI, contenido condicional en el cliente.Rutas protegidas, obtención de datos sensibles, APIs.
Mayor Seguridad con Server Components

🗺️ Flujo de Autenticación con NextAuth.js

Comprender cómo funciona el flujo es clave para depurar y extender tu autenticación.

Usuario Inicia sesión (Click) Client Component hook: signIn('google') Solicitud Estado Sesión NextAuth.js API Handler /api/auth/[...nextauth] Crea JWT / Maneja Cookies OAuth Provider Google / GitHub Auth Callback Server Component getServerSession() Flujo Seguro de Autenticación con NextAuth.js

Este diagrama muestra cómo interactúan el cliente, el servidor Next.js y el proveedor OAuth para establecer y mantener la sesión del usuario.


✅ Conclusión

Has aprendido a implementar un sistema de autenticación robusto en tu aplicación Next.js utilizando NextAuth.js y un proveedor OAuth como Google. Hemos cubierto desde la configuración inicial, la integración de proveedores, la gestión de sesiones y, lo más importante, la protección de rutas tanto en el lado del cliente como en el lado del servidor.

NextAuth.js es una herramienta increíblemente potente y flexible que te permite añadir autenticación compleja con relativa facilidad, liberándote para centrarte en la lógica de negocio de tu aplicación. Recuerda siempre priorizar la seguridad de tus credenciales y considerar las implicaciones de seguridad al elegir entre protección del lado del cliente y del servidor.

¡Ahora estás listo para construir aplicaciones Next.js seguras y personalizadas!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!