Au cours des dernières années, l’ajout d’une authentification à votre application est passé de quelque chose d’obscur et de compliqué à quelque chose pour lequel vous pouvez littéralement utiliser une API.

Il n’y a pas de pénurie de dépôts d’exemples et de tutoriels sur la façon d’implémenter des schémas d’authentification spécifiques dans Next.js, mais moins sur le pourquoi des schémas, des outils et des compromis choisis.

Cet article vous expliquera ce qu’il faut prendre en compte lorsque l’on aborde l’authentification dans Next.js, depuis le choix d’un fournisseur jusqu’à la construction de routes pour la connexion et le choix entre le côté serveur et le côté client.

Choisir une méthode d’authentification/un fournisseur

Il y a fondamentalement 1000 façons d’intégrer l’authentification dans votre application. Plutôt que de se concentrer sur des fournisseurs particuliers (le sujet d’un autre article de blog), examinons les types de solutions d’authentification et quelques exemples de chacune d’entre elles. En termes d’implémentation, next-auth devient rapidement une option populaire pour intégrer votre application Next.js avec plusieurs fournisseurs, ajouter le SSO, etc.

Base de données traditionnelle

Celle-ci est aussi simple que possible : vous stockez les noms d’utilisateur et les mots de passe dans une base de données relationnelle. Lorsqu’un utilisateur s’inscrit pour la première fois, vous insérez une nouvelle ligne dans la table « users  avec les informations fournies. Lorsqu’il se connecte, vous vérifiez les informations d’identification par rapport à ce que vous avez stocké dans la table. Lorsqu’un utilisateur souhaite modifier son mot de passe, vous mettez à jour la valeur de la table.

L’authentification traditionnelle par base de données est certainement le système d’authentification le plus populaire si l’on considère l’ensemble des applications existantes, et elle existe pratiquement depuis toujours. Il est très flexible, bon marché et ne vous enferme pas dans un fournisseur particulier. Mais vous devez le construire vous-même et, en particulier, vous préoccuper du cryptage et vous assurer que vos mots de passe ne tombent pas entre de mauvaises mains.

Solutions d’authentification de votre fournisseur de base de données

Au cours des dernières années (et pour Firebase, il y a plus de quelques années), il est devenu relativement courant pour les fournisseurs de bases de données gérées de proposer une sorte de solution d’authentification gérée. Firebase, Supabase et AWS proposent tous une base de données gérée et une authentification gérée en tant que service via une suite d’API qui fait facilement abstraction de la création d’utilisateurs et de la gestion des sessions (nous y reviendrons plus tard).

Pour connecter un utilisateur avec l’authentification Supabase, rien de plus simple:

async function signInWithEmail() {
  const { data, error } = await supabase.auth.signInWithPassword({
    email: '[email protected]',
    password: 'example-password',
  })
}

Des solutions d’authentification qui ne proviennent pas de votre fournisseur de base de données

L’authentification en tant que service de votre DBaaS est peut-être encore plus courante que l’authentification en tant que service d’une entreprise ou d’un produit entier. Auth0 existe depuis 2013 (et appartient maintenant à Okta), et des ajouts récents comme Stytch ont donné la priorité à l’expérience des développeurs et ont gagné une certaine notoriété.

Auth0 pour l'authentification
Auth0 pour l’authentification

Authentification unique (SSO)

Le SSO vous permet d’« externaliser » votre identité à un fournisseur externe, qui peut être une entreprise, un fournisseur axé sur la sécurité comme Okta ou quelque chose de plus largement adopté comme Google ou GitHub. Le SSO de Google est omniprésent dans le monde du SaaS, et certains outils destinés aux développeurs ne s’authentifient que via GitHub.

Quel que soit le ou les fournisseurs choisis, le SSO est généralement un complément aux autres types d’authentification mentionnés ci-dessus et comporte ses propres particularités en matière d’intégration avec des plateformes externes (attention : SAML utilise XML).

D’accord, alors lequel me convient le mieux ?

