← Blog
Technique5 min de lecture9 juin 2026

Supabase Auth: pourquoi user_metadata.role n'est pas fiable

Un role dans user_metadata peut sembler pratique. Pour des permissions serieuses, ce n'est pourtant pas une base fiable.

PN
Paul Noorman
Fondateur, OurPlan
Supabase Auth: pourquoi user_metadata.role n'est pas fiable

Mettre un role metier dans user_metadata parait pratique. En quelques lignes, vous stockez "admin", "manager" ou "client" et vous avancez. Le probleme, c'est que ce n'est pas un bon endroit pour de l'autorisation serieuse.

Avec Supabase, la distinction importante est la suivante :

  • rawusermeta_data peut etre modifie par l'utilisateur
  • rawappmeta_data ne peut pas etre modifie par l'utilisateur

Cette difference change tout. Si une policy RLS ou une logique d'acces se base sur une information que l'utilisateur peut modifier lui-meme, ce n'est plus une vraie permission. C'est une porte que vous lui laissez entrouverte.

Pourquoi l'erreur est frequente

Le schema est tentant :

  • on enregistre un utilisateur
  • on lui ajoute un champ role dans user_metadata
  • on lit ce champ dans le front ou dans une policy

Sur le papier, c'est simple. En pratique, c'est fragile, parce que Supabase documente clairement que rawusermeta_data est modifiable par l'utilisateur avec supabase.auth.update().

Si vous utilisez ce champ comme preuve d'autorisation, vous melangez un champ pratique de profil avec une regle de securite.

Ce qu'il faut utiliser a la place

Pour des roles, droits ou statuts qui doivent avoir une valeur de securite, la bonne zone est rawappmeta_data.

Supabase l'explique aussi dans sa documentation RLS :

  • rawusermeta_data n'est pas un bon endroit pour les donnees d'autorisation
  • rawappmeta_data est le bon endroit pour ces donnees, car l'utilisateur ne peut pas le modifier lui-meme

Dit simplement :

  • user_metadata = donnees de profil, preferences, infos non sensibles
  • app_metadata = donnees d'autorisation ou de contexte d'acces

Le vrai risque

Le risque n'est pas seulement theorique.

Imaginons une policy du type :

using ((auth.jwt() -> 'user_metadata' ->> 'role') = 'admin')

Si un utilisateur peut changer cette valeur, toute la logique devient douteuse. Vous croyez filtrer l'acces, mais vous basez ce filtre sur une source qui n'est pas fiable.

Le probleme est encore plus subtil quand l'application fonctionne "a peu pres". Vous pouvez avoir des tests qui passent, un admin qui voit ses bons ecrans, et pourtant une base qui repose sur une information editable par le mauvais acteur.

Une structure plus saine

La version solide ressemble plutot a ceci :

  1. vous gardez les roles metier dans rawappmeta_data ou dans une table de roles interne
  2. vous lisez ces informations dans vos policies ou vos controles serveur
  3. vous reservez user_metadata aux champs pratiques, comme un nom, une langue ou un avatar

Exemple de policy plus saine :

using ((auth.jwt() -> 'app_metadata' ->> 'role') = 'admin')

Ou, mieux encore dans certains cas, vous passez par une table de jointure qui represente vos equipes, organisations ou niveaux d'acces. Cela demande un peu plus de schema, mais c'est plus lisible et plus evolutif.

Quand user_metadata reste utile

Ce n'est pas un champ "mauvais". Il est tres utile pour :

  • afficher un prenom ou un nom public
  • memoriser une preference de langue
  • conserver un fuseau horaire
  • stocker une valeur de confort qui n'a pas d'impact de securite

Le bon test est simple : si l'utilisateur change cette valeur, est-ce que cela doit pouvoir lui ouvrir un acces de plus ?

Si la reponse est non, alors user_metadata peut aller. Si la reponse est oui, il faut sortir cette logique de la.

Le piege des controles uniquement front-end

Beaucoup d'equipes font aussi une erreur voisine : elles cachent un bouton dans l'interface si user_metadata.role !== 'admin', puis elles pensent que la securite est reglee.

Ce n'est pas une securite. C'est juste une condition d'affichage.

Le front peut etre plus confortable avec ce genre de test. Mais le vrai controle doit vivre :

  • dans la base avec la RLS
  • ou dans du code serveur avec une verification fiable

Sinon, vous avez juste un joli rideau devant une porte mal fermee.

Une bonne strategie pour un portail client

Pour un projet concret, voici une bonne base :

  • user_metadata pour le nom, l'avatar, la langue
  • app_metadata pour le role global si vous avez un systeme simple
  • tables metier en base si vous avez des organisations, des permissions fines ou des acces par client

Exemple typique :

  • un utilisateur appartient a une ou plusieurs organisations
  • ses droits reels sont derives d'une table memberships
  • le front peut afficher un badge ou un menu selon ces droits
  • la base reste la source de verite

Cette approche evite de surcharger le JWT avec une logique qui devient vite trop rigide.

Comment auditer un projet existant

Si vous avez deja un projet en place, verifiez trois choses :

  1. Est-ce qu'une policy RLS lit user_metadata pour une decision d'acces ?
  2. Est-ce qu'un route handler ou un endpoint serveur traite user_metadata.role comme une preuve suffisante ?
  3. Est-ce que le front et la base utilisent la meme source de verite pour les droits ?

Si la reponse est oui au premier ou au deuxieme point, il y a probablement un nettoyage a faire.

Ce qu'il faut retenir

user_metadata est utile pour le profil. Il n'est pas fiable pour l'autorisation.

Si un role, un statut ou une permission a un impact de securite, basez-vous sur une source que l'utilisateur ne peut pas modifier, comme rawappmeta_data, ou mieux encore sur des tables metier et des policies claires.

La bonne question n'est pas "ou est-ce le plus simple a stocker ?". La bonne question est : qui peut changer cette valeur, et que se passe-t-il s'il la change ?

Sources officielles