Leçon 7/8 10 min

Protéger son API : les tokens

API key pour identifier une app, Bearer token pour un utilisateur : 401 vs 403 et la règle d'or sur les clés côté client.

Le serveur ne se souvient de rien

Reviens une seconde au cours HTTP, leçon sur les sessions et les cookies. La grande leçon était brutale : le serveur ne se souvient de rien. Chaque requête arrive comme la première, sans mémoire de la précédente. C'est le navigateur qui porte un badge (le cookie de session) pour prouver « c'est encore moi » à chaque appel.

Une API, c'est exactement la même histoire, en plus nu. Elle est stateless comme tout HTTP : chaque requête doit se présenter toute seule. Mais il y a une différence clé. Une API n'est pas faite pour un navigateur qui clique de page en page. Elle est faite pour des programmes : le site de la mairie qui interroge ta bibliothèque, l'appli mobile, un autre serveur. Ces clients-là n'ont pas le cookie de session du navigateur.

Alors les API portent leur badge ailleurs : dans un en-tête HTTP. Toujours le même, le standard : Authorization. C'est lui le sujet de cette leçon. Qui es-tu, comment le prouves-tu à chaque appel, et que se passe-t-il quand le badge manque ou ne suffit pas.

L'API key : identifier une application

Premier type de badge : la clé d'API. Elle ne répond pas à « quel humain ? » mais à « quelle application ? ». Le site web de la mairie veut afficher les nouveautés de la bibliothèque. Ce n'est pas une personne qui se connecte, c'est un programme qui parle à un autre programme. On appelle ça du server-to-server.

La bibliothèque génère une clé pour la mairie, une longue chaîne secrète. À chaque appel, le site de la mairie la présente, soit dans l'en-tête standard, soit dans un en-tête dédié :

Authorization: Bearer cle_live_8f3a9c2b...
# ou un en-tete maison
X-Api-Key: cle_live_8f3a9c2b...

Deux propriétés importantes. La clé identifie l'app, pas une personne : tous les appels du site de la mairie ressemblent à « c'est la mairie ». Et elle est révocable : si la clé fuite, la bibliothèque la désactive et en génère une neuve, sans toucher aux autres partenaires. C'est un robinet qu'on peut couper.

Une clé par partenaire : ne donne jamais la même clé à deux applications différentes. Une clé par client, c'est ce qui te permet de couper l'accès de l'un sans punir les autres, et de voir qui consomme quoi dans tes logs. C'est le minimum pour garder le contrôle.

Le Bearer token : identifier un utilisateur

Deuxième type de badge, celui qui répond à « quel humain ? » : le Bearer token. Sam, la bibliothécaire, ouvre l'appli pour ajouter un livre au catalogue. Il faut prouver que c'est bien Sam, et qu'elle a le droit. Le flux tient en trois temps.

  • Elle se connecte une fois. POST /api/connexion avec son email et son mot de passe dans le corps de la requête.
  • Le serveur lui rend un token. Une chaîne signée, à durée de vie courte, du genre eyJhbGc.... Le mot de passe ne ressort jamais : seul ce token voyage ensuite.
  • Chaque appel suivant porte le token. Dans l'en-tête : Authorization: Bearer eyJhbGc.... Le serveur le lit, vérifie la signature, et sait que c'est Sam, sans redemander le mot de passe.

Le mot « Bearer » est à prendre au pied de la lettre : porteur. Quiconque détient le token est toi. Le serveur ne vérifie pas qui présente le badge, seulement que le badge est valide. C'est puissant et dangereux à la fois. Trois conséquences, toutes déjà vues dans le cours HTTP :

  • HTTPS obligatoire. En clair, le token transiterait à nu et n'importe qui sur le réseau pourrait le voler (rappel de la leçon HTTPS/TLS du cours HTTP). Sans chiffrement, le badge est lisible par tous.
  • Durée de vie courte. Un token volé doit expirer vite. Quelques minutes à quelques heures, puis on le renouvelle. Moins il vit, moins il vaut cher pour un voleur.
  • Jamais dans une URL. Les URL finissent dans les logs des serveurs, dans l'historique du navigateur, dans le champ Referer. Un token dans ?token=eyJ..., c'est un secret écrit en clair partout. Il va dans l'en-tête, point.