Il n’y a pas de « bon » choix ici – ce qui convient à votre projet dépend de vos priorités. Si vous voulez que les choses bougent rapidement sans une tonne de configuration initiale, l’externalisation de l’authentification a du sens (même l’externalisation complète, interface utilisateur incluse, à quelqu’un comme Auth0). Si vous anticipez une configuration plus complexe, construire votre propre backend d’authentification est judicieux. Et si vous envisagez de soutenir des clients plus importants, vous aurez besoin d’ajouter le SSO à un moment donné.

Next.js est tellement populaire que la plupart de ces fournisseurs d’authentification ont des documents et des guides d’intégration spécifiques à Next.js.

Construire des routes pour l’inscription et la connexion, et des conseils pour aller plus loin dans l’authentification

Certains fournisseurs d’authentification comme Auth0 fournissent en fait des pages web entières hébergées pour l’inscription et la connexion. Mais si vous construisez ces pages à partir de zéro, je trouve qu’il est utile de les créer tôt dans le processus car vous en aurez besoin pour fournir des redirections lorsque vous mettrez en œuvre votre authentification.

Il est donc logique de créer la structure de ces pages et d’ajouter les requêtes au backend après coup. La façon la plus simple d’implémenter l’authentification est d’avoir deux de ces routes :

  • Une pour l’inscription
  • Une autre pour se connecter une fois que l’utilisateur a déjà un compte

Au-delà des principes de base, vous devrez couvrir les cas particuliers, par exemple lorsqu’un utilisateur oublie son mot de passe. Certaines équipes préfèrent que le processus de réinitialisation du mot de passe se déroule sur une route distincte, tandis que d’autres ajoutent des éléments d’interface utilisateur dynamiques à la page d’ouverture de session habituelle.

Une belle page d’inscription ne fera peut-être pas la différence entre le succès et l’échec, mais de petites touches peuvent laisser une bonne impression et améliorer l’interface utilisateur. Voici quelques exemples de sites qui ont mis un peu plus d’amour dans leurs processus d’authentification.

1. Mettez à jour votre barre de navigation en cas de session active

L’appel à l’action de Stripe dans sa barre de navigation change selon que vous avez une session authentifiée ou non. Voici à quoi ressemble le site de marketing si vous n’êtes pas authentifié. Notez l’appel à l’action pour vous connecter :

La page d'accueil de Stripe modifie l'appel à l'action selon que vous êtes ou non authentifié
La page d’accueil de Stripe modifie l’appel à l’action selon que vous êtes ou non authentifié

Et voici à quoi ressemble la page d’accueil si vous êtes authentifié. Notez que l’appel à l’action change pour amener l’utilisateur à son tableau de bord au lieu de se connecter :

Modifications de la page d'accueil de Stripe
Modifications de la page d’accueil de Stripe

Cela ne change pas fondamentalement mon expérience avec Stripe, mais c’est agréable.

Une parenthèse technique intéressante : il y a une bonne raison pour que la plupart des entreprises ne fassent pas dépendre la barre de navigation de leur site marketing de l’authentification – cela signifierait une requête API supplémentaire pour vérifier l’état d’authentification à chaque chargement de page, la majorité d’entre elles étant destinées à des visiteurs qui ne sont probablement pas authentifiés.

2. Ajoutez un contenu utile au formulaire d’inscription

Au cours des dernières années, en particulier dans le domaine du SaaS, les entreprises ont commencé à ajouter du contenu à la page d’inscription pour « encourager » l’utilisateur à terminer l’inscription. Cela peut contribuer à améliorer votre taux de conversion sur la page, au moins de manière progressive.

Voici une page d’inscription de Retool, avec une animation et quelques logos sur le côté :

Si vous décidez de procéder de la sorte, veillez à ce que les polices de chaque côté correspondent.
Si vous décidez de procéder de la sorte, veillez à ce que les polices de chaque côté correspondent.

C’est également ce que nous faisons chez Kinsta pour notre page d’inscription :

Page d'inscription Kinsta
Page d’inscription Kinsta

Le petit contenu supplémentaire peut aider à rappeler à l’utilisateur ce pour quoi il s’inscrit et pourquoi il en a besoin.

3. Si vous utilisez un mot de passe : suggérez ou imposez un mot de passe fort

