Daily Actions: Sécuriser son application Next.js avec AWS Cognito et CDK
Maintenant que notre application Next.js est en ligne sur S3 et CloudFront, il est temps de lui ajouter une brique indispensable : l’authentification. Avec une architecture en export statique, les solutions traditionnelles basées sur des serveurs (comme NextAuth.js avec des routes API Node.js) ne fonctionnent pas directement.
Nous allons utiliser AWS Cognito pour gérer nos utilisateurs (User Pool) et intégrer le SDK AWS (ou Amplify) côté front-end pour connecter notre application statique en toute sécurité. Le CDK se charge de toute la configuration infrastructure.
Important
Ce deuxième article fait suite au déploiement de notre application statique (Daily Actions). Assurez-vous d’avoir cloné la dernière branche Git du projet ou de repartir de votre structure précédente (/frontend et />infrastructure).
Objectif du tutoriel
- Déployer un Cognito User Pool et un User Pool Client via le CDK.
- Récupérer automatiquement les IDs de configuration (User Pool ID, Client ID).
- Configurer le front-end Next.js pour gérer la connexion/inscription (UI ou SDK).
- Mettre à jour l’infrastructure globale en une commande.
Prérequis
Avant de commencer, il vous faut :
- avoir pris connaisance de cet article: Daily Actions: Déployer Next.js sur AWS (export statique) : S3 et CloudFront en 5 minutes avec CDK
1. Mise à jour de l’infrastructure CDK (Cognito)
Ouvrez votre fichier infrastructure/lib/infrastructure-stack.ts et ajoutez la ressource Cognito User Pool. Nous allons également exporter les identifiants nécessaires pour que le front-end sache à quel User Pool s’adresser.
// create userpool
const userPool = new cognito.UserPool(this, "UserPool", {
userPoolName: "DailyAppUserPool",
selfSignUpEnabled: true,
signInAliases: { email: true },
autoVerify: { email: true }, // voir remarque 1
featurePlan: cognito.FeaturePlan.ESSENTIALS, // requis pour Managed Login
standardAttributes: {
email: { required: true, mutable: false },
},
});
// create user pool client
const userPoolClient = new cognito.UserPoolClient(this, "UserPoolClient", {
userPoolClientName: "DailyAppUserPoolClient",
userPool,
generateSecret: false,
authFlows: { userSrp: true },
oAuth: {
flows: { authorizationCodeGrant: true },
callbackUrls: [
`https://${distribution.domainName}/`,
"http://localhost:3001/", // dev local
],
logoutUrls: [
`https://${distribution.domainName}/`,
"http://localhost:3001/",
],
scopes: [
cognito.OAuthScope.OPENID,
cognito.OAuthScope.EMAIL,
cognito.OAuthScope.PHONE,
],
},
});
// Domaine préfixe Cognito + moteur Managed Login
const domain = userPool.addDomain("Domain", {
cognitoDomain: { domainPrefix: "daily-app-auth-xyz" }, // unique par région
managedLoginVersion: cognito.ManagedLoginVersion.NEWER_MANAGED_LOGIN,
});
// Style par défaut, celui que la console crée pour vous
const branding = new cognito.CfnManagedLoginBranding(this, "Branding", {
userPoolId: userPool.userPoolId,
clientId: userPoolClient.userPoolClientId,
useCognitoProvidedValues: true,
});
branding.node.addDependency(domain);
new cdk.CfnOutput(this, "CognitoDomainUrl", { value: domain.baseUrl() });
new cdk.CfnOutput(this, "UserPoolId", {
value: userPool.userPoolId,
});
new cdk.CfnOutput(this, "UserPoolClientId", {
value: userPoolClient.userPoolClientId,
});
new cdk.CfnOutput(this, "DistributionDomainName", {
value: distribution.domainName,
});
creation des configs cognito
//
const config = {
authority: `https://cognito-idp.${this.region}.amazonaws.com/${userPool.userPoolId}`,
client_id: userPoolClient.userPoolClientId,
redirect_uri: "https://" + distribution.domainName + "/",
response_type: "code",
scope: "phone openid email",
};
// --- 4. Déploiement S3 --- & upload des configs cognito
new s3deploy.BucketDeployment(this, "DeployNextApp", {
sources: [
s3deploy.Source.asset("../frontend/out"),
s3deploy.Source.jsonData("config.json", config),
],
destinationBucket: bucket,
distribution,
distributionPaths: ["/*"],
});Configuration du Front-end Next.js
Puisque notre application est compilée en export statique (output: 'export'), elle ne possède pas de serveur Node.js pour injecter des variables d’environnement au moment des requêtes.
Pour contourner cette limite et permettre à notre application de récupérer dynamiquement l’URL de notre User Pool Cognito, nous utilisons un fichier config.json placé dans le dossier racine de Next.js, chargé au démarrage via le layout principal (RootLayout).
new s3deploy.BucketDeployment(this, "DeployNextApp", {
sources: [
s3deploy.Source.asset("../frontend/out"),
s3deploy.Source.jsonData("config.json", config),
],
destinationBucket: bucket,
distribution,
distributionPaths: ["/*"],
});Installation de la bibliothèque OIDC
Dans votre dossier frontend, installez le package react-oidc-context (et oidc-client-ts requis en dépendance) pour gérer la connexion OpenID Connect avec Cognito :
cd frontend
pnpm install oidc-client-ts react-oidc-contextLe RootLayout dynamique
Votre fichier app/layout.tsx récupère automatiquement le fichier config.json au chargement de l’application, initialise le fournisseur d’authentification (AuthProvider), et sécurise le rendu de votre interface :
"use client"
import { Geist, Geist_Mono } from "next/font/google"
import "./globals.css"
import { ThemeProvider } from "@/components/theme-provider"
import { cn } from "@/lib/utils"
import { AuthProvider } from "react-oidc-context"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { useEffect, useState } from "react"
const geist = Geist({ subsets: ["latin"], variable: "--font-sans" })
const fontMono = Geist_Mono({
subsets: ["latin"],
variable: "--font-mono",
})
interface Config {
authority: string
client_id: string
redirect_uri: string
response_type: string
scope: string
}
const queryClient = new QueryClient()
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
const [config, setConfig] = useState<Config | null>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetch("/config.json")
.then((res) => {
if (!res.ok) throw new Error(`config.json introuvable (${res.status})`)
return res.json()
})
.then((data) => setConfig(data))
.catch((err) => {
console.error(err)
setError(err.message)
})
}, [])
return (
<html
lang="en"
suppressHydrationWarning
className={cn(
"antialiased",
fontMono.variable,
"font-sans",
geist.variable
)}
>
<body>
{error ? (
<p>Erreur de configuration : {error}</p>
) : !config ? (
<p>Chargement…</p>
) : (
<AuthProvider {...config}>
<QueryClientProvider client={queryClient}>
<ThemeProvider>{children}</ThemeProvider>
</QueryClientProvider>
</AuthProvider>
)}
</body>
</html>
)
}Note
Grâce à cette approche, le fichier config.json généré automatiquement par votre script d’infrastructure après le déploiement CDK s’associera parfaitement avec votre application statique hébergée sur S3 et CloudFront.
Utilisation de l’authentification dans un composant (Page de connexion / Navbar)
const auth = useAuth();
const handleSignIn = () => {
auth.signinRedirect({ prompt: "login" });
};
const handleSignOut = () => {
auth.signoutSilent();
};Note
auth.signinRedirect() : Redirige automatiquement l’utilisateur vers la page d’authentification hébergée par AWS Cognito (ou l’interface de login configurée).
auth.signoutSilent() : ;
2. Le code de l’infrastructure (CDK)
avec la mise à jours de l’infra infrastructure/lib/infrastructure-stack.ts. devient
import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as s3deploy from "aws-cdk-lib/aws-s3-deployment";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as cognito from "aws-cdk-lib/aws-cognito";
export class InfrastructureStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const bucket = new s3.Bucket(this, "NextAppBucket", {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
bucketName: "next-app-daily-app",
});
const distribution = new cloudfront.Distribution(
this,
"NextAppDistribution",
{
defaultBehavior: {
origin: origins.S3BucketOrigin.withOriginAccessControl(bucket),
viewerProtocolPolicy:
cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
defaultRootObject: "index.html",
errorResponses: [
{
httpStatus: 404,
responseHttpStatus: 404,
responsePagePath: "/404.html",
},
],
},
);
// create userpool
const userPool = new cognito.UserPool(this, "UserPool", {
userPoolName: "DailyAppUserPool",
selfSignUpEnabled: true,
signInAliases: { email: true },
autoVerify: { email: true }, // voir remarque 1
featurePlan: cognito.FeaturePlan.ESSENTIALS, // requis pour Managed Login
standardAttributes: {
email: { required: true, mutable: false },
},
});
// create user pool client
const userPoolClient = new cognito.UserPoolClient(this, "UserPoolClient", {
userPoolClientName: "DailyAppUserPoolClient",
userPool,
generateSecret: false,
authFlows: { userSrp: true },
oAuth: {
flows: { authorizationCodeGrant: true },
callbackUrls: [
`https://${distribution.domainName}/`,
"http://localhost:3001/", // dev local
],
logoutUrls: [
`https://${distribution.domainName}/`,
"http://localhost:3001/",
],
scopes: [
cognito.OAuthScope.OPENID,
cognito.OAuthScope.EMAIL,
cognito.OAuthScope.PHONE,
],
},
});
// Domaine préfixe Cognito + moteur Managed Login
const domain = userPool.addDomain("Domain", {
cognitoDomain: { domainPrefix: "daily-app-auth-xyz" }, // unique par région
managedLoginVersion: cognito.ManagedLoginVersion.NEWER_MANAGED_LOGIN,
});
// Style par défaut, celui que la console crée pour vous
const branding = new cognito.CfnManagedLoginBranding(this, "Branding", {
userPoolId: userPool.userPoolId,
clientId: userPoolClient.userPoolClientId,
useCognitoProvidedValues: true,
});
branding.node.addDependency(domain);
new cdk.CfnOutput(this, "CognitoDomainUrl", { value: domain.baseUrl() });
new cdk.CfnOutput(this, "UserPoolId", {
value: userPool.userPoolId,
});
new cdk.CfnOutput(this, "UserPoolClientId", {
value: userPoolClient.userPoolClientId,
});
new cdk.CfnOutput(this, "DistributionDomainName", {
value: distribution.domainName,
});
const config = {
authority: `https://cognito-idp.${this.region}.amazonaws.com/${userPool.userPoolId}`,
client_id: userPoolClient.userPoolClientId,
redirect_uri: "https://" + distribution.domainName + "/",
response_type: "code",
scope: "phone openid email",
};
new s3deploy.BucketDeployment(this, "DeployNextApp", {
sources: [
s3deploy.Source.asset("../frontend/out"),
s3deploy.Source.jsonData("config.json", config),
],
destinationBucket: bucket,
distribution,
distributionPaths: ["/*"],
});
}
}3. Déploiement en une étape
cd infrastructure
npm run deploy:allEn sortie, récupérez l’URL CloudFront (domaine du type https://xxxxxxxxxxxx.cloudfront.net/) pour ouvrir l’application.


5. Nettoyer
Pour éviter des coûts après les tests :
cd infrastructure
cdk destroyConclusion
Dans cet article nous avons :
- ajiuyter une authentification a notre app
- projet gitlab (branch cognito)
Note
Votre interface est prête ; l’étape suivante est connecter notre application à une api gateway



Commentaires