On reste côté design d'API. Comment hacher un mot de passe, signer et vérifier un token, gérer l'expiration côté serveur : tout ça est traité en profondeur dans le cours Sécurité web, leçon authentification. Ici, on regarde comment l'API porte le badge, pas comment on le fabrique en béton armé.

401 contre 403 : le distinguo qui compte

Tu as croisé ces deux codes à la leçon 4. Ici ils prennent corps, et c'est LE point à ne jamais confondre. Authentification et autorisation sont deux questions différentes.

  • 401 Unauthorized, c'est « qui êtes-vous ? ». Pas de token, token expiré, signature invalide : le serveur ne sait même pas à qui il parle. Il répond 401. Le mot anglais est mal choisi (il dit « non autorisé »), mais le sens réel est « non authentifié ». Va d'abord prouver ton identité.
  • 403 Forbidden, c'est « je sais qui vous êtes, et non ». Le token est parfaitement valide, le serveur sait que c'est toi. Mais tu n'as pas le droit pour cette action précise. Un membre simple qui tente DELETE /livres/42 : son badge est bon, mais supprimer un livre est réservé aux bibliothécaires. 403.

La règle mnémonique : 401 = je ne sais pas qui tu es ; 403 = je sais, et c'est non. Renvoyer 403 quand il fallait 401 (ou l'inverse) envoie le client réparer le mauvais problème : retenter une connexion alors que c'est un droit qui manque, ou demander des permissions alors que le token a simplement expiré.

Authentification ≠ autorisation. S'authentifier, c'est prouver son identité (le 401 te le réclame). Être autorisé, c'est avoir le droit pour CETTE action (le 403 te le refuse). Un même utilisateur valide peut faire un appel en 200 et le suivant en 403, selon ce qu'il tente. Le badge dit qui tu es ; les permissions disent ce que tu peux.

La règle d'or : jamais de clé dans le JS du navigateur

Voici l'erreur que tout le monde commet une fois, et qu'il faut tuer dans l'œuf. Tu veux que ton site affiche des données d'une API tierce payante. La tentation : appeler l'API directement depuis le JavaScript de ta page, avec la clé dedans. Surtout pas.

Tout ce qui part dans le navigateur est public. Une clé d'API écrite dans un fetch() côté client est lisible par n'importe qui : il suffit d'ouvrir l'onglet Réseau des outils de dev, ou de regarder le code source. C'est rappelé tout au long du cours Sécurité web : le front n'est pas un coffre-fort, c'est une vitrine. Une clé secrète dans la vitrine n'est plus secrète. Le bon montage : ton front parle à TON back, et c'est ton back (où la clé est cachée, côté serveur) qui parle à l'API tierce. La clé ne quitte jamais ton serveur.

Le schéma mental : navigateur → ton serveur (garde la clé) → API tierce. Le navigateur ne voit jamais la clé. C'est trois lettres de plus dans le chemin, mais c'est la frontière entre un secret gardé et un secret affiché à la Terre entière.

Et OAuth 2.0 ?

OAuth 2.0 est LE standard pour déléguer un accès. C'est ce qui se cache derrière « se connecter avec Google » : tu donnes à une appli l'accès à certaines de tes données Google sans jamais lui donner ton mot de passe Google. C'est plus riche (et plus complexe) que les tokens vus ici, et hors du périmètre d'implémentation de ce cours : retiens juste le nom et l'idée, tu sauras le reconnaître et choisir d'aller le creuser quand un projet le réclamera.

Le badge, de la connexion au refus

Garde cette séquence en tête : on se connecte une fois pour obtenir le token, puis chaque appel le porte. Sans badge, c'est 401. Avec un badge valide mais sans le droit, c'est 403.