Je me sens à l’aise pour dire que les développeurs savent tous que les mots de passe sont intrinsèquement peu sûrs, mais ce n’est pas le cas de toutes les personnes qui s’inscriront à votre produit. Encourager vos utilisateurs à créer des mots de passe sûrs est bon pour vous et bon pour eux.

Coinbase est assez strict en ce qui concerne l’inscription et exige que vous utilisiez un mot de passe sécurisé plus compliqué que votre simple prénom :

Mot de passe faible sur Coinbase
Mot de passe faible sur Coinbase

Après avoir généré un mot de passe à partir de mon gestionnaire de mots de passe, j’étais prêt à partir :

Mot de passe fort sur Coinbase
Mot de passe fort sur Coinbase

L’interface utilisateur ne m’a cependant pas expliqué pourquoi le mot de passe n’était pas assez sûr, ni quelles étaient les exigences au-delà de la présence d’un numéro. Le fait d’inclure ces informations dans le texte de votre produit rendra les choses plus faciles pour votre utilisateur et permettra d’éviter les frustrations liées aux tentatives de nouvel essai du mot de passe.

4. Labellisez vos entrées pour qu’elles soient compatibles avec un gestionnaire de mots de passe

Un Américain sur trois utilise un gestionnaire de mot de passe comme 1Password, et pourtant de nombreux formulaires sur le web continuent d’ignorer le « type= » dans les entrées HTML. Faites en sorte que vos formulaires soient compatibles avec les gestionnaires de mots de passe:

  • Enfermez vos éléments d’entrée dans un élément de formulaire
  • Attribuez un type et un libellé aux entrées
  • Ajoutez des capacités d’autocomplétion à vos entrées
  • N’ajoutez pas de champs de manière dynamique (je vous regarde, Delta)

Cela peut faire la différence entre une connexion incroyablement fluide en 10 secondes et une connexion manuelle ennuyeuse, en particulier sur mobile.

Choisir entre Sessions et JWT

Une fois que votre utilisateur s’est authentifié, vous devez choisir une stratégie pour conserver cet état lors des requêtes suivantes. HTTP est sans état, et nous ne voulons certainement pas demander à notre utilisateur son mot de passe à chaque requête. Il existe deux méthodes populaires pour gérer cela – les sessions (ou cookies) et les JWT (jetons web JSON) – et elles diffèrent selon que c’est le serveur ou le client qui fait le travail.

Sessions, ou cookies

Dans l’authentification par session, la logique et le travail de maintien de l’authentification sont pris en charge par le serveur. Voici le processus de base :

  1. L’utilisateur s’authentifie via la page de connexion.
  2. Le serveur crée un enregistrement qui représente cette « session » de navigation particulière Cet enregistrement est généralement inséré dans une base de données avec un identifiant aléatoire et des détails sur la session, comme sa date de début et sa date d’expiration.
  3. Cet identifiant aléatoire – quelque chose comme `6982e583b1874abf9078e1d1dd5442f1` – est envoyé à votre navigateur et stocké sous forme de cookie.
  4. Lors des requêtes ultérieures du client, l’identifiant est inclus et vérifié dans le tableau des sessions de la base de données.

C’est assez simple et modifiable en ce qui concerne la durée des sessions, le moment où elles doivent être révoquées, etc. L’inconvénient est la latence à une échelle significative, étant donné toutes les écritures et les lectures dans la base de données, mais ce n’est peut-être pas une considération majeure pour la plupart des lecteurs.

Jetons web JSON (JWT)

Au lieu de gérer l’authentification pour les requêtes ultérieures sur le serveur, les JWT vous permettent de les gérer (principalement) du côté client. Voici comment cela fonctionne :

  1. L’utilisateur s’authentifie via la page de connexion.
  2. Le serveur génère un JWT qui contient l’identité de l’utilisateur, les autorisations qui lui sont accordées et une date d’expiration (parmi d’autres éléments potentiels).
  3. Le serveur signe ce jeton en cryptant son contenu et envoie le tout au client.
  4. Pour chaque demande, le client peut décrypter le jeton et vérifier que l’utilisateur a la permission de faire la requête (tout cela sans avoir besoin de communiquer avec le serveur).

