Bug Description
Bug Description
When using a Google Service Account with Domain-Wide Delegation to send emails via the Gmail API in n8n, the credential “Google Service Account API” fails with the following error when impersonation is enabled:
401 - {"error":"unauthorized\_client","error\_description":"Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested."}
Despite all delegation, roles, and scopes being correctly configured (verified manually via curl and JWT), the n8n implementation does not generate the JWT properly for impersonation. This affects nodes like Gmail v2 and HTTP Request if the Service Account API credential is used.
✅ Manual requests using a properly signed JWT do work, which confirms that the issue is with how n8n builds and signs the JWT internally.
To Reproduce
To Reproduce
1. Go to `Credentials > Google Service Account API`
2. Input:
- `Service Account Email`: svc-account@your-project.iam.gserviceaccount.com
- `Private Key`: -----BEGIN PRIVATE KEY-----\nMIIE...base64...==\n-----END PRIVATE KEY-----
- `Impersonate a User`: svc-account@your-project.iam.gserviceaccount.com
- `Scope(s)`:
```
https://mail.google.com/
```
3. Click “Test Connection” → **Succeeds**
4. Create Gmail Node → Operation `Send`
5. Run the workflow
- Now test outside n8n using this
bash script flow (works):
#!/bin/bash
set -e
KEY_FILE="/home/diegoug/Downloads/premium-state-463215-m9-9f61b0fc17ec.json"
USER="convivencia@venturalaprosperidad.com"
TO="diego.uribe.gamez@gmail.com"
SUBJECT="Correo de prueba desde Service Account"
BODY="Hola, este es un mensaje enviado desde una cuenta de servicio."
echo "📁 [INFO] Usando archivo JSON de la cuenta de servicio: $KEY_FILE"
EMAIL=$(jq -r .client_email "$KEY_FILE")
PRIVATE_KEY=$(jq -r .private_key "$KEY_FILE" | sed 's/\\n/\n/g')
echo ""
echo "🔐 ====== DATOS DE CREDENCIAL GOOGLE ======"
echo "Service Account Email: $EMAIL"
echo "User to impersonate (sub): $USER"
echo ""
echo "🔑 Private Key:"
echo "-----BEGIN PRIVATE KEY-----"
jq -r .private_key "$KEY_FILE" | sed 's/\\n/\n/g'
echo "-----END PRIVATE KEY-----"
echo "🔐 Longitud de clave privada: ${#PRIVATE_KEY}"
echo ""
# Timestamps
NOW=$(date +%s)
EXP=$(($NOW + 3600))
HEADER_JSON='{"alg":"RS256","typ":"JWT"}'
HEADER=$(echo -n "$HEADER_JSON" | openssl base64 -e | tr -d '=' | tr '/+' '_-' | tr -d '\n')
CLAIMS_JSON="{\"iss\":\"$EMAIL\",\"scope\":\"https://mail.google.com/\",\"aud\":\"https://oauth2.googleapis.com/token\",\"exp\":$EXP,\"iat\":$NOW,\"sub\":\"$USER\"}"
CLAIMS=$(echo -n "$CLAIMS_JSON" | openssl base64 -e | tr -d '=' | tr '/+' '_-' | tr -d '\n')
SIGN_INPUT="$HEADER.$CLAIMS"
SIGNATURE=$(echo -n "$SIGN_INPUT" | openssl dgst -sha256 -sign <(echo -e "$PRIVATE_KEY") | openssl base64 -e | tr -d '=' | tr '/+' '_-' | tr -d '\n')
JWT="$SIGN_INPUT.$SIGNATURE"
echo ""
echo "🔏 ====== JWT FIRMADO ======"
echo "JWT completo:"
echo "$JWT"
echo ""
# Solicitud de token
RESPONSE=$(curl -s -X POST https://oauth2.googleapis.com/token \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=$JWT")
ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)
if [[ "$ACCESS_TOKEN" == "null" || -z "$ACCESS_TOKEN" ]]; then
echo "❌ ERROR: No se pudo obtener el access_token."
echo "$RESPONSE"
exit 1
fi
echo ""
echo "🔑 ====== ACCESS TOKEN ======"
echo "$ACCESS_TOKEN"
echo ""
# MIME email
MIME_MESSAGE=$(printf "From: %s\nTo: %s\nSubject: %s\n\n%s" "$USER" "$TO" "$SUBJECT" "$BODY")
EMAIL_RAW=$(echo -n "$MIME_MESSAGE" | base64 -w 0)
echo "✉️ ====== EMAIL MIME EN BASE64 ======"
echo "$EMAIL_RAW"
echo ""
# Enviar correo
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"raw\":\"$EMAIL_RAW\"}" \
"https://gmail.googleapis.com/gmail/v1/users/$USER/messages/send")
HTTP=$(echo "$RESPONSE" | grep HTTP_STATUS | cut -d':' -f2)
BODY_RES=$(echo "$RESPONSE" | sed -e 's/HTTP_STATUS\:.*//g')
echo "🔍 ====== RESPUESTA DE GMAIL API ======"
echo "$BODY_RES"
echo ""
if [[ "$HTTP" -ne 200 ]]; then
echo "❌ [FAIL] Error HTTP $HTTP al enviar el correo."
else
echo "✅ [SUCCESS] Correo enviado exitosamente a $TO desde $USER."
fi
Expected behavior
🎯 Expected behavior
n8n should generate a correct JWT including:
"sub" (for impersonation)
"iss", "scope", "aud", "iat", "exp"
It should allow using impersonation seamlessly via Gmail or HTTP nodes using Google Service Account API credentials, respecting domain-wide delegation. Email should be sent as the impersonated user without 401 or precondition errors.
Operating System
n8nio/n8n
n8n Version
1.103.1
Node.js Version
v22.17.0
Database
PostgreSQL
Execution mode
main (default)
Bug Description
Bug Description
When using a Google Service Account with Domain-Wide Delegation to send emails via the Gmail API in n8n, the credential “Google Service Account API” fails with the following error when impersonation is enabled:
Despite all delegation, roles, and scopes being correctly configured (verified manually via
curland JWT), the n8n implementation does not generate the JWT properly for impersonation. This affects nodes like Gmail v2 and HTTP Request if the Service Account API credential is used.✅ Manual requests using a properly signed JWT do work, which confirms that the issue is with how n8n builds and signs the JWT internally.
To Reproduce
To Reproduce
bash scriptflow (works):Expected behavior
🎯 Expected behavior
n8n should generate a correct JWT including:
"sub"(for impersonation)"iss","scope","aud","iat","exp"It should allow using impersonation seamlessly via Gmail or HTTP nodes using Google Service Account API credentials, respecting domain-wide delegation. Email should be sent as the impersonated user without 401 or precondition errors.
Operating System
n8nio/n8n
n8n Version
1.103.1
Node.js Version
v22.17.0
Database
PostgreSQL
Execution mode
main (default)