Configurar Next.js con Aws Congnito, Amplify
Configuración Inicial de AWS Cognito
-
Crear un User Pool en Cognito:
- En la consola de AWS, ve a Cognito > User Pool > Create user pool.
- Configura:
- Application type: Traditional web application / SPA
- Nombre del grupo:
your-name-user-group
. - Atributos: Habilita correo electrónico como nombre de usuario.
-
Obtén las credenciales:
- USER_POOL_ID
- USER_POOL_CLIENT_ID
Crear Una aplicación Next.js
-
Ejecuta el comando de creación (usa la última versión de Next.js):
npx create-next-app@latest
-
Sigue las indicaciones del CLI:
- ✔️ Nombre del proyecto:
my-next-auth-app
(o el nombre que prefieras). - ✔️ ¿Usar TypeScript?: Sí (
Yes
). - ✔️ ¿Usar el directorio App Router?: Sí (
Yes
). - ✔️ Configurar ESLint?: Sí.
- ✔️ Configurar Tailwind CSS?: Opcional (recomendado).
- ✔️ Configurar
src/
directory?: Sí (mejor organización). - ✔️ Configurar rutas de importación?: No (a menos que lo necesites).
- ✔️ Nombre del proyecto:
-
Navega al directorio del proyecto:
cd my-next-auth-app
Configurar AWS Amplify en Next.js
-
Instalar dependencias:
npm install aws-amplify @aws-amplify/adapter-nextjs
-
Configurar Amplify con Cognito: Crea un archivo componente para la auth config
src/components/amplify-config.ts
:
'use client';
import { Amplify, type ResourcesConfig } from 'aws-amplify';
export const authConfig: ResourcesConfig['Auth'] = {
Cognito: {
userPoolId: String(process.env.NEXT_PUBLIC_USER_POOL_ID),
userPoolClientId: String(process.env.NEXT_PUBLIC_USER_POOL_CLIENT_ID)
}
};
Amplify.configure(
{
Auth: authConfig
},
{ ssr: true }
);
export default function ConfigureAmplifyClientSide() {
return null;
}
- Inicializar Amplify en
app/layout.tsx
:
import ConfigureAmplifyClientSide from '@/components/amplify-cognito-config';
import './globals.css';
type RootProps = Readonly<{
children: React.ReactNode;
}>;
export default function RootLayout({ children }: RootProps) {
return (
<html lang="en">
<body className="antialiased">
<ConfigureAmplifyClientSide />
{children}
</body>
</html>
);
}
- Variables de entorno (
.env.local
):
NEXT_PUBLIC_USER_POOL_ID=
NEXT_PUBLIC_USER_POOL_CLIENT_ID=
Protección De rutas y sesión
Para que nuestras vistas y api esten protegidas puedes configurar el middleware en Next.js creando lo siguiente en src/middleware.ts
import { type NextRequest, NextResponse } from 'next/server';
import { authenticatedUser } from '@/lib/amplify';
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
const user = await authenticatedUser({ request, response: NextResponse.next() });
if (pathname === '/' && !user) {
const url = request.nextUrl.clone();
url.pathname = '/auth/login';
return NextResponse.redirect(url);
}
if (pathname.startsWith('/api/') && !user) {
return new NextResponse(
JSON.stringify({
error: 'No autorizado'
}),
{
status: 403,
headers: {
'Content-Type': 'application/json'
}
}
);
}
if (pathname === '/auth/login' && user) {
const url = request.nextUrl.clone();
url.pathname = '/';
return NextResponse.redirect(url);
}
return NextResponse.next();
}
export const config = {
matcher: ['/', '/dashboard/:path*', '/profile/:path*', '/api/:path*', '/auth/login']
};
Con esto nos aseguramos de que si un usuario esta en la ruta raiz y no esta autenticado le redirigiremos a login, si intenta hacer peticiones a nuestra api, recibira un 403
Como ves hay una funcion que comprueba en servidor si esta autenticado el usuario en cognito, para ello crearemos un archivo en src/lib/amplify/auth-user.ts
import type { NextServer } from '@aws-amplify/adapter-nextjs';
import { fetchAuthSession, getCurrentUser } from 'aws-amplify/auth/server';
import { runWithAmplifyServerContext } from './run-context';
export async function authenticatedUser(context: NextServer.Context) {
return await runWithAmplifyServerContext({
nextServerContext: context,
operation: async (contextSpec) => {
try {
const session = await fetchAuthSession(contextSpec);
if (!session.tokens) {
return;
}
const user = {
…(await getCurrentUser(contextSpec))
};
return user;
} catch (error) {
console.error(error);
}
}
});
}
y una inicializacion del contexto por separado **`src/lib/amplify/run-context.ts
import { createServerRunner } from '@aws-amplify/adapter-nextjs';
export const { runWithAmplifyServerContext } = createServerRunner({
config: {
Auth: {
Cognito: {
userPoolId: String(process.env.NEXT_PUBLIC_USER_POOL_ID),
userPoolClientId: String(process.env.NEXT_PUBLIC_USER_POOL_CLIENT_ID)
}
}
}
});
Inicio, Registro y cierre de sesión
Para manejar peticiones en cliente de forma sencilla podemos instalar Tanstack Query para ahorarnos algo de codigo
Inicio de sesión
'use client';
import { useMutation } from '@tanstack/react-query';
import { handleSignIn } from '@/core';
import { QUERY_KEYS, type LoginFormValues } from '@/lib';
export const useSingIn = () => {
return useMutation({
mutationKey: [QUERY_KEYS.AUTH, QUERY_KEYS.AUTH_SIGNIN],
mutationFn: (data: LoginFormValues) => handleSignIn(data),
onSuccess: () => {
window.location.href = '/';
},
onError: (error) => {
alert(error.message);
}
});
};
import { signIn } from 'aws-amplify/auth';
import { handlerErrorMessage } from '@/lib';
export async function handleSignIn({ email, password }: { email: string; password: string }) {
try {
const signInResult = await signIn({
username: String(email),
password: String(password)
});
return signInResult;
} catch (error) {
throw new Error(handlerErrorMessage(error));
}
}
Con estas funciones podras implementar un formulario que Inicie correctamente sesion en tu aplicacion
Registro
'use client';
import { useMutation } from '@tanstack/react-query';
import { handleSignUp } from '@/core';
import { QUERY_KEYS, type SignUpFormValues } from '@/lib';
export const useSingUp = () => {
return useMutation({
mutationKey: [QUERY_KEYS.AUTH, QUERY_KEYS.AUTH_SIGNUP],
mutationFn: (data: LoginFormValues) => handleSignUp(data),
onSuccess: () => {
window.location.href = '/auth/signup/confirm';
},
onError: (error) => {
alert(error.message);
}
});
};
import { signUp } from 'aws-amplify/auth';
import { handlerErrorMessage } from '@/lib';
export async function handleSignUp({ email, password }: { email: string; password: string }) {
try {
await signUp({
username: email,
password: password,
options: {
userAttributes: {
email
},
// optional
autoSignIn: true
}
});
} catch (error) {
throw new Error(handlerErrorMessage(error));
}
}
'use client';
import { useMutation } from '@tanstack/react-query';
import { handleSignUpConfirm } from '@/core';
import { QUERY_KEYS, type SignUpFormValues } from '@/lib';
export const useSingUpConfirm = () => {
return useMutation({
mutationKey: [QUERY_KEYS.AUTH, QUERY_KEYS.AUTH_SIGNUP_CONFIRM],
mutationFn: (data: LoginFormValues) => handleSignIn(data),
onSuccess: () => {
window.location.href = '/auth/login';
},
onError: (error) => {
alert(error.message);
}
});
};
import { autoSignIn, confirmSignUp } from 'aws-amplify/auth';
import { handlerErrorMessage } from '@/lib';
export async function handleSignUp({ email, code }: { email: string; code: string }) {
try {
await confirmSignUp({
username: email,
confirmationCode: code
});
await autoSignIn()
} catch (error) {
throw new Error(handlerErrorMessage(error));
}
}
Con estas funciones podras implementar el flujo de registro, recuerda crear las pantallas de registro y confirmacion de registro, ya que al usuario le llegara un codigo de confirmacion por email.
Cierre de sesión
'use client';
import { useMutation } from '@tanstack/react-query';
import { handleSignOut } from '@/core';
import { QUERY_KEYS } from './keys';
export const useSignout = () => {
return useMutation({
mutationKey: [QUERY_KEYS.AUTH, QUERY_KEYS.AUTH_SIGNOUT],
mutationFn: handleSignOut,
onSuccess: () => {
window.location.href = '/';
},
onError: (error) => {
alert(error.message);
}
});
};
import { signOut } from 'aws-amplify/auth';
import { handlerErrorMessage } from '@/lib';
export async function handleSignOut() {
try {
await signOut();
} catch (error) {
throw new Error(handlerErrorMessage(error));
}
}
Con estas funciones podras implementar el cierre correcto de sesion.
Despliegue AWS Amplify
- AWS Amplify > Create new app > Github (u otro proveedor)
- Selecciona un repositorio y una rama > Next > Deploy
- Recuerda configurar tus variables de entorno
- NEXT_PUBLIC_USER_POOL_ID=
- NEXT_PUBLIC_USER_POOL_CLIENT_ID=
- Por ultimo actualiza tu
amplify.yml
en Build Settings
version: 1
frontend:
phases:
preBuild:
commands:
- nvm use 20
- env | grep -e NEXT_PUBLIC_USER_POOL_ID >> .env.production
- env | grep -e NEXT_PUBLIC_USER_POOL_CLIENT_ID >> .env.production
- npm ci --cache .npm --prefer-offline
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- .next/cache/**/*
- .npm/**/*