Avec tout le travail d’authentification post-initiale déchargé sur le client, votre application peut se charger et fonctionner beaucoup plus rapidement. Mais il y a un problème majeur : il n’y a aucun moyen d’invalider un JWT à partir du serveur. Si l’utilisateur souhaite se déconnecter d’un appareil ou si la portée de son autorisation change, vous devez attendre que le JWT expire.

Choisir entre l’authentification côté serveur et l’authentification côté client

Une partie de ce qui rend Next.js génial est le rendu statique intégré – si votre page est statique, c’est-à-dire qu’elle n’a pas besoin de faire des appels d’API externes, Next.js la met automatiquement en cache et peut la servir extrêmement rapidement via un CDN. Pre-Next.js 13 sait si une page est statique si vous n’incluez pas de « getServerSideProps » ou « getInitialProps » dans le fichier, tandis que toutes les versions postérieures à Next.js 13 utilisent React Server Components pour le faire à la place.

Pour l’authentification, vous avez le choix entre rendre une page statique de « chargement » et faire la recherche côté client ou tout faire côté serveur. Pour les pages qui requièrent une authentification [1], vous pouvez rendre un « squelette » statique et effectuer les requêtes d’authentification côté client. En théorie, cela signifie que la page se charge plus rapidement, même si le contenu initial n’est pas entièrement prêt.

Voici un exemple simplifié tiré de la documentation qui rend un état de chargement tant que l’objet utilisateur n’est pas prêt :

import useUser from '../lib/useUser'
 
const Profile = () => {
  // Fetch the user client-side
  const { user } = useUser({ redirectTo: '/login' })
 
  // Server-render loading state
  if (!user || user.isLoggedIn === false) {
    // Build some sort of loading page here
    return <div>Loading...</div>
  }
 
  // Once the user request finishes, show the user
  return (
    <div>
      <h1>Your Account</h1>
      <p>Username: {JSON.stringify(user.username,null)}</p>
      <p>Email: {JSON.stringify(user.email,null)}</p>
      <p>Address: {JSON.stringify(user.address,null)}</p>
    </div>
  )
}
 
export default Profile

Notez que vous devrez construire une sorte d’interface utilisateur de chargement pour conserver l’espace pendant que le client fait des requêtes après le chargement.

Si vous voulez simplifier les choses et exécuter l’authentification côté serveur, vous pouvez ajouter votre demande d’authentification dans la fonction « getServerSideProps », et Next attendra de rendre la page jusqu’à ce que la demande soit complète. Au lieu de la logique conditionnelle de l’extrait ci-dessus, vous pouvez exécuter quelque chose de plus simple comme cette version simplifiée de la documentation de Next :

import withSession from '../lib/session'
 
export const getServerSideProps = withSession(async function ({ req, res }) {
  const { user } = req.session
 
  if (!user) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    }
  }
 
  return {
    props: { user },
  }
})
 
const Profile = ({ user }) => {
  // Show the user. No loading state is required
  return (
    <div>
      <h1>Your Account</h1>
      <p>Username: {JSON.stringify(user.username,null)}</p>
      <p>Email: {JSON.stringify(user.email,null)}</p>
      <p>Address: {JSON.stringify(user.address,null)}</p>
    </div>
  )
}
 
export default Profile

Il y a toujours une logique ici pour gérer l’échec de l’authentification, mais elle redirige vers la connexion au lieu de rendre un état de chargement.

Résumé

Alors, laquelle de ces solutions convient le mieux à votre projet ? Commencez par évaluer votre confiance dans la rapidité de votre système d’authentification. Si vos requêtes ne prennent pas de temps, vous pouvez les exécuter côté serveur et éviter l’état de chargement. Si vous voulez donner la priorité au rendu de quelque chose tout de suite et attendre la requête, sautez « getServerSideProps » et exécutez l’authentification ailleurs.

[1] Lorsque vous utilisez Next, c’est une bonne raison de ne pas exiger l’authentification pour chaque page. Il est plus simple de le faire, mais cela signifie que vous passez à côté des avantages de performance du framework web en premier lieu.

Justin Gage

Justin is a technical writer and author of the popular Technically newsletter. He did his B.S. in Data Science before a stint in full-stack engineering and now focuses on making complex technical concepts accessible to everyone.