---
contentId: 05da35d9-c21f-4b45-ba86-e88d61339502
title: "Nuxt Devient Full-Stack : Comment Gérer l'Autorisation ?"
description: Sécurisez votre application Nuxt avec des techniques d'autorisation avancées. Apprenez à restreindre l'accès non autorisé et à protéger les données sensibles.
date: 2024-06-09
---

Lorsque vous développez une application, il est essentiel de **granter** ou de **restrict** l'accès à des zones ou des données spécifiques en fonction du rôle ou des permissions d'un utilisateur. Ce concept est connu sous le nom d'**autorisation**.

L'autorisation est un aspect critique de toute application à des fins de **sécurité**. Il est impératif de protéger les **données sensibles**, telles que l'adresse e-mail ou le mot de passe d'un utilisateur, et de prévenir la divulgation d'**informations privées** qui pourraient compromettre la vie privée des utilisateurs et l'intégrité de votre entreprise.

Pour clarifier : **l'authentification** implique de vérifier l'identité d'un utilisateur, tandis que **l'autorisation** consiste à permettre ou à refuser l'accès à des ressources en fonction du rôle ou des permissions de l'utilisateur.

En construisant Orion, j'ai été confronté à ce défi et j'ai expérimenté plusieurs approches. Dans cet article, je partagerai plusieurs stratégies pour gérer l'autorisation dans une application NuxtHub, allant des méthodes les plus simples aux plus sophistiquées. Il est crucial de comprendre que la méthode la plus complexe n'est pas nécessairement la meilleure pour chaque application.

Orion sert de collection de templates dirigée par la communauté pour vos projets, allant des pages d'atterrissage aux applications web complètes. NuxtHub fonctionne comme une plateforme de déploiement et de gestion pour Nuxt, propulsée par Cloudflare.

<CalloutReadMore id="2349bbcd-1535-4f33-8da7-299f46af18b9" />

## Le Contexte

Considérez cet **endpoint** dans votre application Nuxt :

```ts
export default defineEventHandler(async (event) => {
  const id = getRouteParam(event, 'id')

  const publication = await db.query.publications.findOne({
    where: { id },
    columns: {
      title: true,
      content: true,
      status: true,
      authorId: true,
    },
  })

  return publication
})
```

Vous pouvez accéder à cet endpoint en effectuant une requête `GET /api/publication/<id>`. Cet endpoint retourne une publication. À première vue, il n'y a pas de problème avec cet endpoint. Cependant, un examen plus attentif révèle qu'une publication a un statut qui peut être `brouillon`, `publiée` ou `supprimée`.

Le problème est que quiconque peut accéder à cet endpoint et consulter une **publication brouillon** ou une **publication supprimée**. Certains pourraient être en mesure de voir le contenu d'une publication avant sa publication, ce qui pourrait contenir des informations vitales telles qu'un communiqué de presse ou le lancement d'un produit. Un tel scénario pourrait nuire considérablement à votre entreprise.

Il s'agit d'un problème de sécurité qui nécessite de restreindre l'accès à cet endpoint, en utilisant le [principe du moindre privilège](https://en.wikipedia.org/wiki/Principle_of_least_privilege). La sécurité n'est pas simplement une fonctionnalité mais doit être intégrée dans la conception du système.

## Authentifié ou Non

La section suivante repose sur l'utilisation du package Nuxt [**nuxt-auth-utils**](https://github.com/Atinux/nuxt-auth-utils). Néanmoins, son utilisation n'est pas essentielle.

La première étape pour sécuriser cet **endpoint** consiste à vérifier si l'utilisateur est **authentifié**. Si l'utilisateur ne dispose pas d'une authentification, il convient de renvoyer une erreur `401 Unauthorized`.

En explorant le package **nuxt-auth-utils**, j'ai découvert un utilitaire serveur appelé `requireUserSession`. Cet utilitaire peut être utilisé au début d'un endpoint pour tenter de récupérer la session de l'utilisateur et déclencher une erreur `401 Unauthorized` s'il n'y a pas de session, indiquant que l'utilisateur n'est pas authentifié.

