OAuth 2.0 · OIDC · GCP · Workspace · Gmail

Comprendre finement comment Google
vous authentifie — et autorise.

Une carte complète des mécanismes d'authentification de l'écosystème Google, pour développeurs. On y décortique les flux réels (requêtes HTTP, endpoints exacts), la structure des tokens (JWT décodés), et quel service utilise quelle méthode. Deux questions guident tout : qui es-tu ? (authentification) et as-tu le droit ? (autorisation).

00
Le vocabulaire commun

Les briques que tout le reste réutilise

Avant les flux : les objets qui circulent. Access token, ID token, refresh token, scope, audience, issuer, JWKS. Tous les mécanismes qui suivent produisent, transportent ou vérifient ces mêmes objets.

Google implémente OAuth 2.0 (RFC 6749, un cadre d'autorisation) et OpenID Connect (une couche d'authentification posée sur OAuth). Deux confusions à évacuer d'emblée :

  • OAuth ≠ OIDC. OAuth délivre un access_token qui dit « le porteur a le droit d'appeler telle API ». OIDC ajoute un id_token qui dit « voici qui s'est connecté ». Un access token ne prouve pas une identité.
  • Authentification ≠ autorisation. Prouver qui l'on est (tokens) est distinct d'avoir le droit d'agir (IAM). Un token parfaitement valide peut se voir refuser un appel : c'est IAM (§04) qui tranche à l'exécution.
Le chemin complet, de bout en bout
Identitéhumain · machine
prouve
MécanismeOAuth · SA · WIF
émet
access_token
(+ id_token)
présenté à
IAMa le droit ?
si oui
API Googleréponse

Quelle que soit son origine (§01 à §03), tout token traverse IAM (§04) au moment de l'appel : l'authentification produit le token, l'autorisation décide s'il passe. Ce sont deux étapes distinctes.

Les trois tokens

Access token — un jeton porteur (bearer) présenté à chaque appel d'API dans l'en-tête Authorization: Bearer …. Côté Google c'est souvent un jeton opaque (ya29.…), pas un JWT. Durée de vie courte (~1 heure). Il répond à « as-tu le droit d'appeler cette API, dans ces scopes ? » — pas à « qui es-tu ».

ID token — un JWT signé par Google, propre à OIDC. Il porte l'identité de l'utilisateur dans ses claims (sub, email, name…). Votre application le vérifie : signature via les clés publiques JWKS, plus iss, aud et exp. C'est lui qui sert à connecter un utilisateur (voir §07 pour le décoder).

Refresh token — un jeton longue durée qui permet d'obtenir de nouveaux access tokens sans réafficher l'écran de consentement. Précieux et sensible : à stocker chiffré côté serveur. Pour l'obtenir sur le web, il faut demander access_type=offline (et souvent prompt=consent).

Vérifier un token : issuer, audience, JWKS

Un JWT Google se vérifie sans appeler Google à chaque fois : on récupère (et met en cache) ses clés publiques, puis on contrôle localement trois choses.

🔑
Les trois contrôles non négociables d'un id_token

iss = https://accounts.google.com · aud = votre client_id (le jeton vous est bien destiné) · exp non dépassé. Signature vérifiée contre https://www.googleapis.com/oauth2/v3/certs (JWKS). La configuration complète est publiée sur https://accounts.google.com/.well-known/openid-configuration.

Scope : la granularité de l'autorisation (ex. https://www.googleapis.com/auth/gmail.send). L'utilisateur les approuve sur le consent screen ; l'access token les porte. Audience (aud) : le destinataire prévu du jeton. Issuer (iss) : son émetteur.

01
Identité humaine · autorisation déléguée

OAuth 2.0 & OpenID Connect

Le flux quand un utilisateur se connecte à votre application ou lui délègue l'accès à ses données Google. La norme aujourd'hui : Authorization Code + PKCE.