Trois acteurs : le front (appli de Sam), l'API de la bibliothèque, et la base des tokens. Étape 1, le front envoie email et mot de passe à POST /api/connexion ; l'API vérifie auprès de la base et renvoie un token. Étape 2, le front rappelle l'API avec Authorization Bearer ; le token valide donne 201. Plus bas, deux cas d'échec : sans badge l'API répond 401 (qui êtes-vous), et avec un badge de membre simple sur un DELETE réservé, l'API répond 403 (je sais qui vous êtes, et non). Front (appli de Sam) API bibliothèque Base des tokens POST /api/connexion (email + mdp) vérifie 200 + token eyJhbGc... POST /livres · Authorization: Bearer … 201 Created POST /livres · sans badge 401 : qui êtes-vous ? DELETE /livres/42 · badge membre 403 : je sais, et c'est non
Une connexion pour le token, puis chaque appel le porte. Sans badge : 401. Bon badge, mauvais rôle : 403.
Prédis avant de lire

Un membre simple, parfaitement connecté, présente un token valide et tente DELETE /livres/42, une action réservée aux bibliothécaires. Le serveur répond 401 ou 403 ?

Voir la réponse

403. Le token est valide, donc le serveur sait parfaitement qui tu es : la question « qui êtes-vous ? » (401) est déjà réglée. Ce qui manque, c'est le droit de supprimer un livre. C'est exactement le rôle du 403 : « je sais qui vous êtes, et non ». On répondrait 401 seulement si le token manquait, était expiré ou invalide.

À toi : du refus 401 au refus 403

Un terminal simulé face à l'API de la bibliothèque. Tu vas te heurter au 401 (pas de badge), te connecter pour décrocher un token, réussir un ajout avec le badge, puis te prendre un 403 sur une action réservée. Regarde bien la différence entre les deux refus.

🖥️ Terminal simulé · présenter son badge à l'API
$

The server remembers nothing

Go back for a second to the HTTP course, the lesson on sessions and cookies. The big takeaway was blunt: the server remembers nothing. Every request arrives like the first one, with no memory of the previous one. It's the browser that carries a badge (the session cookie) to prove "it's still me" on every call.

An API is the exact same story, only barer. It's stateless like all of HTTP: every request has to introduce itself on its own. But there's a key difference. An API isn't built for a browser clicking from page to page. It's built for programs: the town hall website querying your library, the mobile app, another server. Those clients don't have the browser's session cookie.

So APIs carry their badge elsewhere: in an HTTP header. Always the same standard one: Authorization. That's the subject of this lesson. Who are you, how do you prove it on every call, and what happens when the badge is missing or not enough.

The API key: identifying an application

First kind of badge: the API key. It doesn't answer "which human?" but "which application?". The town hall website wants to show the library's new arrivals. It's not a person logging in, it's a program talking to another program. We call that server-to-server.

The library generates a key for the town hall, a long secret string. On every call, the town hall site presents it, either in the standard header or in a dedicated one:

Authorization: Bearer key_live_8f3a9c2b...
# or a custom header
X-Api-Key: key_live_8f3a9c2b...

Two important properties. The key identifies the app, not a person: every call from the town hall site looks like "this is the town hall". And it's revocable: if the key leaks, the library disables it and issues a fresh one, without touching the other partners. It's a tap you can shut off.

One key per partner: never give the same key to two different applications. One key per client is what lets you cut off one's access without punishing the others, and see who consumes what in your logs. That's the minimum to stay in control.

The Bearer token: identifying a user

Second kind of badge, the one that answers "which human?": the Bearer token. Sam, the librarian, opens the app to add a book to the catalogue. We need to prove it really is Sam, and that she's allowed. The flow is three steps.

  • She logs in once. POST /api/login with her email and password in the request body.
  • The server hands her a token. A signed, short-lived string, something like eyJhbGc.... The password never comes back out: only this token travels from then on.
  • Every following call carries the token. In the header: Authorization: Bearer eyJhbGc.... The server reads it, checks the signature, and knows it's Sam, without asking for the password again.

