8
n8n 中文网amn8n.com

自托管JWT认证系统与数据表和令牌管理

高级

这是一个自动化工作流,包含 87 个节点。主要使用 If, Set, Code, Merge, Crypto 等节点。 自托管JWT认证系统与数据表和令牌管理

前置要求
  • HTTP Webhook 端点(n8n 会自动生成)

分类

-
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "nodes": [
    {
      "id": "d4d5e7a8-c447-4239-a570-2c705868974d",
      "name": "生成盐值",
      "type": "n8n-nodes-base.crypto",
      "position": [
        -848,
        -1248
      ],
      "parameters": {
        "action": "generate",
        "encodingType": "hex",
        "dataPropertyName": "salt"
      },
      "typeVersion": 1
    },
    {
      "id": "8c643b0d-574b-4c8a-a00a-f8587dfed883",
      "name": "哈希密码",
      "type": "n8n-nodes-base.crypto",
      "position": [
        -576,
        -1248
      ],
      "parameters": {
        "type": "SHA512",
        "value": "={{ $json.passwordWithSalt }}",
        "dataPropertyName": "password_hash"
      },
      "typeVersion": 1
    },
    {
      "id": "01a0c5fa-d902-4ee3-9116-240a1bc54d71",
      "name": "处理登录 Webhook",
      "type": "n8n-nodes-base.set",
      "position": [
        560,
        -1280
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0e50cdb0-f300-4d82-a81c-054dffbc9cfb",
              "name": "email",
              "type": "string",
              "value": "={{ $json.body.email }}"
            },
            {
              "id": "e5fffb05-c66c-4e35-811b-8718ea43645b",
              "name": "password",
              "type": "string",
              "value": "={{ $json.body.password}}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "cd32f96d-a552-4522-86c6-c35010193be0",
      "name": "获取用户",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        992,
        -1200
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "conditions": [
            {
              "keyName": "email",
              "keyValue": "={{ $json.email }}"
            }
          ]
        },
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "YbPI3l04vLMH8qga",
          "cachedResultUrl": "/projects/AT3bgmtmB45S5nQf/datatables/YbPI3l04vLMH8qga",
          "cachedResultName": "review_users"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c91e3b78-b71a-4fde-92c6-41cc99e75c0c",
      "name": "提取盐值和哈希",
      "type": "n8n-nodes-base.code",
      "position": [
        1360,
        -1200
      ],
      "parameters": {
        "jsCode": "const user = $input.item.json;\nconst inputPassword = $('Verify Input').item.json.password;\nconst [salt, storedHash] = user.password_hash.split(':');\n\nif (!salt || !storedHash) {\n  throw new Error('Invalid password format in database');\n}\n\nreturn {\n  json: {\n    userId: user.id,\n    email: user.email,\n    username: user.username,\n    salt: salt,\n    storedHash: storedHash,\n    passwordWithSalt: inputPassword + salt\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "03a90300-9b8c-43cd-b51d-fcdc4a2abc85",
      "name": "哈希输入密码",
      "type": "n8n-nodes-base.crypto",
      "position": [
        1536,
        -1200
      ],
      "parameters": {
        "type": "SHA512",
        "value": "={{ $json.passwordWithSalt }}",
        "dataPropertyName": "inputHash"
      },
      "typeVersion": 1
    },
    {
      "id": "b70a3266-04fc-4398-ac19-7fb2cbf005f7",
      "name": "签名访问令牌",
      "type": "n8n-nodes-base.crypto",
      "position": [
        2176,
        -1328
      ],
      "parameters": {
        "type": "SHA256",
        "value": "={{ $json.accessSignatureInput }}",
        "action": "hmac",
        "secret": "={{ $('SET ACCESS AND REFRESH SECRET').item.json.ACCESS_SECRET }}",
        "encoding": "base64",
        "dataPropertyName": "accessSignature"
      },
      "typeVersion": 1
    },
    {
      "id": "a17a5545-1961-48e5-8e5a-4cc51f16d32f",
      "name": "签名刷新令牌",
      "type": "n8n-nodes-base.crypto",
      "position": [
        2176,
        -1104
      ],
      "parameters": {
        "type": "SHA256",
        "value": "={{ $json.refreshSignatureInput }}",
        "action": "hmac",
        "secret": "={{ $('SET ACCESS AND REFRESH SECRET').item.json.REFRESH_SECRET }}",
        "encoding": "base64",
        "dataPropertyName": "refreshSignature"
      },
      "typeVersion": 1
    },
    {
      "id": "9c400b40-273f-43ac-b6db-81ef5dd410cb",
      "name": "格式化 JWT 令牌",
      "type": "n8n-nodes-base.code",
      "position": [
        2560,
        -1216
      ],
      "parameters": {
        "jsCode": "const header = $('Create JWT Payload').first().json.header;\nconst accessPayload = $input.item.json.accessPayload;\nconst refreshPayload = $input.item.json.refreshPayload;\nconst accessSignature = $input.item.json.accessSignature;\nconst refreshSignature = $input.item.json.refreshSignature;\n\nconst accessSigBase64url = Buffer.from(accessSignature, 'base64')\n  .toString('base64url');\nconst refreshSigBase64url = Buffer.from(refreshSignature, 'base64')\n  .toString('base64url');\n\nconst accessJWT = `${header}.${accessPayload}.${accessSigBase64url}`;\nconst refreshJWT = `${header}.${refreshPayload}.${refreshSigBase64url}`;\n\nreturn {\n  json: {\n    userId: $input.item.json.userId,\n    email: $input.item.json.email,\n    username: $input.item.json.username,\n    accessToken: accessJWT,\n    refreshToken: refreshJWT\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "638d49ae-5b15-49a8-b7f0-e5c32308e0e6",
      "name": "合并 JWT 令牌",
      "type": "n8n-nodes-base.merge",
      "position": [
        2400,
        -1216
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "joinMode": "keepEverything",
        "fieldsToMatchString": "userId"
      },
      "typeVersion": 3.2
    },
    {
      "id": "a1f01174-648e-45cc-9bd0-cb4294f8dccc",
      "name": "哈希刷新令牌以存储",
      "type": "n8n-nodes-base.crypto",
      "position": [
        2752,
        -1216
      ],
      "parameters": {
        "type": "SHA256",
        "value": "={{ $json.refreshToken }}",
        "dataPropertyName": "refreshTokenHash"
      },
      "typeVersion": 1
    },
    {
      "id": "ca71f487-e4c7-4f33-a6f8-b7a13a3f90b5",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -1360
      ],
      "parameters": {
        "color": 5,
        "width": 3344,
        "height": 608,
        "content": "## 登录流程"
      },
      "typeVersion": 1
    },
    {
      "id": "4f8558f6-0a62-49e4-a04d-85cba870e6ab",
      "name": "解析 JWT",
      "type": "n8n-nodes-base.code",
      "position": [
        832,
        160
      ],
      "parameters": {
        "jsCode": "const token = $input.first().json.access_token;\n\nif (!token) {\n  throw new Error('Token is required');\n}\n\nconst parts = token.split('.');\n\nif (parts.length !== 3) {\n  throw new Error('Invalid token format');\n}\n\nconst [header, payload, signature] = parts;\n\nconst decodedPayload = JSON.parse(\n  Buffer.from(payload, 'base64url').toString()\n);\n\nconst now = Math.floor(Date.now() / 1000);\nif (decodedPayload.exp < now) {\n  throw new Error('Token expired');\n}\n\nif (decodedPayload.type === 'refresh') {\n  throw new Error('Invalid token type');\n}\n\nreturn {\n  json: {\n    header,\n    payload,\n    access_token: token,\n    providedSignature: signature,\n    signatureInput: `${header}.${payload}`,\n    userId: decodedPayload.userId,\n    email: decodedPayload.email\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "3ed751a2-bf2d-4e65-a13e-487b117f8e01",
      "name": "验证 HMAC 签名",
      "type": "n8n-nodes-base.crypto",
      "position": [
        1280,
        160
      ],
      "parameters": {
        "type": "SHA256",
        "value": "={{ $('Parse JWT').item.json.access_token }}",
        "action": "hmac",
        "secret": "={{ $('SET ACCESS AND REFRESH SECRET2').item.json.ACCESS_SECRET }}",
        "encoding": "base64",
        "dataPropertyName": "expectedSignature"
      },
      "typeVersion": 1
    },
    {
      "id": "e503f741-5c7e-4bf2-bc0f-14a9484c6a80",
      "name": "比较签名",
      "type": "n8n-nodes-base.code",
      "onError": "continueErrorOutput",
      "position": [
        1472,
        160
      ],
      "parameters": {
        "jsCode": "const providedSignature = $('Parse JWT').first().json.providedSignature;\nconst expectedSignature = $input.item.json.expectedSignature;\n\nconst providedBase64 = Buffer.from(providedSignature, 'base64url')\n  .toString('base64');\n\nif (providedBase64 !== expectedSignature) {\n  throw new Error('Invalid token signature');\n}\n\nreturn {\n  json: {\n    valid: true,\n    userId: $input.item.json.userId,\n    email: $input.item.json.email\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "76d82b8d-a879-41cc-8358-af930333ad19",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -16
      ],
      "parameters": {
        "color": 6,
        "width": 1616,
        "height": 496,
        "content": "## 验证令牌流程"
      },
      "typeVersion": 1
    },
    {
      "id": "5761b1d1-22fd-43c8-85a7-ae967630c44a",
      "name": "创建用户",
      "type": "n8n-nodes-base.dataTable",
      "onError": "continueErrorOutput",
      "position": [
        -304,
        -1248
      ],
      "parameters": {
        "columns": {
          "value": {
            "email": "={{ $json.email }}",
            "username": "={{ $json.username }}",
            "password_hash": "={{ $json.password_hash }}"
          },
          "schema": [
            {
              "id": "email",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "email",
              "defaultMatch": false
            },
            {
              "id": "username",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "username",
              "defaultMatch": false
            },
            {
              "id": "password_hash",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "password_hash",
              "defaultMatch": false
            },
            {
              "id": "refresh_token",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "refresh_token",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "YbPI3l04vLMH8qga",
          "cachedResultUrl": "/projects/AT3bgmtmB45S5nQf/datatables/YbPI3l04vLMH8qga",
          "cachedResultName": "review_users"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "469e109c-a981-49be-bbe7-b24bd0467f04",
      "name": "注册 Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -2240,
        -1232
      ],
      "webhookId": "8e3d56c5-db22-44aa-8262-7a1f84fd0641",
      "parameters": {
        "path": "register-user",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "82ca8cb5-4eda-45ee-96bf-dfa520a44726",
      "name": "格式化密码和盐值",
      "type": "n8n-nodes-base.code",
      "position": [
        -848,
        -960
      ],
      "parameters": {
        "jsCode": "const password = $input.item.json.password;\n   const salt = $input.item.json.salt;\n   \n   return {\n     json: {\n       ...items[0].json,\n       passwordWithSalt: password + salt\n     }\n   };"
      },
      "typeVersion": 2
    },
    {
      "id": "07149331-060d-4d01-a418-4f1a2abe4813",
      "name": "格式化用户数据",
      "type": "n8n-nodes-base.code",
      "position": [
        -592,
        -944
      ],
      "parameters": {
        "jsCode": "return {\n     json: {\n       email: $input.item.json.email,\n       username: $input.item.json.username,\n       password_hash: `${$input.item.json.salt}:${$input.item.json.password_hash}`\n     }\n   };"
      },
      "typeVersion": 2
    },
    {
      "id": "b460d06f-759c-47ca-9cc4-b33e49489563",
      "name": "注册错误响应",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -64,
        -1024
      ],
      "parameters": {
        "options": {
          "responseCode": 500
        },
        "respondWith": "json",
        "responseBody": "{\n   \"success\": false,\n   \"message\": \"Internal Server Error\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "c0751a62-a1af-441a-a8db-583eddabd3f3",
      "name": "验证注册请求",
      "type": "n8n-nodes-base.code",
      "onError": "continueErrorOutput",
      "position": [
        -1968,
        -1232
      ],
      "parameters": {
        "jsCode": "const { email, username, password } = $input.item.json;\n   \n   if (!email || !username || !password) {\n     throw new Error('Email, username, and password are required');\n   }\n   \n   if (password.length < 8) {\n     throw new Error('Password must be at least 8 characters');\n   }\n   \n   return {\n     json: {\n       email: email.toLowerCase().trim(),\n       username: username.trim(),\n       password: password\n     }\n   };"
      },
      "typeVersion": 2
    },
    {
      "id": "1ecf5f63-5099-4aaa-ab89-de65c7e6dbcd",
      "name": "注册成功",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -64,
        -1264
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={\n    \"success\": true,\n    \"message\": \"User registered successfully\",\n    \"user\": {\n        \"id\": \"{{ $json.id }}\",\n        \"email\": \"{{ $json.email }}\",\n        \"username\": \"{{ $json.username }}\"\n    }\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "5439ebb6-4ec7-4fe5-be49-f44b0c45d889",
      "name": "如果用户存在",
      "type": "n8n-nodes-base.if",
      "position": [
        1152,
        -1216
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "36861ab1-365a-4a88-b50c-9949932f6d01",
              "operator": {
                "type": "object",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('Get User').item.json }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "083ce4f7-f48a-4d28-8265-4aa75b87d642",
      "name": "用户未找到",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1136,
        -960
      ],
      "parameters": {
        "options": {
          "responseCode": 401
        },
        "respondWith": "json",
        "responseBody": "{\n   \"success\": false,\n   \"message\": \"User Not Found\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "53f40d35-f65e-4a17-9041-910a31b3d652",
      "name": "JavaScript 代码",
      "type": "n8n-nodes-base.code",
      "position": [
        2928,
        -1216
      ],
      "parameters": {
        "jsCode": "return {\n     json: {\n       user_id: $input.item.json.userId,\n       token_hash: $input.item.json.refreshTokenHash,\n       expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),\n       created_at: new Date().toISOString(),\n       accessToken: $input.item.json.accessToken,\n       refreshToken: $input.item.json.refreshToken,\n       userId: $input.item.json.userId,\n       email: $input.item.json.email,\n       username: $input.item.json.username\n     }\n   };"
      },
      "typeVersion": 2
    },
    {
      "id": "04d49b68-8955-498e-a60f-4ed4e89e55ef",
      "name": "更新用户刷新令牌",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        3136,
        -1344
      ],
      "parameters": {
        "columns": {
          "value": {
            "refresh_token": "={{ $json.refreshToken }}"
          },
          "schema": [
            {
              "id": "email",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "email",
              "defaultMatch": false
            },
            {
              "id": "username",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "username",
              "defaultMatch": false
            },
            {
              "id": "password_hash",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "password_hash",
              "defaultMatch": false
            },
            {
              "id": "refresh_token",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "refresh_token",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyValue": "={{ $('Get User').item.json.id }}"
            }
          ]
        },
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "YbPI3l04vLMH8qga",
          "cachedResultUrl": "/projects/AT3bgmtmB45S5nQf/datatables/YbPI3l04vLMH8qga",
          "cachedResultName": "review_users"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c91111d8-9657-44c0-be37-07bce3b60fd6",
      "name": "存储刷新令牌",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        3136,
        -1104
      ],
      "parameters": {
        "columns": {
          "value": {
            "user_id": "={{ $('Get User').item.json.id }}",
            "expires_at": "={{ $json.expires_at }}",
            "token_hash": "={{ $json.token_hash }}"
          },
          "schema": [
            {
              "id": "user_id",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "user_id",
              "defaultMatch": false
            },
            {
              "id": "token_hash",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "token_hash",
              "defaultMatch": false
            },
            {
              "id": "expires_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "expires_at",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "user_id",
              "keyValue": "={{ $('Get User').item.json.id }}"
            }
          ]
        },
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "wACP3KxHplgwFrJ1",
          "cachedResultUrl": "/projects/AT3bgmtmB45S5nQf/datatables/wACP3KxHplgwFrJ1",
          "cachedResultName": "refresh_tokens"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "032cfc35-65e3-4b0c-a326-d4ea61228cc5",
      "name": "合并",
      "type": "n8n-nodes-base.merge",
      "position": [
        3344,
        -1216
      ],
      "parameters": {
        "mode": "chooseBranch"
      },
      "typeVersion": 3.2
    },
    {
      "id": "9112cefa-951d-4e30-9148-6f83625f78fd",
      "name": "验证访问令牌",
      "type": "n8n-nodes-base.webhook",
      "position": [
        416,
        32
      ],
      "webhookId": "0dd7bd7d-2ab4-4d14-83e2-165eb4bc870a",
      "parameters": {
        "path": "verify-token",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "38b357fd-91a3-4e3a-82de-62b71a556fbb",
      "name": "当由另一个工作流执行时",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        448,
        320
      ],
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "token"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "8cc94ddc-6bbd-44a7-b7f0-afbad69d7daf",
      "name": "刷新访问令牌",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -2336,
        -16
      ],
      "webhookId": "8e3d56c5-db22-44aa-8262-7a1f84fd0641",
      "parameters": {
        "path": "refresh",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "f9946939-4efe-43e4-8626-69ddb41b618a",
      "name": "解析刷新令牌",
      "type": "n8n-nodes-base.code",
      "position": [
        -1904,
        0
      ],
      "parameters": {
        "jsCode": "const refreshToken = $('Process Refresh Token').first().json.refresh_token;\n\nif (!refreshToken) {\n  throw new Error('Refresh token is required');\n}\n\nconst parts = refreshToken.split('.');\n\nif (parts.length !== 3) {\n  throw new Error('Invalid token format');\n}\n\nconst [header, payload, signature] = parts;\n\nconst decodedPayload = JSON.parse(\n  Buffer.from(payload, 'base64url').toString()\n);\n\nconst now = Math.floor(Date.now() / 1000);\nif (decodedPayload.exp < now) {\n  throw new Error('Refresh token expired');\n}\n\nreturn {\n  json: {\n    header,\n    payload,\n    providedSignature: signature,\n    signatureInput: `${header}.${payload}`,\n    userId: decodedPayload.userId,\n    refreshToken: refreshToken\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "b0c044ef-13cc-4711-b7dd-3db0d176f202",
      "name": "验证签名",
      "type": "n8n-nodes-base.crypto",
      "position": [
        -1904,
        272
      ],
      "parameters": {
        "type": "SHA256",
        "value": "={{ $json.signatureInput }}",
        "action": "hmac",
        "secret": "={{ $('SET ACCESS AND REFRESH SECRET1').item.json.REFRESH_SECRET }}",
        "encoding": "base64",
        "dataPropertyName": "expectedSignature"
      },
      "typeVersion": 1
    },
    {
      "id": "d426f5e3-dd5c-485e-bd8e-ef9c8b0a028d",
      "name": "比较刷新令牌签名",
      "type": "n8n-nodes-base.code",
      "position": [
        -1648,
        0
      ],
      "parameters": {
        "jsCode": "const providedSignature = $input.item.json.providedSignature;\nconst expectedSignature = $input.item.json.expectedSignature;\n\nconst providedBase64 = Buffer.from(providedSignature, 'base64url')\n  .toString('base64');\n\nif (providedBase64 !== expectedSignature) {\n  throw new Error('Invalid token signature');\n}\n\nreturn {\n  json: {\n    userId: $input.item.json.userId,\n    refreshToken: $input.item.json.refreshToken\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "597d6c4f-0b0e-4777-867c-6a883d128dcc",
      "name": "哈希刷新令牌以进行数据库查找",
      "type": "n8n-nodes-base.crypto",
      "position": [
        -1648,
        272
      ],
      "parameters": {
        "type": "SHA256",
        "value": "={{ $json.refreshToken }}",
        "dataPropertyName": "tokenHash"
      },
      "typeVersion": 1
    },
    {
      "id": "56f2e5a6-460d-4a6c-ade3-f7e99d9ae99e",
      "name": "如果刷新令牌存在",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -1392,
        16
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "conditions": [
            {
              "keyName": "token_hash",
              "keyValue": "={{ $json.tokenHash }}"
            },
            {
              "keyName": "user_id",
              "keyValue": "={{ $json.userId }}"
            }
          ]
        },
        "matchType": "allConditions",
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "wACP3KxHplgwFrJ1",
          "cachedResultUrl": "/projects/AT3bgmtmB45S5nQf/datatables/wACP3KxHplgwFrJ1",
          "cachedResultName": "refresh_tokens"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "98469d0f-cebc-4cf0-b775-1d72769d0560",
      "name": "如果刷新令牌有效",
      "type": "n8n-nodes-base.if",
      "position": [
        -1200,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c14943c0-fa2b-4681-b9f1-a9953da78aed",
              "operator": {
                "type": "object",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('If Refresh Token Exists').item.json }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8d1c5fee-ca4a-4e5d-8efd-b9196d1cf9d1",
      "name": "创建访问令牌载荷",
      "type": "n8n-nodes-base.code",
      "position": [
        -992,
        0
      ],
      "parameters": {
        "jsCode": "const userId = $('Parse Refresh Token').item.json.userId;\nconst now = Math.floor(Date.now() / 1000);\n\nconst accessPayload = {\n  userId: userId,\n  iat: now,\n  exp: now + (15 * 60)\n};\n\nconst header = Buffer.from(JSON.stringify({alg: 'HS256', typ: 'JWT'}))\n  .toString('base64url');\n\nconst accessPayloadEncoded = Buffer.from(JSON.stringify(accessPayload))\n  .toString('base64url');\n\nconst signatureInput = `${header}.${accessPayloadEncoded}`;\n\nreturn {\n  json: {\n    header,\n    payload: accessPayloadEncoded,\n    signatureInput\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "860e8b13-759e-4c81-861e-44c8c8e3bd51",
      "name": "格式化访问令牌 JWT",
      "type": "n8n-nodes-base.code",
      "position": [
        -624,
        0
      ],
      "parameters": {
        "jsCode": "const header = $input.item.json.header;\nconst payload = $input.item.json.payload;\nconst signature = $input.item.json.accessSignature;\n\nconst signatureBase64url = Buffer.from(signature, 'base64')\n  .toString('base64url');\n\nconst accessToken = `${header}.${payload}.${signatureBase64url}`;\n\nreturn {\n  json: {\n    accessToken\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "7ec78f50-e2bc-4f9e-a063-f5cee4e05562",
      "name": "返回新访问令牌",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -448,
        0
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={\n  \"success\": \"true\",\n  \"accessToken\": \"{{ $json.accessToken }}\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "fa47661a-acb6-42b2-8783-b6c21fedf42b",
      "name": "会话已过期",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -992,
        272
      ],
      "parameters": {
        "options": {
          "responseCode": 403
        },
        "respondWith": "json",
        "responseBody": "{\n  \"success\": false,\n  \"message\": \"Session Expired\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "ffea7a05-b838-44cc-b9b5-2177cf31c97f",
      "name": "解析注册请求",
      "type": "n8n-nodes-base.set",
      "position": [
        -2256,
        -976
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a3b6e7b7-b2e7-42fb-8832-b62f1e53dca7",
              "name": "email",
              "type": "string",
              "value": "={{ $json.body.email }}"
            },
            {
              "id": "c388e803-f70b-44e2-b7dc-c272669fa261",
              "name": "username",
              "type": "string",
              "value": "={{ $json.body.username }}"
            },
            {
              "id": "12504a6d-e779-4768-9572-d4a9d02a522e",
              "name": "password",
              "type": "string",
              "value": "={{ $json.body.password }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "9edc4ba9-11b5-4705-8846-0623eec15b35",
      "name": "登录成功",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3520,
        -1216
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={\n    \"accessToken\": \"{{ $('Hash Refresh Token for Storage').item.json.accessToken }}\",\n    \"refreshToken\": \"{{ $('Hash Refresh Token for Storage').item.json.refreshToken }}\",\n    \"user\": {\n        \"id\": \"{{ $json.id }}\",\n        \"email\": \"{{ $json.email }}\",\n        \"username\": \"{{ $json.username }}\"\n    }\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "74bb0bc8-75c7-444d-b396-a797598a95ef",
      "name": "登录凭据无效响应",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1696,
        -976
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "{\n  \"success\": false,\n  \"message\": \"Credentials Invalid\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "91534fa0-06f0-466b-a751-cd736e38e212",
      "name": "处理验证令牌 Webhook",
      "type": "n8n-nodes-base.set",
      "position": [
        592,
        32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0e50cdb0-f300-4d82-a81c-054dffbc9cfb",
              "name": "access_token",
              "type": "string",
              "value": "={{ $json.body.access_token }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ba27471d-e369-4ae3-af7d-11c6f6375dc6",
      "name": "验证输入",
      "type": "n8n-nodes-base.code",
      "onError": "continueErrorOutput",
      "position": [
        784,
        -1184
      ],
      "parameters": {
        "jsCode": "const email = $('Process login webhook').first().json.email;\nconst password = $('Process login webhook').first().json.password;\n\nif (!email || !password) {\n  throw new Error('Email and password are required');\n}\n\nreturn {\n  json: {\n    email: email.toLowerCase().trim(),\n    password: password\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "207cace5-a58b-4b4d-bf5b-5bc2516f8ae1",
      "name": "验证密码",
      "type": "n8n-nodes-base.code",
      "onError": "continueErrorOutput",
      "position": [
        1696,
        -1200
      ],
      "parameters": {
        "jsCode": "const inputHash = $input.item.json.inputHash;\nconst storedHash = $input.item.json.storedHash;\n\nif (inputHash !== storedHash) {\n  throw new Error('Invalid credentials');\n}\n\nreturn {\n  json: {\n    userId: $input.item.json.userId,\n    email: $input.item.json.email,\n    username: $input.item.json.username\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "8307d145-aaec-459c-9709-f60c41bb355e",
      "name": "创建 JWT 载荷",
      "type": "n8n-nodes-base.code",
      "position": [
        1904,
        -1216
      ],
      "parameters": {
        "jsCode": "const userId = $input.item.json.userId;\nconst email = $input.item.json.email;\nconst username = $input.item.json.username;\n\nconst now = Math.floor(Date.now() / 1000);\n\nconst accessPayload = {\n  userId: userId,\n  email: email,\n  iat: now,\n  exp: now + (15 * 60)\n};\n\nconst refreshPayload = {\n  userId: userId,\n  type: 'refresh',\n  iat: now,\n  exp: now + (7 * 24 * 60 * 60)\n};\n\nconst header = Buffer.from(JSON.stringify({alg: 'HS256', typ: 'JWT'}))\n  .toString('base64url');\n\nconst accessPayloadEncoded = Buffer.from(JSON.stringify(accessPayload))\n  .toString('base64url');\nconst refreshPayloadEncoded = Buffer.from(JSON.stringify(refreshPayload))\n  .toString('base64url');\n\nreturn {\n  json: {\n    userId,\n    email,\n    username,\n    header,\n    accessPayload: accessPayloadEncoded,\n    refreshPayload: refreshPayloadEncoded,\n    accessSignatureInput: `${header}.${accessPayloadEncoded}`,\n    refreshSignatureInput: `${header}.${refreshPayloadEncoded}`\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "d55ad76d-58a1-405b-a916-10896e6aacef",
      "name": "处理刷新令牌",
      "type": "n8n-nodes-base.set",
      "position": [
        -2160,
        -16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0e50cdb0-f300-4d82-a81c-054dffbc9cfb",
              "name": "refresh_token",
              "type": "string",
              "value": "={{ $json.body.refresh_token }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e1e60536-b002-44f6-af55-4382c343e4fd",
      "name": "签名新访问令牌",
      "type": "n8n-nodes-base.crypto",
      "position": [
        -816,
        0
      ],
      "parameters": {
        "type": "SHA256",
        "value": "={{ $json.signatureInput }}",
        "action": "hmac",
        "secret": "={{ $('SET ACCESS AND REFRESH SECRET1').item.json.ACCESS_SECRET }}",
        "encoding": "base64",
        "dataPropertyName": "accessSignature"
      },
      "typeVersion": 1
    },
    {
      "id": "1c20777d-fbd2-45ab-846a-11664df3a0a3",
      "name": "如果用户名可用",
      "type": "n8n-nodes-base.if",
      "position": [
        -1568,
        -1264
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "36861ab1-365a-4a88-b50c-9949932f6d01",
              "operator": {
                "type": "object",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $('Get User By Username').item.json }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2057c030-5dc4-4adb-9c35-18cf15f3d282",
      "name": "按用户名获取用户",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -1760,
        -1248
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "conditions": [
            {
              "keyName": "username",
              "keyValue": "={{ $json.username }}"
            }
          ]
        },
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "YbPI3l04vLMH8qga",
          "cachedResultUrl": "/projects/AT3bgmtmB45S5nQf/datatables/YbPI3l04vLMH8qga",
          "cachedResultName": "review_users"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "d2cb987b-183d-4ea7-be00-8b0181df6e89",
      "name": "用户名已被占用错误",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1568,
        -960
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={\n  \"success\": false,\n  \"message\": \"That username is taken. Try a different one!\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "2dc548d9-838f-4f7b-9076-768d1ae0bbbe",
      "name": "注册请求无效错误",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1984,
        -960
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={\n  \"success\": false,\n  \"message\": \"{{ $json.error }}\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "c5f15368-adbc-426a-afe7-c418ebdbc810",
      "name": "如果邮箱可用",
      "type": "n8n-nodes-base.if",
      "position": [
        -1120,
        -1264
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "36861ab1-365a-4a88-b50c-9949932f6d01",
              "operator": {
                "type": "object",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $('Get User By Email').item.json }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "da039f08-ad18-4783-9fae-ab35ddaeab93",
      "name": "按邮箱获取用户",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -1328,
        -1248
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "conditions": [
            {
              "keyName": "email",
              "keyValue": "={{ $json.email }}"
            }
          ]
        },
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "YbPI3l04vLMH8qga",
          "cachedResultUrl": "/projects/AT3bgmtmB45S5nQf/datatables/YbPI3l04vLMH8qga",
          "cachedResultName": "review_users"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "551765c6-9690-4044-9767-81d280101024",
      "name": "邮箱已被占用错误",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1120,
        -960
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={\n  \"success\": false,\n  \"message\": \"That email is already registered. Try a different one!\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "926df073-ee05-45a4-8221-1eeae39095ec",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2352,
        -1360
      ],
      "parameters": {
        "color": 5,
        "width": 2496,
        "height": 608,
        "content": "## 注册流程"
      },
      "typeVersion": 1
    },
    {
      "id": "beef3c9d-e920-4038-9003-6fa317457706",
      "name": "登录 Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        400,
        -1280
      ],
      "webhookId": "8e3d56c5-db22-44aa-8262-7a1f84fd0641",
      "parameters": {
        "path": "login",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "707614cc-c896-40ef-97c9-2a433182ad59",
      "name": "错误请求",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        784,
        -960
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "{\n  \"success\": false,\n  \"message\": \"{{ $json.error }}\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "c0d24010-60fa-445b-881e-1bce33fd3e66",
      "name": "访问令牌有效",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1728,
        16
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "{\n    \"success\": true,\n    \"message\": \"Token Is Valid\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "3078f280-cdf6-47c9-b7e2-b06801ac0245",
      "name": "访问令牌无效",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1744,
        272
      ],
      "parameters": {
        "options": {
          "responseCode": 403
        },
        "respondWith": "json",
        "responseBody": "{\n   \"success\": false,\n   \"message\": \"Access Denied\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "4607d4be-8a77-4b1b-9d74-1a435acbc98e",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2368,
        -80
      ],
      "parameters": {
        "color": 5,
        "width": 2176,
        "height": 560,
        "content": "## 刷新令牌流程"
      },
      "typeVersion": 1
    },
    {
      "id": "a8f6dfcf-9f47-4153-a472-da54b44d2a8b",
      "name": "便签4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2352,
        -1664
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 272,
        "content": "## 📝 注册流程"
      },
      "typeVersion": 1
    },
    {
      "id": "175cc098-cf21-427c-885b-aa8c01056d10",
      "name": "便签5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1888,
        -1664
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 272,
        "content": "## 🔐 密码安全"
      },
      "typeVersion": 1
    },
    {
      "id": "ab2930d7-7130-4876-acb6-79956f83ed0c",
      "name": "### 需要帮助?",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -1680
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 272,
        "content": "## 🔑 登录流程"
      },
      "typeVersion": 1
    },
    {
      "id": "cc909ef8-b87e-48d1-9f2e-d06b2704558f",
      "name": "## 试试看!",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2000,
        -1776
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 384,
        "content": "## 🎫 双令牌系统"
      },
      "typeVersion": 1
    },
    {
      "id": "719b318f-746a-40f5-b3ec-b7ef4ddaad5b",
      "name": "GET 模型",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 272,
        "content": "✅ 令牌验证"
      },
      "typeVersion": 1
    },
    {
      "id": "85218220-90b2-481b-aef8-7fa9a5d6db31",
      "name": "## 1. 创建新的自定义 OpenAI 凭据",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2368,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 272,
        "content": "## 🔄 刷新过程"
      },
      "typeVersion": 1
    },
    {
      "id": "4f2b4bff-9e34-4a92-8427-766ae54fa997",
      "name": "便签10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1904,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 272,
        "content": "## 🔒 深度防御"
      },
      "typeVersion": 1
    },
    {
      "id": "564f1dd3-11c1-4e00-9139-6d81404b821c",
      "name": "便签 11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4224,
        -688
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## 🔑 关键安全"
      },
      "typeVersion": 1
    },
    {
      "id": "e9cf5472-0061-48e2-95e4-b65ed512fd3c",
      "name": "便签 12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3744,
        -688
      ],
      "parameters": {
        "color": 3,
        "width": 416,
        "height": 336,
        "content": "## ⚠️ 故障排除"
      },
      "typeVersion": 1
    },
    {
      "id": "2382a6af-4e25-4495-be93-22f9cb945203",
      "name": "便利贴13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3152,
        -1520
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## 🧪 测试序列"
      },
      "typeVersion": 1
    },
    {
      "id": "0d27d432-abbe-42b1-9a9a-b70434e4aa38",
      "name": "便签14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3616,
        -1520
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 576,
        "content": "# 💾 TABLE SETUP\n\n## **Use n8n Data Tables Feature**\n\n### Table 'users':\n- **email** string (login identifier)\n- **username** string (login identifier)\n- **password_hash** string (as \"salt:hash\")\n- **refresh_token** string (latest token)\n\n### Table 'refresh_tokens':\n- **token_hash** - string (SHA-256 hash)\n- **user_id** number (which user owns it)\n- **expires_at** datetime (when it expires)\n\n**Access tokens**: NOT stored (stateless, fast)"
      },
      "typeVersion": 1
    },
    {
      "id": "4cc02f8c-a459-40a1-981d-247e9e311e14",
      "name": "便签15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2496,
        -1776
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 384,
        "content": "⚙️ CUSTOMIZATION POINTS\n\nChange token lifespan:\n- Find: exp: now + (15 * 60)\n- Adjust: (30 * 60) = 30 minutes\n\nChange hash algorithm:\n- Update Crypto nodes (SHA256 → SHA512)\n- Must update ALL instances!\n\nAdd fields to JWT:\n- Modify \"Create JWT Payload\" code\n- Add to payload object\n\nRevoke all user tokens:\n- Delete from refresh_tokens table\n- Or update token_hash to invalid value"
      },
      "typeVersion": 1
    },
    {
      "id": "71e19da5-0fdb-466e-bea1-42dead1f983e",
      "name": "SET ACCESS AND REFRESH SECRET",
      "type": "n8n-nodes-base.set",
      "position": [
        496,
        -976
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "4b5a0988-b8bb-46ff-b33b-ddabe2d006ba",
              "name": "ACCESS_SECRET",
              "type": "string",
              "value": ""
            },
            {
              "id": "5721f756-ce20-4bd6-a4db-b364bffb6cab",
              "name": "REFRESH_SECRET",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f5267c80-05c8-4378-aad1-c8ff115d1d82",
      "name": "Sticky Note32",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        432,
        -1024
      ],
      "parameters": {
        "color": 3,
        "height": 256,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "2d74c194-e5a8-489e-a90b-f21e2740e173",
      "name": "Sticky Note33",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -736
      ],
      "parameters": {
        "color": 3,
        "width": 416,
        "height": 192,
        "content": "## !! ATTENTION !!\nYou can use this node to set the **ACCESS_SECRET** and **REFRESH_SECRET**, but ideally you will use Variables to do this:\n\nYou will need to handle authenticating requests in different workflows, so having these variables available globally is **crucial**"
      },
      "typeVersion": 1
    },
    {
      "id": "682c87b6-9388-43bf-8981-c2d07fcb1996",
      "name": "Sticky Note34",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        816,
        -736
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 240,
        "content": "## Migrating to Variables\n### You'll need to change following nodes to use Variables instead of Set Node:\n- **'Sign Access Token'** and **'Sign Refresh Token'** nodes to use the Variables\n- **Verify Signature** and **Sign New Access Token** nodes to use the Variables\n- **Verify HMAC Signature** node to use the Variable"
      },
      "typeVersion": 1
    },
    {
      "id": "85544cc1-ee32-45e7-9257-17a7a1e3924b",
      "name": "Sticky Note35",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4224,
        -1520
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 640,
        "content": "# 🔐 COMPLETE AUTH WORKFLOW OVERVIEW\n\nImplement a complete auth process using n8n workflow. This template will enable you to easily add authentication to any application you need. Real production apps have a more extensive authentication system, but this is a nice overview of how that system looks like\n\n### 📝 REGISTRATION\n- User submits: email, username, password\n\n### 🔑 LOGIN\n- User submits: email, password\n\n### ✅ VERIFY TOKEN\n- Client submits: access_token\n\n### 🔄 REFRESH TOKEN\n- Client submits: refresh_token\n\n### 📊 CLIENT FLOW:\nRegister → Login (get tokens) → Use access token for requests\n→ When expired (401) → Use refresh token → Get new access token\n→ When refresh expires → Login again"
      },
      "typeVersion": 1
    },
    {
      "id": "0c46dac3-dad7-4aa9-ba02-ca1f3f59e4e2",
      "name": "Sticky Note37",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4240,
        -1664
      ],
      "parameters": {
        "color": 7,
        "width": 1616,
        "height": 80,
        "content": "# Workflow Overview And Setup"
      },
      "typeVersion": 1
    },
    {
      "id": "d5b86a70-6837-4664-acfd-62538f6c7066",
      "name": "Sticky Note38",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4256,
        -816
      ],
      "parameters": {
        "color": 7,
        "width": 1616,
        "height": 80,
        "content": "# Important Notes"
      },
      "typeVersion": 1
    },
    {
      "id": "9064bdba-5fcf-43d0-a3e9-3d7eab89b319",
      "name": "SET ACCESS AND REFRESH SECRET1",
      "type": "n8n-nodes-base.set",
      "position": [
        -2240,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "4b5a0988-b8bb-46ff-b33b-ddabe2d006ba",
              "name": "ACCESS_SECRET",
              "type": "string",
              "value": ""
            },
            {
              "id": "5721f756-ce20-4bd6-a4db-b364bffb6cab",
              "name": "REFRESH_SECRET",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "363e999b-554f-4396-b33a-846889a8cd4c",
      "name": "Sticky Note36",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2320,
        208
      ],
      "parameters": {
        "color": 3,
        "height": 256,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "4f228c6f-9a05-4085-9c17-d980b2b0f3e5",
      "name": "SET ACCESS AND REFRESH SECRET2",
      "type": "n8n-nodes-base.set",
      "position": [
        1072,
        160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "4b5a0988-b8bb-46ff-b33b-ddabe2d006ba",
              "name": "ACCESS_SECRET",
              "type": "string",
              "value": ""
            },
            {
              "id": "5721f756-ce20-4bd6-a4db-b364bffb6cab",
              "name": "REFRESH_SECRET",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5232ecb8-1844-4352-80cc-eff4d17856eb",
      "name": "Sticky Note39",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        112
      ],
      "parameters": {
        "color": 3,
        "height": 256,
        "content": ""
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Login Successful",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get User": {
      "main": [
        [
          {
            "node": "If User Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse JWT": {
      "main": [
        [
          {
            "node": "SET ACCESS AND REFRESH SECRET2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create User": {
      "main": [
        [
          {
            "node": "Registration Successful",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Registration Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Input": {
      "main": [
        [
          {
            "node": "Get User",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Bad Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Salt": {
      "main": [
        [
          {
            "node": "Format Password & Salt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hash Password": {
      "main": [
        [
          {
            "node": "Format User Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Login Webhook": {
      "main": [
        [
          {
            "node": "Process login webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If User Exists": {
      "main": [
        [
          {
            "node": "Extract Salt & Hash",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "User Not Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Password": {
      "main": [
        [
          {
            "node": "Create JWT Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Login Credentials Invalid Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format User Data": {
      "main": [
        [
          {
            "node": "Create User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge JWT Tokens": {
      "main": [
        [
          {
            "node": "Format JWT Tokens",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Signature": {
      "main": [
        [
          {
            "node": "Compare Refresh Token Signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format JWT Tokens": {
      "main": [
        [
          {
            "node": "Hash Refresh Token for Storage",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get User By Email": {
      "main": [
        [
          {
            "node": "If Email Is Available",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sign Access Token": {
      "main": [
        [
          {
            "node": "Merge JWT Tokens",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Update User Refresh Token",
            "type": "main",
            "index": 0
          },
          {
            "node": "Store Refresh Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare Signatures": {
      "main": [
        [
          {
            "node": "Access Token Valid",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Access Token Invalid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create JWT Payload": {
      "main": [
        [
          {
            "node": "Sign Access Token",
            "type": "main",
            "index": 0
          },
          {
            "node": "Sign Refresh Token",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Sign Refresh Token": {
      "main": [
        [
          {
            "node": "Merge JWT Tokens",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Extract Salt & Hash": {
      "main": [
        [
          {
            "node": "Hash Input Password",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hash Input Password": {
      "main": [
        [
          {
            "node": "Verify Password",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Refresh Token": {
      "main": [
        [
          {
            "node": "Verify Signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Refresh Token": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Verify Access Token": {
      "main": [
        [
          {
            "node": "Process Verify Token Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get User By Username": {
      "main": [
        [
          {
            "node": "If Username Is Available",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Refresh Access Token": {
      "main": [
        [
          {
            "node": "Process Refresh Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Registration Webhook": {
      "main": [
        [
          {
            "node": "Parse Register Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Email Is Available": {
      "main": [
        [
          {
            "node": "Generate Salt",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email Taken Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Refresh Token": {
      "main": [
        [
          {
            "node": "SET ACCESS AND REFRESH SECRET1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process login webhook": {
      "main": [
        [
          {
            "node": "SET ACCESS AND REFRESH SECRET",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sign New Access Token": {
      "main": [
        [
          {
            "node": "Format Access Token JWT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify HMAC Signature": {
      "main": [
        [
          {
            "node": "Compare Signatures",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Password & Salt": {
      "main": [
        [
          {
            "node": "Hash Password",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Register Request": {
      "main": [
        [
          {
            "node": "Validate Registration Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Access Token JWT": {
      "main": [
        [
          {
            "node": "Return New Access Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Refresh Token Exists": {
      "main": [
        [
          {
            "node": "If Refresh Token Is Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Username Is Available": {
      "main": [
        [
          {
            "node": "Get User By Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Username Taken Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Refresh Token Is Valid": {
      "main": [
        [
          {
            "node": "Create Access Token Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Session Expired",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update User Refresh Token": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Access Token Payload": {
      "main": [
        [
          {
            "node": "Sign New Access Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Verify Token Webhook": {
      "main": [
        [
          {
            "node": "Parse JWT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SET ACCESS AND REFRESH SECRET": {
      "main": [
        [
          {
            "node": "Verify Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Registration Request": {
      "main": [
        [
          {
            "node": "Get User By Username",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Registration Request Invalid Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hash Refresh Token for Storage": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SET ACCESS AND REFRESH SECRET1": {
      "main": [
        [
          {
            "node": "Parse Refresh Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SET ACCESS AND REFRESH SECRET2": {
      "main": [
        [
          {
            "node": "Verify HMAC Signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare Refresh Token Signature": {
      "main": [
        [
          {
            "node": "Hash Refresh Token For DB Lookup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hash Refresh Token For DB Lookup": {
      "main": [
        [
          {
            "node": "If Refresh Token Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Executed by Another Workflow": {
      "main": [
        [
          {
            "node": "Parse JWT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量87
分类-
节点类型10
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

外部链接
在 n8n.io 查看

分享此工作流