Le principe : votre application ne voit jamais le mot de passe Google. L'utilisateur s'authentifie chez Google, approuve des scopes, et votre app reçoit un code à usage unique qu'elle échange, côté serveur, contre des tokens. PKCE (code_challenge/code_verifier) empêche l'interception du code — obligatoire pour les clients publics (SPA, mobile), recommandé partout.

Les paramètres qui comptent

  • response_type=code · client_id · redirect_uri (doit correspondre exactement à la config)
  • scope — inclure openid email profile pour obtenir un id_token (OIDC)
  • state — anti-CSRF, à vérifier au retour · nonce — lié à l'id_token, anti-rejeu
  • code_challenge + code_challenge_method=S256 — PKCE
  • access_type=offline + prompt=consent — pour recevoir un refresh_token
Redirection navigateur
GET https://accounts.google.com/o/oauth2/v2/auth?
  response_type=code&
  client_id=1234-abc.apps.googleusercontent.com&
  redirect_uri=https://app.exemple.fr/callback&
  scope=openid%20email%20https://www.googleapis.com/auth/gmail.send&
  state=xyz-anti-csrf&
  code_challenge=E9Melhoa2Ow...&
  code_challenge_method=S256&
  access_type=offline&
  prompt=consent
Serveur → Google (back-channel)
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

code=4/0Adeu...&
client_id=1234-abc.apps.googleusercontent.com&
client_secret=GOCSPX-...&
redirect_uri=https://app.exemple.fr/callback&
grant_type=authorization_code&
code_verifier=dBjftJeZ4CVP...

// Réponse
{
  "access_token": "ya29.a0Af...",
  "expires_in": 3599,
  "refresh_token": "1//09Fx...",
  "scope": "openid email .../gmail.send",
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSUzI1NiIs..."
}
Appel d'API avec l'access token
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
Authorization: Bearer ya29.a0Af...
Content-Type: application/json

{ "raw": "<message RFC 2822 encodé base64url>" }

// Rafraîchir quand l'access token expire
POST https://oauth2.googleapis.com/token
grant_type=refresh_token&refresh_token=1//09Fx...&client_id=...&client_secret=...

Sous le capot : tout le cheminement cryptographique

On reprend le flux, mais cette fois on regarde ce qui est calculé à chaque étape : où passe le mot de passe, ce que prouvent les secrets, comment un token est signé puis vérifié.

Étape 0 — le mot de passe ne quitte jamais Googlepassword

Toute la redirection existe pour ça : l'utilisateur tape son mot de passe sur accounts.google.com, en TLS, jamais sur votre application. Votre serveur ne voit ni le mot de passe, ni le second facteur.

