{
  "id": "oYwnHDD0m2EAdXpi",
  "name": "Register users and authenticate with magic links using Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "dcaa8a9d-abdd-41a8-a359-868318d7f661",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 1904,
        "height": 384,
        "content": "## 1. Login\nValidate username and password against Google Sheets, then reuse or issue a new token and redirect to the auth endpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "44324518-59de-4068-b49d-da5b9cabcf66",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 1232,
        "height": 288,
        "content": "## 0. Register\nCollect an email address via form, generate credentials and a one-time token, save the user to Google Sheets, and send an onboarding email with the login link."
      },
      "typeVersion": 1
    },
    {
      "id": "2472b7c4-cd6e-4194-8d12-59c4249ea7f3",
      "name": "Append or update row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        688,
        320
      ],
      "parameters": {
        "columns": {
          "value": {
            "role": "Property Manager",
            "token": "YOUR_CREDENTIAL_HERE",
            "password": "YOUR_CREDENTIAL_HERE",
            "username": "={{ $json.email }}"
          },
          "schema": [
            {
              "id": "username",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "password",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "password",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "token",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "token",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "username"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "d7f38863-74ee-4bb9-bb4d-2fd445594eea",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        1072
      ],
      "parameters": {
        "color": 7,
        "width": 1696,
        "height": 400,
        "content": "## 2. Auth\nValidate the one-time token via Google Sheets, generate a session id (sid), set an HttpOnly cookie, clear the token, and redirect to the profile page."
      },
      "typeVersion": 1
    },
    {
      "id": "891d65a6-0b6c-474d-b029-d4702c8e4174",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        1552
      ],
      "parameters": {
        "color": 7,
        "width": 1424,
        "height": 416,
        "content": "## 3. Profile\nRead the sid cookie, look up the active session in Google Sheets, and render a profile page \u2014 or redirect to login if the session is invalid."
      },
      "typeVersion": 1
    },
    {
      "id": "fee598fe-64d8-469e-be7d-745bd2be40e4",
      "name": "Prepare sid",
      "type": "n8n-nodes-base.code",
      "position": [
        1248,
        1088
      ],
      "parameters": {
        "jsCode": "function makeSid(len = 48) {\n  const alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n  let out = \"\";\n  // Mix time + random to reduce collisions\n  let seed = Date.now().toString(36) + \"-\" + Math.random().toString(36).slice(2);\n\n  out += seed.replace(/[^a-z0-9-]/gi, \"\").slice(0, 12);\n\n  while (out.length < len) {\n    out += alphabet[Math.floor(Math.random() * alphabet.length)];\n  }\n  return out.slice(0, len);\n}\n\nconst sid = makeSid(64);\n\nreturn { json: { sid } };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f8747e29-893d-4e5d-916c-bb56975eb881",
      "name": "Generate token",
      "type": "n8n-nodes-base.code",
      "position": [
        1328,
        816
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function makeToken(len = 32) {\n  let out = '';\n  while (out.length < len) {\n    out += Math.random().toString(36).slice(2); // Strip the leading \"0.\"\n  }\n  return out.slice(0, len);\n}\nconst token = makeToken()\nreturn {token};"
      },
      "typeVersion": 2
    },
    {
      "id": "fd48786d-6e97-4361-bce0-40dc80596ba2",
      "name": "If has token in DB",
      "type": "n8n-nodes-base.if",
      "position": [
        976,
        640
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "df0d0deb-4db8-4ef7-9bd0-c79ab6fb4f35",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('Check username/password').item.json.token }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "43b3e0a0-c749-4b08-8cc1-0e84fe357bed",
      "name": "If has token",
      "type": "n8n-nodes-base.if",
      "position": [
        464,
        1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "51cff10a-dac2-4480-a177-3b6166e4fbdf",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('Auth').item.json.query.token }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "18b1b20d-06cb-44c5-b295-2966ff15c84f",
      "name": "Search by token",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        736,
        1184
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Auth').item.json.query.token || '' }}",
              "lookupColumn": "token"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "dc27c8ff-75d6-4448-afb2-512d21cb08bf",
      "name": "On form register",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        224,
        320
      ],
      "parameters": {
        "options": {
          "path": "register"
        },
        "formTitle": "Register with your email",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Email",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Password will be sent to your email"
      },
      "typeVersion": 2.2
    },
    {
      "id": "135c6108-45ec-45e6-b1e2-317e40a9004f",
      "name": "Create password and token",
      "type": "n8n-nodes-base.code",
      "position": [
        448,
        320
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function generatePassword(length = 12) {\n  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n  let out = '';\n  for (let i = 0; i < length; i++) {\n    const idx = Math.floor(Math.random() * chars.length);\n    out += chars[idx];\n  }\n  return out;\n}\nfunction makeToken(len = 32) {\n  let out = '';\n  while (out.length < len) {\n    out += Math.random().toString(36).slice(2); // Strip the leading \"0.\"\n  }\n  return out.slice(0, len);\n}\nconst email = $json.Email\nconst password = generatePassword()\nconst token = makeToken()\nreturn {email, password, token};"
      },
      "typeVersion": 2
    },
    {
      "id": "fec2646b-dd15-4e8c-ad07-9fa7de065c9a",
      "name": "Send username/password",
      "type": "n8n-nodes-base.gmail",
      "position": [
        928,
        320
      ],
      "parameters": {
        "sendTo": "={{ $('Create password and token').item.json.email }}",
        "message": "=Link to login: https://YOUR_DOMAIN/webhook/auth?token={{ $('Create password and token').item.json.token }}\n<br/>\nUsername: {{ $('Create password and token').item.json.email }}\n<br/>\nPassword: {{ $('Create password and token').item.json.password }}",
        "options": {},
        "subject": "=Your login credentials"
      },
      "typeVersion": 2.1
    },
    {
      "id": "f3241140-939d-4871-8bf3-25dc9c69b653",
      "name": "Notice register success",
      "type": "n8n-nodes-base.form",
      "position": [
        1184,
        320
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "completionTitle": "Registration successful",
        "completionMessage": "=<p><strong>\u2705 Please check email {{ $('Create password and token').item.json.email }} to get your login credentials</strong></p>\n<p>\n  After that, you can login here:<br>\n  <a href=\"https://YOUR_DOMAIN/form/login\" target=\"_blank\" rel=\"noopener noreferrer\">\n    https://YOUR_DOMAIN/form/login\n  </a>\n</p>"
      },
      "typeVersion": 1
    },
    {
      "id": "3da0b92c-edad-43e2-bcf1-e7bca316d7e9",
      "name": "On form login",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        224,
        736
      ],
      "parameters": {
        "options": {
          "path": "login"
        },
        "formTitle": "Login",
        "formFields": {
          "values": [
            {
              "fieldLabel": "username",
              "requiredField": true
            },
            {
              "fieldType": "password",
              "fieldLabel": "password",
              "requiredField": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "caceffd3-862e-4ffc-817e-ed1300546caa",
      "name": "Check username/password",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        480,
        736
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.username }}",
              "lookupColumn": "username"
            },
            {
              "lookupValue": "={{ $json.password }}",
              "lookupColumn": "password"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "91c227fd-ecbb-4d9f-a608-4606453a0e93",
      "name": "If login success",
      "type": "n8n-nodes-base.if",
      "position": [
        720,
        736
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "455881ad-941f-4257-9427-1e48e7bc2974",
              "operator": {
                "type": "object",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "cedd4efe-c0e3-447b-a368-f4da51b0e004",
      "name": "Redirect to login form",
      "type": "n8n-nodes-base.form",
      "position": [
        992,
        816
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "redirectUrl": "=https://YOUR_DOMAIN/form/login",
        "respondWith": "redirect"
      },
      "typeVersion": 1
    },
    {
      "id": "6c2905c0-67d1-4c29-8ee5-3676d6ab1255",
      "name": "Redirect to auth url",
      "type": "n8n-nodes-base.form",
      "position": [
        1312,
        624
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "redirectUrl": "=https://YOUR_DOMAIN/webhook/auth?token={{ $json.token }}",
        "respondWith": "redirect"
      },
      "typeVersion": 1
    },
    {
      "id": "52426a88-2b8f-4839-b608-8c28e8cef5b1",
      "name": "Add token to DB",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1568,
        816
      ],
      "parameters": {
        "columns": {
          "value": {
            "token": "YOUR_CREDENTIAL_HERE",
            "username": "={{ $('Check username/password').item.json.username }}"
          },
          "schema": [
            {
              "id": "username",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "password",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "password",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "token",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "token",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "username"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "1a3c065f-b882-4088-9c34-5ebbb4fb787a",
      "name": "Redirect to auth with new token",
      "type": "n8n-nodes-base.form",
      "position": [
        1840,
        816
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "redirectUrl": "=https://YOUR_DOMAIN/webhook/auth?token={{ $('Generate token').item.json.token }}",
        "respondWith": "redirect"
      },
      "typeVersion": 1
    },
    {
      "id": "b4e2aebf-3f52-4234-ae86-f7c29f2a226d",
      "name": "Auth",
      "type": "n8n-nodes-base.webhook",
      "position": [
        208,
        1200
      ],
      "parameters": {
        "path": "auth",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "340dcc3d-797c-44ca-a5ab-830687fbfffa",
      "name": "Invalid token",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        736,
        1344
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "Invalid token"
      },
      "typeVersion": 1.4
    },
    {
      "id": "6119d68b-76d0-40ce-8ac1-df2253ca1a81",
      "name": "If token valid",
      "type": "n8n-nodes-base.if",
      "position": [
        976,
        1184
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a29fd91f-24e3-4302-9735-25419166511b",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.username }}",
              "rightValue": ""
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 2.2
    },
    {
      "id": "d3588bf0-be76-4e85-9ac4-ce11de129b25",
      "name": "Redirect to login form1",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1248,
        1280
      ],
      "parameters": {
        "options": {},
        "redirectURL": "={{ 'https://' + $('Auth').item.json.headers.host }}/form/login",
        "respondWith": "redirect"
      },
      "typeVersion": 1.4
    },
    {
      "id": "4d02d506-b001-45b8-afab-32ac76d29896",
      "name": "Update sid",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1600,
        1088
      ],
      "parameters": {
        "columns": {
          "value": {
            "sid": "={{ $json.sid }}",
            "token": "",
            "username": "={{ $('Search by token').item.json.username }}"
          },
          "schema": [
            {
              "id": "username",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "password",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "password",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "token",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "token",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sid",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "sid",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "username"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "0960c383-12b3-4176-8501-3b85d9453256",
      "name": "Redirect to profile and set cookies",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1600,
        1280
      ],
      "parameters": {
        "options": {
          "responseCode": 302,
          "responseHeaders": {
            "entries": [
              {
                "name": "Set-Cookie",
                "value": "=sid={{$json.sid}}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=604800"
              },
              {
                "name": "Location",
                "value": "/webhook/profile"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "={{ $json.sid }}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "ed4908f3-50f0-4150-adbc-ca5b706a94e3",
      "name": "Profile",
      "type": "n8n-nodes-base.webhook",
      "position": [
        224,
        1712
      ],
      "parameters": {
        "path": "profile",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "2d514f17-01f8-4410-a759-50289488502f",
      "name": "Check sid cookies header",
      "type": "n8n-nodes-base.code",
      "position": [
        448,
        1712
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function getCookie(headers, name) {\n  const raw = (headers.cookie || headers.Cookie || \"\");\n  if (!raw) return null;\n\n  const parts = raw.split(\";\").map(s => s.trim());\n  for (const p of parts) {\n    const eq = p.indexOf(\"=\");\n    if (eq === -1) continue;\n    const k = p.slice(0, eq).trim();\n    const v = p.slice(eq + 1).trim();\n    if (k === name) return decodeURIComponent(v);\n  }\n  return null;\n}\n\nconst headers = $json.headers || {};\nconst sid = getCookie(headers, \"sid\");\n\nreturn {\n  json: {\n    sid,\n    hasCookieHeader: !!(headers.cookie || headers.Cookie),\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9d062264-f2c0-4659-b68f-714b03389ac4",
      "name": "Search sid",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        672,
        1712
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.sid || '' }}",
              "lookupColumn": "sid"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "d7dd2d42-a6bf-4adb-9571-1736cdb11703",
      "name": "If has data with sid",
      "type": "n8n-nodes-base.if",
      "position": [
        880,
        1712
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a29fd91f-24e3-4302-9735-25419166511b",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.username }}",
              "rightValue": ""
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 2.2
    },
    {
      "id": "89d72c2d-0f9a-4e63-a1e0-7245cdafad39",
      "name": "Redirect to login",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1168,
        1808
      ],
      "parameters": {
        "options": {},
        "redirectURL": "={{ 'https://' + $('Profile').item.json.headers.host }}/form/login",
        "respondWith": "redirect"
      },
      "typeVersion": 1.4
    },
    {
      "id": "e13952a5-dc41-4429-a0ba-bb918e18e41f",
      "name": "Prepare html",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        1568
      ],
      "parameters": {
        "jsCode": "const html = `Hello ${$input.first().json.username}`;\n\nreturn { json: { html } };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "bbb5e14a-e5f1-4eff-acb0-e02203922a70",
      "name": "Show profile page",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1392,
        1568
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "={{ $json.html }}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "9496073d-aaa6-4d65-a2d5-a4ee7cd3c85c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -896,
        576
      ],
      "parameters": {
        "width": 880,
        "height": 720,
        "content": "## Overview\nThis workflow provides a lightweight authentication flow for n8n web portals using Google Sheets as a simple user/session store and email delivery for onboarding. It supports register, username/password login, one-click magic link auth, and a cookie-based profile page.\n\n### How it works\n1. User registers with an email address via a form\n2. n8n generates a username, password, and a one-time auth token\n3. User data is stored in Google Sheets\n4. n8n sends an email containing credentials + a one-click login link (`/auth?token=...`)\n5. User can log in with username/password or click the magic link\n6. `/auth` validates the token, creates a session id (sid), sets it as an HttpOnly cookie, and invalidates the token\n7. `/profile` reads the sid cookie, validates the session in Google Sheets, and displays user info to confirm login\n\n### Setup\n- Configure Gmail credentials in the Send email node\n- Create a Google Sheet with columns: username, password, role, token, sid\n- Add your Google Sheets URL (replace YOUR_SPREADSHEET_ID)\n- Replace YOUR_DOMAIN with your n8n instance domain in all nodes\n- Ensure your n8n instance is served over HTTPS so Secure cookies work\n\n### Customization\n- Adjust token expiry (e.g., 10-15 minutes) and one-time use behavior\n- Change session cookie settings (name, Max-Age, SameSite)\n- Customize the registration email content\n- Replace the Profile HTML with your own portal UI\n- Add a /logout endpoint to revoke sessions\n- Add rate-limiting at reverse proxy (Nginx/Traefik/Cloudflare)\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "b1b52d4f-9d40-40ce-b044-44d951a1bfb7",
  "connections": {
    "Auth": {
      "main": [
        [
          {
            "node": "If has token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Profile": {
      "main": [
        [
          {
            "node": "Check sid cookies header",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search sid": {
      "main": [
        [
          {
            "node": "If has data with sid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare sid": {
      "main": [
        [
          {
            "node": "Redirect to profile and set cookies",
            "type": "main",
            "index": 0
          },
          {
            "node": "Update sid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If has token": {
      "main": [
        [
          {
            "node": "Search by token",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Invalid token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare html": {
      "main": [
        [
          {
            "node": "Show profile page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form login": {
      "main": [
        [
          {
            "node": "Check username/password",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate token": {
      "main": [
        [
          {
            "node": "Add token to DB",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If token valid": {
      "main": [
        [
          {
            "node": "Prepare sid",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redirect to login form1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add token to DB": {
      "main": [
        [
          {
            "node": "Redirect to auth with new token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search by token": {
      "main": [
        [
          {
            "node": "If token valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If login success": {
      "main": [
        [
          {
            "node": "If has token in DB",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redirect to login form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form register": {
      "main": [
        [
          {
            "node": "Create password and token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If has token in DB": {
      "main": [
        [
          {
            "node": "Redirect to auth url",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generate token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If has data with sid": {
      "main": [
        [
          {
            "node": "Prepare html",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redirect to login",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send username/password": {
      "main": [
        [
          {
            "node": "Notice register success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check username/password": {
      "main": [
        [
          {
            "node": "If login success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check sid cookies header": {
      "main": [
        [
          {
            "node": "Search sid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create password and token": {
      "main": [
        [
          {
            "node": "Append or update row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append or update row in sheet": {
      "main": [
        [
          {
            "node": "Send username/password",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}