Take the word "Bearer" literally: holder. Whoever holds the token is you. The server doesn't check who presents the badge, only that the badge is valid. That's powerful and dangerous at once. Three consequences, all already seen in the HTTP course:

  • HTTPS mandatory. In the clear, the token would travel bare and anyone on the network could steal it (recall the HTTPS/TLS lesson of the HTTP course). Without encryption, the badge is readable by everyone.
  • Short lifetime. A stolen token must expire fast. A few minutes to a few hours, then you renew it. The less it lives, the less it's worth to a thief.
  • Never in a URL. URLs end up in server logs, browser history, the Referer field. A token in ?token=eyJ... is a secret written in plain sight everywhere. It goes in the header, full stop.

We stay on the API design side. How to hash a password, sign and verify a token, handle expiry server-side: all of that is covered in depth in the Web Security course, authentication lesson. Here we look at how the API carries the badge, not how to forge it in reinforced concrete.

401 vs 403: the distinction that matters

You met these two codes in lesson 4. Here they take shape, and it's THE point never to confuse. Authentication and authorization are two different questions.

  • 401 Unauthorized is "who are you?". No token, expired token, invalid signature: the server doesn't even know who it's talking to. It answers 401. The English word is badly chosen (it says "unauthorized"), but the real meaning is "unauthenticated". Go prove your identity first.
  • 403 Forbidden is "I know who you are, and no". The token is perfectly valid, the server knows it's you. But you don't have the right for this exact action. A regular member trying DELETE /books/42: their badge is good, but deleting a book is reserved for librarians. 403.

The memory rule: 401 = I don't know who you are; 403 = I know, and it's no. Returning 403 when it should be 401 (or the reverse) sends the client to fix the wrong problem: retrying a login when it's a missing right, or asking for permissions when the token simply expired.

Authentication ≠ authorization. Authenticating is proving your identity (401 demands it). Being authorized is having the right for THIS action (403 denies it). A single valid user can make one call return 200 and the next return 403, depending on what they attempt. The badge says who you are; permissions say what you can do.

The golden rule: never a key in the browser's JS

Here's the mistake everyone makes once, and that must be killed in the egg. You want your site to show data from a paid third-party API. The temptation: call the API directly from your page's JavaScript, with the key inside. Absolutely not.

Anything sent to the browser is public. An API key written in a client-side fetch() is readable by anyone: just open the Network tab of the dev tools, or look at the source. It's repeated throughout the Web Security course: the front end isn't a safe, it's a shop window. A secret key in the window is no longer secret. The right setup: your front end talks to YOUR back end, and it's your back end (where the key is hidden, server-side) that talks to the third-party API. The key never leaves your server.

The mental picture: browser → your server (keeps the key) → third-party API. The browser never sees the key. It's three extra letters in the path, but it's the border between a kept secret and a secret broadcast to the whole planet.

What about OAuth 2.0?

OAuth 2.0 is THE standard for delegating access. It's what hides behind "sign in with Google": you give an app access to some of your Google data without ever giving it your Google password. It's richer (and more complex) than the tokens seen here, and outside this course's implementation scope: just keep the name and the idea, so you'll recognise it and choose to dig in when a project calls for it.

The badge, from login to refusal

Keep this sequence in mind: you log in once to get the token, then every call carries it. With no badge, it's 401. With a valid badge but no right, it's 403.

Three actors: the front end (Sam's app), the library API, and the token store. Step 1, the front end sends email and password to POST /api/login; the API checks against the store and returns a token. Step 2, the front end calls the API again with Authorization Bearer; the valid token returns 201. Below, two failure cases: with no badge the API answers 401 (who are you), and with a regular-member badge on a reserved DELETE, the API answers 403 (I know who you are, and no). Front (Sam's app) Library API Token store POST /api/login (email + password) checks 200 + token eyJhbGc... POST /books · Authorization: Bearer … 201 Created POST /books · no badge 401: who are you? DELETE /books/42 · member badge 403: I know, and it's no
One login for the token, then every call carries it. No badge: 401. Right badge, wrong role: 403.
Predict before reading on