Ce que fait Google (côté serveur d'autorisation)

  • Réception du mot de passe sur une connexion TLS vers un domaine Google.
  • Vérification contre un hash salé et étiré (fonction lente de type scrypt/dérivée + pepper côté serveur) — le mot de passe en clair n'est jamais stocké.
  • Défi MFA éventuel (TOTP, clé de sécurité WebAuthn/FIDO2, prompt sur mobile).
  • Succès → Google pose un cookie de session sur .google.com et émet le code d'autorisation. Votre app ne reçoit que ce code.

C'est ce cookie de session Google qui explique qu'aux connexions suivantes, l'écran ne redemande pas le mot de passe : Google vous reconnaît déjà, il ne reste que le consentement aux scopes.

⚠︎
Règle d'or

Si votre application voit le mot de passe Google de l'utilisateur, ce n'est pas OAuth — c'est un anti-pattern (au mieux) ou du phishing (au pire). La séparation des domaines est la garantie de sécurité centrale du flux.

PKCE — la preuve que c'est bien vous qui échangez le codeSHA-256

Le code transite par le navigateur : il peut être intercepté (extension malveillante, redirection piégée). PKCE (RFC 7636) neutralise le vol : le code ne vaut rien sans un secret que seule votre app connaît, et qui n'a jamais transité en clair.

Génération (étape 1) & vérification (étape 6)
Côté app · à l'autorisation
code_verifieraléatoire · 43–128 car. · gardé secret
SHA-256
BASE64URL
code_challengeseul lui est envoyé →
Côté Google · à l'échange
reçoit code_verifierenvoyé au token endpoint
recalcule
SHA-256 → BASE64URL
résultat == code_challenge mémorisé ?
✓ oui → échange✗ non → refus

SHA-256 est à sens unique : un attaquant qui intercepte le code et le code_challenge ne peut pas retrouver le code_verifier, donc ne peut pas échanger le code.

  • code_verifier : chaîne aléatoire à forte entropie, 43 à 128 caractères non réservés. Généré et gardé par l'app, jamais envoyé à l'étape 1.
  • code_challenge = BASE64URL( SHA256( code_verifier ) ), avec code_challenge_method=S256. C'est lui seul qui part dans la requête d'autorisation.
Calcul réel en shell (openssl)
# 1. code_verifier : aléatoire, non réservé
code_verifier=$(openssl rand -base64 96 | tr -d '\n=+/' | cut -c1-64)

# 2. code_challenge = BASE64URL( SHA256(code_verifier) )
code_challenge=$(printf '%s' "$code_verifier" \
  | openssl dgst -binary -sha256 \
  | openssl base64 | tr -d '=' | tr '/+' '_-')

À l'échange (étape 6), l'app envoie le code_verifier. Google recalcule SHA256(code_verifier) et le compare au code_challenge mémorisé à l'étape 1. Un attaquant qui a volé le code n'a pas le verifier → l'échange échoue. SHA-256 étant à sens unique, le challenge intercepté ne permet pas de retrouver le verifier.

client_id & client_secret — l'identité de l'applicationapp secrets

Deux identifiants distincts, émis à l'enregistrement de l'app dans Google Cloud :

  • client_id — public (ex. 1234-abc.apps.googleusercontent.com). Il identifie l'app, apparaît dans l'URL, et devient l'aud de l'id_token.
  • client_secret — un secret partagé entre votre serveur et Google. Présenté au token endpoint pour prouver que l'échange vient bien de la vraie app.

Il s'envoie soit en en-tête HTTP Basic, soit dans le corps du POST :

Authentification du client au token endpoint
# Variante HTTP Basic (recommandée)
Authorization: Basic BASE64( client_id ":" client_secret )

# Variante corps de requête
client_id=1234-abc.apps.googleusercontent.com&client_secret=GOCSPX-...
Type de clientPeut garder un secret ?Ce qui authentifie l'échange
Confidentiel
back-end serveur
Ouiclient_secret (+ PKCE en défense de fond)
Public
SPA, mobile, desktop
Non — le binaire/JS est distribuablePKCE seul : le code_verifier remplace le secret
⚠︎
Un secret dans une app publique n'est pas un secret

Une SPA ou une app mobile ne peut pas cacher un client_secret (il est extractible du bundle). C'est précisément pourquoi PKCE existe : il fournit une preuve dynamique par requête, sans secret statique embarqué.

state & nonce — deux aléas, deux attaques bloquéesCSRF · rejeu
  • state — valeur aléatoire liée à la session du navigateur, renvoyée telle quelle par Google. L'app la recompare au retour : si elle ne correspond pas, la réponse a été injectée par un tiers → anti-CSRF.
  • nonce — valeur aléatoire envoyée à l'étape 1 ; Google la réinjecte dans l'id_token. L'app vérifie que le nonce du token correspond à celui qu'elle a émis → un ancien id_token rejoué ne passe pas : anti-rejeu, et liaison de l'id_token à cette requête précise.
Signature de l'id_token (RS256) puis vérification par JWKSRSA · SHA-256

L'id_token est un JWS : Google le signe avec sa clé privée RSA, votre app le vérifie avec la clé publique correspondante — sans jamais rejouer d'appel à Google (les clés sont mises en cache).

Signature côté Google · vérification côté app — deux miroirs
① Signature (Google, clé privée)
headerpayload
BASE64URL + « . »
signing_input
SHA-256
empreinte
RSA-sign · clé privée
id_tokensigning_input . BASE64URL(signature)
② Vérification (votre app, clé publique)
id_token reçu
découpe en 3
header · payload · signature
header.kid
JWKS → clé publique (n, e)
+ SHA-256 du signing_input
RSA-verify(signature, empreinte, clé pub.)
✓ signature valide → contrôler iss · aud · exp · nonce

La clé privée ne quitte jamais Google ; la clé publique est diffusée librement via JWKS. Le kid du header sélectionne la bonne clé — ce qui permet à Google de faire tourner ses clés sans casser les vérifications.

Comment Google le signe

RS256 = RSASSA-PKCS1-v1_5 sur un SHA-256
signing_input = BASE64URL(header_json) + "." + BASE64URL(payload_json)
signature     = RSASSA-PKCS1-v1_5( SHA-256, cléPrivéeGoogle, signing_input )
id_token      = signing_input + "." + BASE64URL(signature)

// header : { "alg":"RS256", "kid":"a1b2c3d4", "typ":"JWT" }
// le kid identifie LAQUELLE des clés publiques a servi

Comment votre app le vérifie

  1. Lire le kid du header → choisir la bonne clé publique dans le JWKS (https://www.googleapis.com/oauth2/v3/certs, mis en cache selon les en-têtes HTTP).
  2. Recomposer signing_input, calculer son SHA-256, et vérifier la signature RSA avec la clé publique (n, e).
  3. Signature valide → valider les claims : iss, aud=votre client_id, exp non dépassé, nonce attendu.
En pratique, une lib fait tout ça — Python
from google.oauth2 import id_token
from google.auth.transport import requests

# récupère le JWKS, choisit la clé via kid, vérifie la
# signature RSA + iss/aud/exp — lève une exception sinon
claims = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)
print(claims["email"], claims["sub"])

Rotation : Google fait tourner ses clés régulièrement. Le kid permet de conserver plusieurs clés valides simultanément et de basculer sans casser les vérifications en cours. Décodez un id_token vous-même en section 07.

Web app côté navigateur : Google Identity Services (GIS)

Pour un simple « Se connecter avec Google », GIS gère le flux et vous rend directement un id_token à vérifier côté serveur — pas besoin d'implémenter le flux à la main. Pour accéder à des API Google au nom de l'utilisateur, on garde le flux Authorization Code ci-dessus.

⚠︎
Ne jamais faire confiance à un access token pour l'identité

L'access token est opaque et ne prouve rien sur l'utilisateur. Pour l'identité, vérifiez l'id_token (signature + iss/aud/exp) ou appelez https://openidconnect.googleapis.com/v1/userinfo. Révocation : https://oauth2.googleapis.com/revoke.

02
Identité machine · serveur à serveur

Service Accounts & Application Default Credentials

Quand c'est un programme qui s'authentifie (pas d'humain, pas d'écran de consentement). Le compte de service est une identité, avec un email …@projet.iam.gserviceaccount.com.

Un service account (SA) possède une paire de clés. Le programme signe un JWT avec la clé privée, prouvant qu'il détient l'identité, puis l'échange contre un access token. C'est le flux 2-legged / JWT Bearer : pas de troisième partie, pas d'utilisateur.

ADC — comment le SDK trouve les credentials tout seul

Application Default Credentials est la stratégie de résolution des bibliothèques Google (google-auth, etc.). Vous n'écrivez pas de clé dans le code : la lib cherche, dans l'ordre, la première source disponible.

La cascade ADC : première source trouvée = gagnante
1. GOOGLE_APPLICATION_CREDENTIALS est définie ?
oui →
Fichier de clé / config pointé par la variable
2. Creds posés par gcloud auth application-default login ?
oui →
Credentials utilisateur (poste de dev)
3. Metadata server joignable (GCE · GKE · Cloud Run · Functions) ?
oui →
Token de l'identité attachée — aucune clé sur disque
4. Rien de tout ça
alors
Échec : « could not find default credentials »

Sur GCP, la source 3 répond presque toujours : on ne configure rien. Les clés exportées (source 1) sont à réserver aux environnements hors GCP sans fédération.

La cascade ADC, dans l'ordre

1. Variable GOOGLE_APPLICATION_CREDENTIALS → chemin d'un fichier de clé/config. 2. Credentials utilisateur posés par gcloud auth application-default login. 3. Metadata server de la plateforme (GCE, GKE, Cloud Run, Cloud Functions) : le token est servi automatiquement, sans aucune clé sur disque. → Sur GCP, on ne configure généralement rien.

Depuis une VM / un conteneur GCP — aucune clé requise
GET http://metadata.google.internal/computeMetadata/v1/
       instance/service-accounts/default/token
Metadata-Flavor: Google

// → { "access_token": "ya29...", "expires_in": 3599, ... }

Plutôt que d'exporter une clé, on emprunte l'identité d'un SA depuis une identité déjà authentifiée (nécessite le rôle roles/iam.serviceAccountTokenCreator).

Générer un access token par impersonation
POST https://iamcredentials.googleapis.com/v1/projects/-/
       serviceAccounts/cible@projet.iam.gserviceaccount.com:generateAccessToken
Authorization: Bearer <token de l'appelant>

{ "scope": ["https://www.googleapis.com/auth/cloud-platform"] }
Python — le code est identique partout grâce à ADC
import google.auth
from google.cloud import storage

# Aucune clé en dur : ADC résout la source (var env,
# gcloud, ou metadata server selon l'environnement).
credentials, project = google.auth.default()
client = storage.Client(credentials=credentials, project=project)
Sous le capot : du fichier de clé au token, pas à pasRSA · SHA-256

Ici aucun mot de passe, aucun utilisateur. La preuve d'identité est purement cryptographique : le programme détient la clé privée du SA, il signe un JWT, Google vérifie avec la clé publique qu'il connaît déjà.

1. Ce que contient le fichier de clé JSON

service-account-key.json (champs utiles)
{
  "type": "service_account",
  "private_key_id": "a1b2c3d4...",      // → devient le kid du JWT
  "private_key": "-----BEGIN PRIVATE KEY-----\n...",
  "client_email": "sa@projet.iam.gserviceaccount.com",
  "token_uri": "https://oauth2.googleapis.com/token"
}

2. Le programme forge et signe le JWT (RS256)

Assertion = header . claims . signature
header = { "alg":"RS256", "typ":"JWT", "kid": private_key_id }
claims = {
  "iss": "sa@projet.iam.gserviceaccount.com",   // = client_email
  "sub": "sa@projet.iam.gserviceaccount.com",
  "aud": "https://oauth2.googleapis.com/token",  // le destinataire
  "scope": "https://www.googleapis.com/auth/cloud-platform",
  "iat": now, "exp": now + 3600           // ≤ 1 h
}
signing_input = BASE64URL(header) + "." + BASE64URL(claims)
assertion     = signing_input + "." +
                BASE64URL( RSA_sign(SHA-256, private_key, signing_input) )

3. Échange contre un access token

POST au token endpoint
POST https://oauth2.googleapis.com/token
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&
assertion=<le JWT signé ci-dessus>

4. Comment Google vérifie

Google lit le kid, récupère la clé publique du SA correspondante (publiée sur https://www.googleapis.com/robot/v1/metadata/x509/<email-du-sa>), vérifie la signature RSA, contrôle aud/exp, puis renvoie l'access token. La clé privée, elle, ne quitte jamais votre programme.

Variante : JWT auto-signé, sans aller-retour

Pour beaucoup d'API GCP, on peut présenter directement le JWT auto-signé comme credential (avec aud = l'API cible) et sauter l'étape 3 : pas d'échange réseau, l'API vérifie elle-même la signature. C'est ce que font les libs ADC pour réduire la latence.

⚠︎
Les clés JSON exportées sont le principal facteur de fuite

Une clé .json de SA est un secret long terme sans expiration : commitée par erreur, elle donne un accès durable. Préférez, dans l'ordre : identité attachée à la plateforme (metadata) → impersonation → Workload Identity Federation (§03). Réservez les clés exportées aux cas où rien d'autre n'est possible.

03
Identité machine · sans clé, depuis l'extérieur

Workload Identity Federation

Le problème : une charge de travail hors GCP (GitHub Actions, AWS, Azure, un cluster K8s tiers) a besoin d'appeler des API Google — sans stocker de clé JSON. La solution : échanger le jeton d'identité déjà émis par son environnement contre un token Google.

Le principe est un échange de token (RFC 8693). GCP fait confiance à un fournisseur d'identité externe (un workload identity pool + provider). La charge présente le jeton OIDC/SAML que son environnement lui donne déjà ; le service STS de Google le valide selon des conditions d'attributs, et renvoie un access token Google fédéré — éventuellement suivi d'une impersonation de SA.

Exemple concret : GitHub Actions

GitHub émet, pour chaque job, un jeton OIDC signé décrivant le dépôt, la branche, l'environnement. On configure GCP pour l'accepter et mapper ses claims sur des attributs, avec des conditions (n'accepter que tel dépôt/branche).

.github/workflows — aucune clé stockée dans les secrets
permissions:
  id-token: write   # autorise GitHub à émettre le jeton OIDC
steps:
  - uses: google-github-actions/auth@v2
    with:
      workload_identity_provider: projects/123/locations/global/
        workloadIdentityPools/gh-pool/providers/gh-provider
      service_account: deployer@projet.iam.gserviceaccount.com
L'échange sous-jacent, côté STS
POST https://sts.googleapis.com/v1/token
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=<JWT OIDC de GitHub>&
subject_token_type=urn:ietf:params:oauth:token-type:jwt&
audience=//iam.googleapis.com/projects/123/.../providers/gh-provider&
requested_token_type=urn:ietf:params:oauth:token-type:access_token
Sous le capot : comment STS valide un jeton qu'il n'a pas émisJWKS externe · CEL

Le point subtil : le JWT présenté vient d'un émetteur externe (GitHub, AWS…). Google ne l'a pas signé et n'a pas la clé privée. La confiance est établie une fois, à la configuration du provider, puis vérifiée à chaque échange.

Ce que déclare le provider (configuration, une fois)

  • L'issuer URI de l'IdP externe (ex. https://token.actions.githubusercontent.com).
  • Les audiences autorisées du jeton externe.
  • Un mapping d'attributs (claims externes → attributs Google, ex. google.subject = assertion.sub).
  • Une condition d'attributs en CEL, ex. n'accepter que tel dépôt/branche.

À chaque échange (runtime)

  1. STS lit le iss du JWT externe, récupère la découverte OIDC de cet émetteur (/.well-known/openid-configuration) puis son JWKS, et vérifie la signature du jeton avec la clé publique de l'IdP externe (sélectionnée par kid).
  2. Contrôle des claims : iss attendu, aud autorisée, exp non dépassé.
  3. Application du mapping d'attributs, puis évaluation de la condition CEL — si elle est fausse, l'échange est refusé.
  4. Émission d'un access token fédéré, courte durée, portée réduite.
Condition d'attributs (CEL) — ex. verrouiller le dépôt et la branche
attribute.repository == "mon-org/mon-repo" &&
assertion.ref == "refs/heads/main"

Cas AWS : pas d'OIDC. La charge présente une requête GetCallerIdentity signée (SigV4) ; STS la relaie à AWS pour prouver l'identité IAM appelante, puis applique le même mapping/condition.

L'échange de token, du jeton externe à l'accès GCP
Charge externejob CI / cloud tiers
demande
IdP externeGitHub · AWS…
JWT OIDC
STS
vérif. signature (JWKS ext.)
+ condition CEL
token fédéré
Impersonationoptionnelle · SA
access_token
API Google

Le point clé : le JWT est signé par l'IdP externe, pas par Google. STS récupère le JWKS de cet émetteur pour vérifier la signature, puis n'accepte l'échange que si la condition CEL (dépôt, branche…) est vraie. Aucune clé Google n'est stockée dans la charge.

À ne pas confondre : Workload Identity for GKE

Même famille d'idée, périmètre différent : à l'intérieur d'un cluster GKE, on lie un ServiceAccount Kubernetes (KSA) à un service account Google (GSA). Les pods obtiennent alors des tokens Google via le metadata server, sans clé. « Federation » vise l'extérieur de GCP ; « Workload Identity for GKE » vise l'intérieur d'un cluster.

Le gain

Zéro secret longue durée à gérer, faire tourner ou révoquer. La confiance repose sur l'IdP externe et sur des conditions d'attributs précises (dépôt, branche, sujet) — bien plus sûr qu'une clé JSON dans un secret de CI.

04
Transversal · l'autorisation

IAM — qui a le droit de faire quoi, où

Les sections 01–03 répondent à « qui es-tu ». IAM répond à « as-tu le droit ». C'est la couche que tous les tokens traversent au moment de l'appel d'API sur GCP.

Le modèle tient en une phrase : un principal reçoit un rôle sur une ressource. Ce triplet est une binding ; l'ensemble des bindings d'une ressource forme sa policy.

🧩
Principal · Rôle · Ressource

Principal (member) : un utilisateur, un groupe, un service account, une identité fédérée, un domaine. Rôle : un paquet de permissions (ex. roles/storage.objectViewer). Ressource : le projet, un bucket, une table… Les permissions élémentaires ont la forme service.resource.verbe, ex. storage.objects.get.

Trois familles de rôles

  • BasicOwner, Editor, Viewer. Très larges, hérités de l'ancien modèle : à éviter en production.
  • Predefined — cadrés par service et finement dosés (ex. roles/bigquery.dataViewer). Le choix par défaut.
  • Custom — vous composez la liste exacte de permissions pour coller au moindre privilège.

Héritage : la hiérarchie des ressources

Les policies se cumulent en descendant : Organisation → Dossier → Projet → Ressource. Un rôle accordé au niveau projet s'applique à toutes ses ressources. IAM est additif : il n'existe pas de « deny » implicite entre bindings (les Deny policies explicites existent mais sont un mécanisme distinct).

Hiérarchie des ressources — les policies se cumulent en descendant
Organisation exemple.frpolicy · admins org
└─
Dossier Productionpolicy · équipe prod
└─
Projet app-prodpolicy · roles/editor
└─
Ressource bucket logs-…policy · objectViewer

Un rôle accordé au niveau Projet s'applique à toutes ses ressources. L'effet total sur le bucket = somme des bindings hérités (org + dossier + projet + ressource). IAM est additif : il suffit qu'une binding accorde la permission pour que l'accès passe.

Extrait d'une IAM policy (get-iam-policy)
{
  "bindings": [
    {
      "role": "roles/storage.objectViewer",
      "members": [
        "user:alice@exemple.fr",
        "serviceAccount:app@projet.iam.gserviceaccount.com",
        "group:data@exemple.fr"
      ],
      "condition": {
        "title": "seulement-bucket-logs",
        "expression": "resource.name.startsWith('projects/_/buckets/logs-')"
      }
    }
  ]
}
Comment le token devient une décision

À chaque appel d'API, GCP résout l'identité du token (utilisateur, SA, identité fédérée), agrège toutes les bindings applicables via la hiérarchie, évalue les conditions, et autorise si au moins une binding accorde la permission requise. C'est là — et pas à l'émission du token — que l'accès est réellement accordé ou refusé.

05
En pratique · CLI & messagerie

gcloud CLI & les spécificités Gmail

Deux points de friction concrets : distinguer les trois gcloud auth qui ne font pas la même chose, et comprendre pourquoi envoyer un mail via Gmail ne se fait plus avec un mot de passe.

Les trois gcloud auth à ne pas confondre

CommandeAuthentifie…Écrit quoi / où
gcloud auth loginVous (humain), via OAuth navigateurCredentials pour la CLI gcloud elle-même
gcloud auth application-default loginVous, mais pour ADC (le code / SDK)application_default_credentials.json — lu par les libs, pas par gcloud
gcloud auth activate-service-accountUn service account via sa cléActive la clé JSON pour gcloud (contexte non-interactif / CI)
Le piège classique

gcloud auth login ne suffit pas pour que votre script Python trouve des credentials : gcloud et ADC sont deux stocks séparés. Pour le code, c'est gcloud auth application-default login qu'il faut (§02, cascade ADC).

Gmail : plus de mot de passe, place à OAuth

Google a supprimé l'option « applications moins sécurisées » : on n'envoie plus de mail en passant son mot de passe de compte en clair au SMTP. Deux voies subsistent.

Appeler l'API Gmail avec un access token OAuth (flux §01) portant le bon scope. Granularité fine par scope :

  • https://www.googleapis.com/auth/gmail.send — envoyer uniquement
  • https://www.googleapis.com/auth/gmail.readonly — lire
  • https://mail.google.com/ — accès complet (sensible, à éviter si possible)

Le SMTP classique smtp.gmail.com reste utilisable, mais l'authentification passe par XOAUTH2 : on présente l'access token OAuth au lieu d'un mot de passe. Idéal pour brancher une lib d'emailing existante sans réécrire vers l'API.

Chaîne d'auth SASL XOAUTH2 (avant encodage base64)
user=alice@exemple.fr^Aauth=Bearer ya29.a0Af...^A^A
# ^A = octet 0x01. Le tout encodé base64 → AUTH XOAUTH2

Les App Passwords (mots de passe d'application à 16 caractères) subsistent pour les protocoles legacy, mais uniquement si la validation en deux étapes est activée, et ils sont progressivement restreints. À considérer comme une solution de repli, pas comme une cible d'architecture.

🏢
Workspace : domain-wide delegation

Sur un domaine Google Workspace, un service account peut être autorisé par l'admin à emprunter l'identité de n'importe quel utilisateur du domaine (domain-wide delegation) pour agir en son nom via les API — utile pour les intégrations serveur, mais puissant : à cadrer strictement par scopes.

06
Référence

Catalogue des services & leur méthode d'auth

Quel service Google s'authentifie comment. Filtrez par méthode ou cherchez un service. Chaque ligne renvoie au mécanisme correspondant plus haut.

ServiceDescriptionMéthode d'auth typiqueNotes
07
Outil · 100% local

Décodeur d'ID token (JWT)

Collez un JWT pour voir son header et son payload en clair. Décodage local seulement — la signature n'est pas vérifiée (il faudrait les clés JWKS de l'émetteur). Rien n'est envoyé nulle part.

eyJhbGciOiJSUzI1Ni….eyJpc3MiOiJhY2NvdW50….Xr8f2K…signature

header   payload (claims)   signature — trois parties base64url séparées par des points.

08
Aller plus loin

Références & blogs de sécurité

Pour creuser : les spécifications faisant autorité, les blogs d'experts OAuth/OIDC, la recherche offensive (tests d'intrusion) et les ressources spécifiques à la sécurité GCP/IAM. Liens externes, ouverts dans un nouvel onglet.

🔗
Ressources externes, susceptibles d'évoluer

Ces liens pointent vers des sites tiers dont le contenu et les URL peuvent changer. Les specs (RFC, OpenID) font foi ; les blogs apportent le retour d'expérience et l'angle offensif. En cas de doute sur un endpoint ou un scope Google, la source ultime reste la documentation officielle liée en §01–06.