```ts
export default defineEventHandler(async (event) => {
  await requireUserSession(event)

  const publication = {} // ...

  return publication
})
```

C'est la première étape pour protéger l'endpoint contre les **utilisateurs non authentifiés**. Cependant, si quiconque peut s'inscrire pour créer un compte, il pourrait toujours accéder au contenu d'une publication brouillon. D'autres actions sont donc nécessaires.

## Seulement pour les Administrateurs

En s'appuyant sur `requireUserSession`, je peux facilement concevoir un nouvel utilitaire nommé `requireAdminSession`. Cette session vérifiera si l'utilisateur est authentifié et dispose de droits d'administrateur. Si l'utilisateur n'est pas un administrateur, une erreur `403 Forbidden` doit être renvoyée. Non autorisé se rapporte à l'authentification, tandis que Forbidden concerne l'autorisation.

```ts
export async function requireAdminSession(event: H3Event, opts: { statusCode?: number, message?: string } = {}): Promise<UserSessionRequired> {
  const userSession = await requireUserSession(event)
  const { user } = userSession

  if (user.roleType !== 'admin') {
    throw createError({
      statusCode: opts.statusCode || 403,
      message: opts.message || 'Non autorisé',
    })
  }

  return userSession as UserSessionRequired
}
```

Par la suite, `requireUserSession` peut être remplacé par `requireAdminSession` dans l'endpoint.

```ts
export default defineEventHandler(async (event) => {
  await requireAdminSession(event)

  const publication = {} // ...

  return publication
})
```

Maintenant, seuls les **admins**, les utilisateurs ayant le rôle `admin`, peuvent accéder au contenu d'une publication.

Cependant, cette approche n'est pas entièrement efficace, car si la publication est publiée, je souhaite permettre à tous les utilisateurs d'y accéder. Actuellement, seuls les admins peuvent voir le contenu d'une publication, quelle que soit le statut de la publication.

## Problèmes avec ces Approches

Les stratégies mentionnées manquent de flexibilité suffisante pour un système d'**autorisation** détaillé. Elles ne peuvent pas accommoder les situations où l'auteur de la publication devrait avoir accès, même si la publication est non publiée.

Pour y remédier, je pourrais rédiger un autre utilitaire nommé `requirePublicationAccess` :

```ts
export async function requirePublicationAccess(event: H3Event, publication: Publication, opts: { statusCode?: number, message?: string } = {}): Promise<UserSessionRequired> {
  const userSession = await getUserSession(event)
  const { user } = userSession

  if (publication.status === 'published')
    return userSession

  if (!userSession) {
    throw createError({
      statusCode: opts.statusCode || 401,
      message: opts.message || 'Non autorisé',
    })
  }

  if (publication.authorId === user.id)
    return userSession

  if (user.roleType === 'admin')
    return userSession

  throw createError({
    statusCode: opts.statusCode || 403,
    message: opts.message || 'Interdit',
  })
}
```

Cette solution est adéquate tant que je dois gérer d'autres endpoints. Cette approche entraînera une excessive duplication de code concernant le traitement des erreurs. De plus, le code devient plus difficile à tester en raison de la nécessité de gérer un événement H3 entier.

Sans une meilleure approche, je continue à développer Orion mais me heurte bientôt à un autre défi : je dois mettre en œuvre la même **logique d'autorisation côté client**. Je ne souhaite pas afficher un bouton d'édition à un utilisateur qui n'est ni l'auteur de la publication ni un administrateur, et je ne veux pas cacher ce bouton d'édition à l'administrateur si je décide de permettre l'édition de la publication sur le serveur. Comme pour [la validation des formulaires](./nuxt-going-full-stack-how-to-streamline-form-validation.md), la duplication de la logique peut entraîner des incohérences substantielles entre le client et le serveur, menant à la frustration des clients. Les utilitaires que j'ai créés jusqu'à présent sont uniquement côté serveur en raison de l'utilisation de `requireUserSession` et `getUserSession`.

Faisons un pas en arrière pour comprendre comment ce problème pourrait être résolu. L'autorisation se compose de trois composants :

- **permettre** l'accès à une ressource
- **refuser** l'accès à une ressource
- **autoriser** l'accès à une ressource

Les deux premiers sont des conditions simples produisant une réponse booléenne à des questions comme "Puis-je accéder à cette publication ? Oui ou Non". Le développeur doit gérer manuellement cette réponse. Le troisième accorde l'accès à la ressource. Pour la question "Puis-je accéder à cette publication ?", il n'y a ni "oui" ni "non" ; rien ne se passe si la réponse est "oui", mais une erreur est levée si la réponse est "non". L'`autoriser` envoie automatiquement une erreur.

Ces éléments n'ont aucun lien direct avec le client, le serveur, ou le framework utilisé ou le système d'authentification. Munis de cette connaissance, j'ai commencé à travailler sur un module local pour Orion afin de gérer l'autorisation de manière plus flexible. Après plusieurs jours d'efforts, je suis fier de vous présenter **nuxt-authorization**.

Au départ, je visais à développer à la fois un module Nitro et un module Nuxt, mais les modules Nitro ne sont pas encore prêts. Cela dit, j'ai soumis quelques demandes de tirage au dépôt Nitro pour améliorer le système de modules. L'une d'elles est le [guide de l'auteur du module](https://github.com/unjs/nitro/pull/2508).

## Présentation de nuxt-authorization

Ce module offre une méthode simple mais puissante pour gérer l'**autorisation** dans une application Nuxt, applicable tant au client qu'au serveur. Il est indépendant du système d'authentification mais peut être intégré sans effort avec [**nuxt-auth-utils**](https://github.com/Atinux/nuxt-auth-utils).

Pour des instructions sur son utilisation, visitez le dépôt GitHub : <GitHubLink repo="barbapapazes/nuxt-authorization" />. Pour explorer la conception et l'implémentation, continuez à lire.

Le module est encore à ses débuts de développement, et si vous avez des retours ou des suggestions d'amélioration, n'hésitez pas à ouvrir un problème sur le dépôt GitHub ou à me contacter sur [X](https://x.com/barbapapazes).

### Problèmes que je souhaite résoudre

Avant de décrire ce qui a été accompli avec **nuxt-authorization**, examinons les problèmes que je souhaitais résoudre.

Tout comme la validation des formulaires, je vise à maintenir une source de vérité unique et une API unique tant pour l'application que pour le serveur. Cela est crucial pour éviter les divergences entre l'application et le serveur.

Je souhaite également avoir la possibilité de définir la **logique d'autorisation** de manière simple et regroupée par ressource. Cette approche améliore la lisibilité, la maintenabilité et la cohérence à travers divers aspects de l'application, tels que la validation des formulaires.

La logique d'autorisation doit être suffisamment flexible pour accommoder une gamme variée de cas d'utilisation, des conditions `permettre` ou `refuser` aux conditions `autoriser` plus complexes. Les erreurs produites par la condition `autoriser` devraient être personnalisables pour améliorer l'expérience utilisateur. Toutes les erreurs non autorisées ne doivent pas nécessairement être un `403 Forbidden`.

Enfin, je désire des composants qui facilitent l'intégration de la logique d'autorisation dans les templates. Par exemple, je souhaite avoir la capacité d'afficher ou de cacher un bouton en fonction de la logique d'autorisation.

### Conception

Le module se compose de deux composants : l'**habilité** et le **bouncer**.

Les habilités constituent les règles qui incarnent la **logique d'autorisation**. Elles prennent un utilisateur et une ou plusieurs ressources et génèrent une condition de refus ou d'autorisation. Bien qu'elles puissent être regroupées par ressource, elles restent indépendantes les unes des autres.

```ts
export const editBook = defineAbility((user: User, book: Book) => {
  return user.id === book.authorId
})
```

Dans l'exemple ci-dessus, seul l'auteur d'un livre est autorisé à le modifier. Par défaut, les aptitudes ne sont exécutées que si l'utilisateur est authentifié, mais il est possible de permettre un accès invité à certaines ressources.

```ts
export const listBooks = defineAbility((user: User | null) => {
  return true
})
```

Dans cet exemple, tout le monde, y compris les utilisateurs non authentifiés, est autorisé à lister les livres.

La fonction `defineAbility` agit comme une usine qui crée une aptitude mais rien au-delà de cela. Elle doit être utilisée avec des fonctions bouncer.

Dans un endpoint serveur, la fonction `autoriser` peut être utilisée pour accorder l'accès à une ressource basée sur les aptitudes.

```ts
export default defineEventHandler(async (event) => {
  await authorize(event, listBooks)

  const books = await db.query.books.findMany()

  return books
})
```

La fonction `autoriser` déclenchera une erreur `403 Forbidden` si l'utilisateur n'est pas autorisé à lister les livres. Un contrôle plus granulaire peut être obtenu en utilisant les fonctions `allows` ou `denies`.

```ts
export default defineEventHandler(async (event) => {
  if (await denies(event, listBooks, book))
    return []

  const books = await db.query.books.findMany()

  return books
})
```

Au lieu de déclencher une erreur, un tableau vide est retourné si l'utilisateur n'est pas autorisé à lister les livres. La méthode appropriée pour gérer l'accès non autorisé dépendra de votre logique commerciale.

Par exemple, retourner une erreur `404 Not Found` si l'utilisateur n'est pas autorisé à accéder à une ressource spécifique peut préserver la confidentialité des données. Divulguer l'existence de la ressource pourrait poser un risque pour la sécurité.

Cette personnalisation est atteignable dans les aptitudes via les fonctions `allow` et `deny`.

```ts
export const viewBook = defineAbility((user: User, book: Book) => {
  if (book.status === 'draft') {
    return deny({
      statusCode: 404,
      message: 'Non trouvé',
    })
  }

  return allow()
})
```

Désormais, la fonction `autoriser` lancera une erreur `404 Not Found` si l'utilisateur n'est pas autorisé à voir un livre en brouillon, au lieu du `403 Forbidden` par défaut.

Côté client, les fonctions `allows`, `denies` et `authorize` peuvent également être utilisées pour administrer la **logique d'autorisation**.

De plus, ces fonctions offrent deux composants : `Can` et `Cannot`. Ces composants permettent d'afficher ou de cacher des portions d'un template en fonction de la **logique d'autorisation**.

```vue
<template>
  <Can :ability="editBook" :args="[book]">
    <button>Modifier</button>
  </Can>
</template>
```

Consultez le dépôt GitHub pour plus d'informations : <GitHubLink repo="barbapapazes/nuxt-authorization" />.

### Dernières Pensées

Le code et la conception de ce package s'inspirent fortement du [Adonis Bouncer](https://docs.adonisjs.com/guides/security/authorization). C'est un produit de qualité, et réinventer des concepts inutilement est souvent futile.

J'implémente déjà ce package dans Orion, et l'expérience a été très agréable. Je suis convaincu qu'il y a encore un potentiel d'amélioration, mais ce module représente un premier pas pour autonomiser les développeurs Nuxt et faire progresser les capacités full-stack de Nuxt. Les contributions et suggestions pour de nouvelles fonctionnalités ou améliorations sont les bienvenues.

Orion est une collection de templates dirigée par la communauté pour vos projets, s'étendant des pages d'atterrissage aux applications web complètes.

J'espère que cet article et ce module vous aideront à gérer l'**autorisation** au sein de votre application Nuxt, empêchant ainsi et restreignant l'**accès non autorisé** à vos données. La sécurité est impérative.

Bonne programmation !