A regular member, perfectly logged in, presents a valid token and attempts DELETE /books/42, an action reserved for librarians. Does the server answer 401 or 403?

Show the answer

403. The token is valid, so the server knows exactly who you are: the "who are you?" question (401) is already settled. What's missing is the right to delete a book. That's exactly the 403's job: "I know who you are, and no". We'd answer 401 only if the token were missing, expired or invalid.

Your turn: from the 401 refusal to the 403 refusal

A simulated terminal facing the library API. You'll hit the 401 (no badge), log in to grab a token, succeed at an add with the badge, then take a 403 on a reserved action. Watch the difference between the two refusals closely.

🖥️ Simulated terminal · present your badge to the API
$

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique à un collègue la différence entre 401 et 403, avec un exemple concret de chaque sur l'API de la bibliothèque.

Une bonne explication dit : 401 = « qui êtes-vous ? », le serveur ne sait pas à qui il parle (pas de token, token expiré ou invalide). 403 = « je sais qui vous êtes, et non », le token est valide mais le rôle ne donne pas le droit pour cette action. Exemple : POST /livres sans en-tête Authorization → 401 ; un membre simple avec un token valide qui tente DELETE /livres/42 → 403. Authentification (prouver son identité) ≠ autorisation (avoir le droit).
🧠 Rappel libre
Rappel libre

Sans remonter : pourquoi un Bearer token impose-t-il HTTPS, une durée de vie courte, et jamais dans une URL ?

Parce que « Bearer » veut dire porteur : quiconque détient le token EST toi. Donc HTTPS pour qu'il ne transite pas en clair et ne soit pas volé sur le réseau ; durée courte pour qu'un token volé expire vite et perde sa valeur ; jamais dans une URL car les URL finissent dans les logs, l'historique et le Referer, ce qui exposerait le secret partout. Le token voyage dans l'en-tête Authorization.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Ton site doit afficher la météo via une API tierce payante. Tu demandes à l'IA de l'intégrer. Elle répond : « C'est fait ! J'appelle l'API directement dans le fetch() de la page, avec ta clé en dur dans le JS. C'est plus simple, pas besoin de back. » Tu acceptes, ou tu rejettes ?

À rejeter sans hésiter. Tout ce qui part dans le navigateur est public : la clé en dur dans le fetch() est lisible dans l'onglet Réseau et le code source. N'importe qui peut la copier et consommer le quota payant à ta place. Le bon montage : le front appelle TON back, et c'est ton back (clé cachée côté serveur) qui appelle l'API tierce. « Plus simple » ne justifie jamais d'afficher un secret au monde entier.
Une API est stateless. Comment un client prouve-t-il son identité à chaque appel, lui qui n'a pas le cookie de session du navigateur ?
Le serveur de la mairie interroge l'API de la bibliothèque, sans aucun humain derrière. Quel badge utilise-t-il ?
Sam est connectée, token valide. Elle tente DELETE /livres/42, réservé aux bibliothécaires en chef. Son rôle est membre simple. Quel code, et pourquoi ?
Pour gagner du temps, un dev met la clé de l'API météo payante directement dans le fetch() JavaScript de la page. Pourquoi est-ce grillé d'avance ?
Prochaine étape

Tu as toutes les pièces : ressources, verbes, status codes, contrat JSON, pagination, et maintenant les tokens. Leçon 8, le capstone : assembler tout ça en une vraie mini-API, en PHP natif que tu connais déjà. Routage sur la méthode, 201, 204, 422, problem+json, le tout dans un seul fichier qui tourne.

Leçon 8 : Construire une mini-API en PHP →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement