Sous-solA API dans NuxtPièce 4/7Intermédiaire

Cacher une clé d'API

Pourquoi une clé d'API ne doit jamais atteindre le navigateur, et comment la garder côté serveur avec runtimeConfig et une route intermédiaire.

Ce que tu vas apprendre

  • Pourquoi une clé placée dans le navigateur est une clé perdue.
  • Déclarer et lire un secret avec runtimeConfig.
  • Faire passer l'appel par ta propre route, qui joue le rôle d'intermédiaire.

Le problème

Une clé d'API est un mot de passe qui identifie ton application auprès d'un service, souvent payant ou limité. Imagine une API de photos de plats qui demande une clé dans l'en-tête Authorization.

La tentation : appeler directement le service depuis ta page.

app/pages/recettes/index.vue
<script setup lang="ts">
// MAUVAIS : cette clé part dans le navigateur de chaque visiteur
const { data } = await useFetch('https://api.exemple-photos.test/plats', {
  headers: { Authorization: 'Bearer ma-cle-secrete' }
})
</script>

Tout ce qui s'exécute dans le navigateur est lisible : code envoyé, onglet Réseau, en-têtes de requête. N'importe quel visiteur copie la clé et l'utilise à tes frais.

Même placée dans une variable d'environnement, une clé lue côté navigateur reste visible. Ce qui compte, c'est où le code s'exécute, pas où tu as rangé la valeur.

La solution : le serveur appelle, pas le navigateur

Le code de server/ ne quitte jamais le serveur. L'idée : ton navigateur appelle ta route (/api/photos), et c'est cette route qui appelle le service avec la clé. On parle d'intermédiaire (ou proxy).

  1. Le navigateur envoie GET /api/photos à ton serveur.
  2. Ta route ajoute la clé et appelle le service externe.
  3. Elle renvoie la réponse au navigateur, sans la clé.

Étape 1 : déclarer le secret

Dans nuxt.config.ts, une clé hors de public reste côté serveur :

nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    cleExemple: '', // secrète : serveur seulement
    public: {
      nomSite: 'Mes recettes' // publique : visible partout
    }
  }
})

La valeur réelle va dans .env. Le nom suit la règle de la documentation : majuscules, préfixe NUXT_, mots séparés par _ aux changements de casse. cleExemple devient NUXT_CLE_EXEMPLE.

.env
NUXT_CLE_EXEMPLE=la-vraie-cle
La variable doit aussi être déclarée dans runtimeConfig, sinon elle est ignorée. Et n'utilise jamais NUXT_PUBLIC_ pour un secret : tout ce qui est public est envoyé au navigateur.

Étape 2 : la route intermédiaire

server/api/photos.get.ts
export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event)

  return await $fetch('https://api.exemple-photos.test/plats', {
    headers: { Authorization: `Bearer ${config.cleExemple}` }
  })
})

useRuntimeConfig(event) reçoit l'event : la documentation le recommande dans les routes serveur pour appliquer les variables d'environnement au moment de l'exécution. Le service reçoit la clé, ton visiteur jamais.

Étape 3 : la page appelle ta route

app/pages/recettes/index.vue
<script setup lang="ts">
const { data: photos } = await useFetch('/api/photos')
</script>

Dans l'onglet Réseau, tu ne vois que /api/photos. Aucune clé, aucune adresse du service externe.

Aller plus loin avec la route

L'intermédiaire apporte plus que le secret. Tu peux :

  • filtrer : ne renvoyer que les champs utiles, comme avec transform ;
  • valider : n'accepter que les paramètres que tu attends, au lieu de les relayer tels quels ;
  • protéger : refuser les requêtes non autorisées avant de dépenser le quota du service.
server/api/photos.get.ts
export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event)
  const { plat } = getQuery(event)

  if (typeof plat !== 'string' || plat.length > 50) {
    throw createError({ status: 400, statusText: 'Parametre plat invalide' })
  }

  return await $fetch('https://api.exemple-photos.test/plats', {
    headers: { Authorization: `Bearer ${config.cleExemple}` },
    query: { nom: plat }
  })
})

Ne relaie jamais une entrée du visiteur sans la vérifier : le chapitre 5 détaille la validation.

Et si la clé a fuité ?

  • Considère-la comme volée : révoque-la chez le service, génère-en une nouvelle.
  • Vérifie que .env est dans .gitignore avant ton premier commit.
  • Utilise une clé différente par environnement (développement, production).

À retenir

  • Tout ce qui s'exécute dans le navigateur est lisible : une clé d'API n'y a jamais sa place.
  • Déclare le secret dans runtimeConfig hors de public, sa valeur dans .env sous le nom NUXT_....
  • Ta route server/api/ appelle le service avec la clé ; la page appelle ta route.
  • Lis la config avec useRuntimeConfig(event) dans une route serveur.
  • Une clé qui a fuité se révoque et se remplace, on ne la « cache » pas après coup.

Pour aller plus loin

Sources officielles

Ensuite

Projet
Lignes de code
Niveau
Sous-sol
Appartement
API dans Nuxt
Pièce
4/7
Plan n°
api/04.cacher-une-cle-d-api
Lignes de code · fiches rédigées à partir des documentations officielles, sources citées en bas de chaque page.