自托管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)可能需要您自行付费。
相关工作流推荐
实时Notion Todoist双向同步模板
使用Redis的Notion Todoist实时双向同步
If
Set
Code
+26
246 节点Mario
销售
在可视化参考库中探索n8n节点
在可视化参考库中探索n8n节点
If
Ftp
Set
+93
113 节点I versus AI
其他
GitHub 同步仪表板 - V2
具有提交历史和回滚功能的 GitHub 工作流版本控制仪表板
If
N8n
Set
+20
94 节点Eduard
开发运维
工作日日志记录
AI工时表生成器 - 集成Gmail、日历和GitHub到Google表格
If
Set
Code
+11
31 节点Luka Zivkovic
个人效率
[模板] - 仪表板聊天
AI模型使用仪表板:追踪LLM工作流的令牌指标和成本
N8n
Set
Code
+12
30 节点Hugo
基于Bright Data、OpenAI和Redis的高级多源AI研究
使用Bright Data、OpenAI和Redis进行高级多源AI研究
If
Set
Code
+15
43 节点Daniel Shashko
市场调研