{
  "id": "aK1bY1cjRKpQNAqu",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Monitor expiring EntraID application secrets and notify responsible",
  "tags": [
    {
      "id": "0FRymMOqi6UCCYTI",
      "name": "free",
      "createdAt": "2025-12-25T11:06:36.527Z",
      "updatedAt": "2025-12-25T11:06:36.527Z"
    },
    {
      "id": "ks1tninGT7ZLrbrc",
      "name": "template",
      "createdAt": "2025-12-31T15:16:19.399Z",
      "updatedAt": "2025-12-31T15:16:19.399Z"
    }
  ],
  "nodes": [
    {
      "id": "c032ba4b-6ad9-4aaf-9336-7023d25710be",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -208,
        16
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f6886b67-bdcd-42fa-8b28-38d232fa91ae",
      "name": "Split Out Applications",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        464,
        16
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "value"
      },
      "typeVersion": 1
    },
    {
      "id": "0648ee8a-90d8-42f0-a79e-29ec3d37f2c9",
      "name": "Set Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        16,
        16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a5f4cc7c-7377-4370-a610-736ed8267eed",
              "name": "notificationEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "cd0be702-2a6a-463a-99c6-1f093ef79a07",
              "name": "daysBeforeExpiry",
              "type": "number",
              "value": 14
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e9617bcf-8764-4f8f-9f7d-37f3be8a38d3",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1360,
        16
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "1239f798-30b5-4da3-a2eb-cee7f25a676e",
      "name": "Filter Client Secrets",
      "type": "n8n-nodes-base.filter",
      "position": [
        912,
        -80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "5b96c25e-f8f8-4ebf-8aa5-2b430e36b16f",
              "operator": {
                "type": "dateTime",
                "operation": "beforeOrEquals"
              },
              "leftValue": "={{ $json.endDateTime }}",
              "rightValue": "={{ new Date(Date.now() + $('Set Variables').first().json.daysBeforeExpiry * 24 * 60 * 60 * 1000) }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "d9f6dfd9-07b3-4ff5-bef3-1501a27f26eb",
      "name": "Split Out Client Secrets",
      "type": "n8n-nodes-base.splitOut",
      "notes": "Password Credentials are Client secrets in the EntraID UI",
      "position": [
        688,
        -80
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "passwordCredentials"
      },
      "notesInFlow": false,
      "typeVersion": 1
    },
    {
      "id": "02b33d87-3e06-4c10-afec-584b826645c1",
      "name": "Split Out Certificates",
      "type": "n8n-nodes-base.splitOut",
      "notes": "Key Credentials are Certificates in the EntraID UI",
      "position": [
        688,
        112
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "keyCredentials"
      },
      "notesInFlow": false,
      "typeVersion": 1
    },
    {
      "id": "e5e91f10-cbeb-44b9-a89a-a4778afdcdf6",
      "name": "Filter Client Certificates",
      "type": "n8n-nodes-base.filter",
      "position": [
        912,
        112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "5b96c25e-f8f8-4ebf-8aa5-2b430e36b16f",
              "operator": {
                "type": "dateTime",
                "operation": "beforeOrEquals"
              },
              "leftValue": "={{ $json.endDateTime }}",
              "rightValue": "={{ new Date(Date.now() + $('Set Variables').first().json.daysBeforeExpiry * 24 * 60 * 60 * 1000) }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "219314a0-9ce8-4352-b6be-3ffb92f52362",
      "name": "Build Client Secrets Report",
      "type": "n8n-nodes-base.set",
      "position": [
        1136,
        -80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b046951a-7b15-4ad3-ad6c-0bec7b2622b3",
              "name": "applicationName",
              "type": "string",
              "value": "={{ $('Split Out Applications').item.json.displayName }}"
            },
            {
              "id": "3fbb2382-35f5-4e91-82b0-89c03434195d",
              "name": "appId",
              "type": "string",
              "value": "={{ $('Split Out Applications').item.json.appId }}"
            },
            {
              "id": "98798f0e-3102-47cd-9bf6-6e6ae7192363",
              "name": "type",
              "type": "string",
              "value": "Client Secret"
            },
            {
              "id": "9b8ac2ee-9dbe-437c-92d5-3933010d5a8e",
              "name": "clientSecretName",
              "type": "string",
              "value": "={{ $json.displayName }}"
            },
            {
              "id": "5260e16d-0c50-4353-a2f5-c39200a79350",
              "name": "clientSecretId",
              "type": "string",
              "value": "={{ $json.keyId }}"
            },
            {
              "id": "192eaab0-67a9-4a22-aa2d-0c16ff6c0ecb",
              "name": "expiresInDays",
              "type": "number",
              "value": "={{ Math.max(0, Math.floor((Date.parse($json.endDateTime) - Date.now()) / (1000 * 60 * 60 * 24))) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "b603c3e5-0027-48b3-8ca7-fcd82d133657",
      "name": "Build Certificates Report",
      "type": "n8n-nodes-base.set",
      "position": [
        1136,
        112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b046951a-7b15-4ad3-ad6c-0bec7b2622b3",
              "name": "applicationName",
              "type": "string",
              "value": "={{ $('Split Out Applications').item.json.displayName }}"
            },
            {
              "id": "3fbb2382-35f5-4e91-82b0-89c03434195d",
              "name": "appId",
              "type": "string",
              "value": "={{ $('Split Out Applications').item.json.appId }}"
            },
            {
              "id": "98798f0e-3102-47cd-9bf6-6e6ae7192363",
              "name": "type",
              "type": "string",
              "value": "Certificate"
            },
            {
              "id": "9b8ac2ee-9dbe-437c-92d5-3933010d5a8e",
              "name": "certificateName",
              "type": "string",
              "value": "={{ $json.displayName }}"
            },
            {
              "id": "5260e16d-0c50-4353-a2f5-c39200a79350",
              "name": "certificateId",
              "type": "string",
              "value": "={{ $json.keyId }}"
            },
            {
              "id": "192eaab0-67a9-4a22-aa2d-0c16ff6c0ecb",
              "name": "expiresInDays",
              "type": "number",
              "value": "={{ Math.max(0, Math.floor((Date.parse($json.endDateTime) - Date.now()) / (1000 * 60 * 60 * 24))) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d6b1957d-2216-414a-94cf-e1da38d3f517",
      "name": "Get EntraID Applications and Secrets",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        240,
        16
      ],
      "parameters": {
        "url": "https://graph.microsoft.com/v1.0/applications?$select=id,appId,displayName,passwordCredentials,keyCredentials",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api"
      },
      "credentials": {
        "oAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "6e3ff7b9-174a-44fd-8f94-c5a262b0894b",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1584,
        16
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "expiringSecrets"
      },
      "typeVersion": 1
    },
    {
      "id": "8d7fe566-f4ce-4c15-a45b-4c7171398bfe",
      "name": "If Expiring Secrets not empty",
      "type": "n8n-nodes-base.if",
      "position": [
        1792,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "2d188564-2edc-4525-a373-31087dcba601",
              "operator": {
                "type": "array",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.expiringSecrets }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "073c5d14-3bbe-427e-a6eb-7343071ef45a",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        2016,
        112
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "11d6a960-44de-44bf-8002-2f001e3d6b19",
      "name": "HTML Table with Expiring Secrets",
      "type": "n8n-nodes-base.html",
      "position": [
        2016,
        -80
      ],
      "parameters": {
        "html": "<h2 style=\"font-family: Arial, Helvetica, sans-serif; margin: 0 0 12px 0;\">Applications with expiring EntraID secrets</h2>\n\n<table style=\"border-collapse: collapse; width: 100%; background: #ffffff; font-family: Arial, Helvetica, sans-serif;\">\n  <thead>\n    <tr>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Application Name</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">App ID</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Type</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Secret / Certificate Name</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Secret / Certificate ID</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Expires In (Days)</th>\n    </tr>\n  </thead>\n\n  <tbody>\n    {{\n      ($json.expiringSecrets || [])\n        .slice() // avoid mutating original data\n        .sort((a, b) =>\n          (a.applicationName || \"\").localeCompare(b.applicationName || \"\", undefined, { sensitivity: \"base\" })\n        )\n        .map((item, idx) => {\n          const name = item.clientSecretName || item.certificateName || \"\";\n          const id = item.clientSecretId || item.certificateId || \"\";\n          const days = Number(item.expiresInDays ?? \"\");\n\n          const rowBg = (idx % 2 === 1) ? \"background:#fafafa;\" : \"\";\n          const expiresStyle =\n            (days <= 30) ? \"color:#b91c1c;font-weight:600;\" :\n            (days <= 90) ? \"color:#b45309;font-weight:600;\" :\n            \"\";\n\n          return `\n            <tr style=\"${rowBg}\">\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${item.applicationName ?? \"\"}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${item.appId ?? \"\"}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${item.type ?? \"\"}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${name}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${id}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px; ${expiresStyle}\">${item.expiresInDays ?? \"\"}</td>\n            </tr>\n          `;\n        })\n        .join(\"\")\n    }}\n  </tbody>\n</table>\n"
      },
      "typeVersion": 1.2
    },
    {
      "id": "87ad3402-890e-4356-bff0-5a121cf6dcc2",
      "name": "Send email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2224,
        -80
      ],
      "parameters": {
        "html": "={{ $json.html }}",
        "options": {},
        "subject": "Applications with expiring EntraID secrets",
        "toEmail": "={{ $('Set Variables').item.json.notificationEmail }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "86d429d5-168c-4f08-bec5-09a24fc5bde9",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        -128
      ],
      "parameters": {
        "width": 608,
        "height": 368,
        "content": "## How it works\n1. Fetches all Entra ID applications and their credential metadata via Microsoft Graph  \n2. Separates client secrets and certificates into individual entries  \n3. Filters entries that expire within the configured time window  \n4. Builds a normalized list of expiring items with days remaining  \n5. Emails an HTML table report (only if results exist)\n\n## Setup steps\n- Microsoft Entra ID Application with permission:\n  - `Application.Read.All`\n  - Microsoft Graph OAuth2 credentials configured in n8n\n  - Assign the credential to the Get EntraID Applications HTTP Request node\n- Adjust the values of the Set Variables node\n- Configure the Send EMail node to send the email with the report"
      },
      "typeVersion": 1
    },
    {
      "id": "9f166c9c-8eaa-45e5-b647-418b6ea0c946",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 432,
        "content": "## Filter expiring secrets"
      },
      "typeVersion": 1
    },
    {
      "id": "c38ec327-7ead-4c78-93ba-8a02cfeca86b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1568,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 832,
        "height": 432,
        "content": "## Report\nBuild the report as an HTML table and send it via mail"
      },
      "typeVersion": 1
    },
    {
      "id": "34dba612-3b2d-4342-9897-760dfb651c3f",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 224,
        "height": 224,
        "content": "## Variables"
      },
      "typeVersion": 1
    },
    {
      "id": "d682d2a0-d5b6-498a-996f-c2a6fc3ab0e5",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 272,
        "content": "## Get EntraID Applications"
      },
      "typeVersion": 1
    },
    {
      "id": "35e3d142-ad5b-4554-a76a-3aa6f6f21aa4",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        256
      ],
      "parameters": {
        "color": 6,
        "width": 608,
        "height": 144,
        "content": "### Notes\n- Requires EntraID Application\n- Use Client Credentials when adding the OAuth2 credentials in n8n\n- Use a schedule trigger to automatically run this\n- Need help? \u2709\ufe0f **office@sus-tech.at**"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "55b402a4-c0b9-46fd-9887-5b07a7491b6d",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "If Expiring Secrets not empty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Variables": {
      "main": [
        [
          {
            "node": "Get EntraID Applications and Secrets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Client Secrets": {
      "main": [
        [
          {
            "node": "Build Client Secrets Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Applications": {
      "main": [
        [
          {
            "node": "Split Out Client Secrets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split Out Certificates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Certificates": {
      "main": [
        [
          {
            "node": "Filter Client Certificates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Client Secrets": {
      "main": [
        [
          {
            "node": "Filter Client Secrets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Certificates Report": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Filter Client Certificates": {
      "main": [
        [
          {
            "node": "Build Certificates Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Client Secrets Report": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Expiring Secrets not empty": {
      "main": [
        [
          {
            "node": "HTML Table with Expiring Secrets",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML Table with Expiring Secrets": {
      "main": [
        [
          {
            "node": "Send email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get EntraID Applications and Secrets": {
      "main": [
        [
          {
            "node": "Split Out Applications",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Set Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}