{
  "nodes": [
    {
      "id": "4cb478c7-57e8-4028-a39c-0a47cd3edb0a",
      "name": "If Token",
      "type": "n8n-nodes-base.if",
      "position": [
        224,
        544
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "37fd6852-3507-40ed-bf5e-d55d22d7fd7c",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.id_token }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "30a6ed20-9f0a-481f-825a-47affdc14a90",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        608,
        416
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "88d8edde-ab2c-45b3-9abb-1d70d518656a",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ ($json.payload.exp * 1000) - 300000}}",
              "rightValue": "={{ Date.now() }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "27cebdb1-e3c5-445c-99fe-421b43a8535b",
      "name": "Return Values",
      "type": "n8n-nodes-base.set",
      "position": [
        1232,
        560
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "8607e929-d7b7-4ed5-86ca-234dab37a4e0",
              "name": "id_token",
              "type": "string",
              "value": "={{ $json.id_token }}"
            },
            {
              "id": "04db663c-0a3e-4254-be2f-8086ea4f43e7",
              "name": "service_url",
              "type": "string",
              "value": "={{ $('Combine Context').item.json.service_url }}"
            },
            {
              "id": "8cfaf785-9736-4828-9203-20bdb23f6eb8",
              "name": "service_path",
              "type": "string",
              "value": "={{ $('Combine Context').item.json.service_path }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1ff1b7b4-69d1-45de-bdc3-425781ac4fda",
      "name": "Combine Context",
      "type": "n8n-nodes-base.merge",
      "position": [
        0,
        544
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "dd9fbe7d-c064-40be-9afe-bd1c65844c4c",
      "name": "Raise Id",
      "type": "n8n-nodes-base.set",
      "position": [
        880,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "294b0c8a-81d9-47d1-a938-11cd6585bf1c",
              "name": "id_token",
              "type": "string",
              "value": "={{ $('Combine Context').item.json.id_token }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e15593e0-3831-4642-83a4-f1262a781c43",
      "name": "Decode JWT",
      "type": "n8n-nodes-base.jwt",
      "position": [
        416,
        416
      ],
      "parameters": {
        "token": "={{ $json.id_token }}",
        "options": {},
        "operation": "decode"
      },
      "credentials": {
        "jwtAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "cc318a16-9e8e-4b8c-a9dc-518b4119e783",
      "name": "Sign New JWT",
      "type": "n8n-nodes-base.jwt",
      "position": [
        784,
        560
      ],
      "parameters": {
        "options": {},
        "useJson": true,
        "claimsJson": "={\n  \"iss\": \"{{ $json.client_email }}\",\n  \"sub\": \"{{ $json.client_email }}\",\n  \"aud\": \"{{ $json.token_uri }}\",\n  \"iat\": {{ Math.floor(Date.now() / 1000) }},\n  \"exp\": {{ Math.floor(Date.now() / 1000) + 3600 }},\n  \"target_audience\": \"{{ $json.service_url }}\"\n}"
      },
      "credentials": {
        "jwtAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9dfe02d4-ca20-449f-98af-98233233f54f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -736,
        192
      ],
      "parameters": {
        "width": 400,
        "height": 496,
        "content": "## Google Service Auth Workflow\n\n## **Purpose**\nHandles Google Cloud authentication with automatic token refresh. Checks if existing token is valid before requesting new one.\n\n## **How It Works**\n1. Receives optional `id_token`, `service_url`, and `service_path` input\n2. Checks token validity (5-min buffer)\n3. Reuses valid token OR generates new\n4. Returns Bearer YOUR_TOKEN_HERE for Cloud Run Service calls\n\n## **Output**\n* `id_token`: Valid Bearer YOUR_TOKEN_HERE\n* `service_url`: Your API endpoint\n* `service_path`: API path"
      },
      "typeVersion": 1
    },
    {
      "id": "ff9df5f7-eac1-449e-b772-e5e8ec029623",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        -16
      ],
      "parameters": {
        "color": 4,
        "width": 304,
        "height": 512,
        "content": "## Using This Workflow\n\n### **As Sub-Workflow**\nCall from main workflow using Execute Workflow node:\n* Optional Input: `id_token` (for reuse)\n* Returns: Fresh or validated token\n\n### **Token Logic**\n* **Has Valid Token**: Returns existing\n* **Token Expiring Soon**: Gets new token\n* **No Token**: Generates fresh token\n\n#### **5-Minute Buffer**\nToken refreshes if expires within 5 minutes to prevent mid-execution expiration\n\n### **Test Standalone**\n1. Required: Pin `service_url` value."
      },
      "typeVersion": 1
    },
    {
      "id": "f99ed6c6-01a2-480e-bc89-508c098b951a",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        0
      ],
      "parameters": {
        "color": 5,
        "width": 256,
        "height": 384,
        "content": "## Key Features\n### **Automatic Validation**\n* Decodes JWT to check expiration\n* No unnecessary API calls\n* Reduces quota usage\n### **Error Prevention**\n* 5-minute expiration buffer\n* Prevents mid-workflow failures\n* Ensures token validity\n### **Efficient Reuse**\n* Tokens valid for 60 minutes\n* Reuses existing valid tokens\n* Only refreshes when needed"
      },
      "typeVersion": 1
    },
    {
      "id": "2e0584c6-8628-49db-b2fa-6ae7488cff41",
      "name": "Start",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        -448,
        560
      ],
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "id_token"
            },
            {
              "name": "service_url"
            },
            {
              "name": "service_path"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "1995c7ce-b6ca-4c99-b683-73c2fa7581f8",
      "name": "Bearer YOUR_TOKEN_HERE Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1008,
        560
      ],
      "parameters": {
        "url": "={{ $('Vars').item.json.token_uri }}",
        "body": "=grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={{ $json.token }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "form-urlencoded",
        "specifyBody": "string"
      },
      "typeVersion": 4.2
    },
    {
      "id": "d98fd0cd-cd67-4290-9aa1-d0cbf8d98bb9",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        352
      ],
      "parameters": {
        "color": 2,
        "width": 166,
        "height": 240,
        "content": "#### Var Config\n- **client_email** - from `.json key`"
      },
      "typeVersion": 1
    },
    {
      "id": "2c38cc10-1c06-45fc-8283-3a4f3afede0e",
      "name": "Vars",
      "type": "n8n-nodes-base.set",
      "position": [
        -224,
        448
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={\n  \"service_url\": \"{{ $json.service_url }}\",\n  \"client_email\": \"_____INSERT_____\",\n  \"token_uri\": \"https://oauth2.googleapis.com/token\"\n}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "ff91e405-5cdc-4857-bc83-166a18342c64",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        544
      ],
      "parameters": {
        "color": 2,
        "width": 230,
        "height": 432,
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n### Create a JWT Credential\n\nUse the **private_key** from your generated `.json key` file.\n\n- **Key Type:** PEM Key  \n- **Private Key:** `private_key` (full block, including `-----BEGIN PRIVATE KEY-----` \u2026 `END PRIVATE KEY-----`)  \n- **Algorithm:** RS256"
      },
      "typeVersion": 1
    },
    {
      "id": "2f44ed65-9dbe-4713-9153-3c9e49893d19",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -688,
        -32
      ],
      "parameters": {
        "color": 2,
        "width": 672,
        "height": 208,
        "content": "## Required Setup \u2014 Google Cloud Run & Service Account\n### You\u2019ll need to:\n- **Configure a Google Cloud Run service** (set **Require authentication**).\n- **Create a Google Service Account** and grant it **Cloud Run Invoker** permission on that service.\n\nFor a detailed, step-by-step breakdown, see my Medium article:\n\n**[Build a Secure Google Cloud Run API, Then Call It from n8n (Free Tier)](https://medium.com/@marcocodes/build-a-secure-google-cloud-run-api-then-call-it-from-n8n-88c03291a95f)**"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Raise Id",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sign New JWT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Vars": {
      "main": [
        [
          {
            "node": "Combine Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start": {
      "main": [
        [
          {
            "node": "Combine Context",
            "type": "main",
            "index": 1
          },
          {
            "node": "Vars",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Token": {
      "main": [
        [
          {
            "node": "Decode JWT",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sign New JWT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Raise Id": {
      "main": [
        [
          {
            "node": "Return Values",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decode JWT": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sign New JWT": {
      "main": [
        [
          {
            "node": "Bearer YOUR_TOKEN_HERE Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Context": {
      "main": [
        [
          {
            "node": "If Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Bearer Token Request": {
      "main": [
        [
          {
            "node": "Return Values",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}