{"id":323801,"date":"2021-05-26T15:01:01","date_gmt":"2021-05-26T15:01:01","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=323801"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=323801","title":{"rendered":"RESTful backend \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d"},"content":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<h3>\u041f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0438<\/h3>\n<p>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d RESTful backend \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430 NodeJS + Express, \u043a\u043e\u0442\u043e\u0440\u044b\u0439:<\/p>\n<ul>\n<li>\n<p>\u043b\u0435\u0433\u043a\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u043e\u043c<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0443 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u0438\u043c\u0435\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0443\u044e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e<\/p>\n<\/li>\n<\/ul>\n<p>\u0413\u0430\u0439\u0434 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043e\u0431\u0448\u0438\u0440\u043d\u044b\u0439, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0447\u0430\u0441\u0442\u0438, \u0430 \u0437\u0430\u0442\u0435\u043c \u0441\u043e\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u043e\u0435\u0434\u0438\u043d\u043e. \u0413\u043e\u0442\u043e\u0432\u044b\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 <a href=\"https:\/\/github.com\/DzenDyn\/baseBackend\" rel=\"noopener noreferrer nofollow\">Github<\/a>. <\/p>\n<h3>\u041d\u0430\u0431\u043e\u0440 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432<\/h3>\n<p>\u0421\u0435\u0440\u0434\u0446\u0435 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2013 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f OpenApi 3.0. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 API \u043d\u0430 \u044f\u0437\u044b\u043a\u0435 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 YAML, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0437\u0430\u0449\u0438\u0449\u0430\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c API. <\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b \u0432\u043e\u0437\u044c\u043c\u0435\u043c MongoDB \u0438 mongoose, \u0432 \u0446\u0435\u043b\u043e\u043c \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u043e\u043c\u0435\u0448\u0430\u0435\u0442 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u044d\u0442\u0443 \u0441\u0432\u044f\u0437\u043a\u0443 \u043d\u0430 \u043b\u044e\u0431\u0443\u044e \u0434\u0440\u0443\u0433\u0443\u044e \u0432 \u0441\u0432\u043e\u0451\u043c \u0448\u0430\u0431\u043b\u043e\u043d\u0435.<\/p>\n<p>Passport.js \u2013 \u0437\u0430\u0449\u0438\u0442\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432, \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f. \u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f passport-jwt. \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c jwt-access \u0438 refresh \u0442\u043e\u043a\u0435\u043d\u044b.<\/p>\n<h3>\u041f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430<\/h3>\n<p>\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442, \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0432 npm init \u0438\u043b\u0438 yarn init, \u044f \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u044e yarn.<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0442\u043e\u0438\u0442 \u043f\u043e\u0437\u0430\u0431\u043e\u0442\u0438\u0442\u044c\u0441\u044f \u043e\u0431 \u0443\u0434\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0441\u0442\u0438\u043b\u0435 \u043a\u043e\u0434\u0430 \u0438 \u0434\u043e\u043f\u0443\u0449\u0435\u043d\u0438\u044f\u0445.<br \/>\u0417\u0430 \u0441\u0442\u0438\u043b\u044c \u043a\u043e\u0434\u0430 \u0443 \u043c\u0435\u043d\u044f \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0442 eslint \u0438 prettier. <\/p>\n<p>\u0412 \u043a\u043e\u0440\u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0438 \u0434\u043b\u044f eslint \u0438 prettier. \u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 \u0441\u0431\u043e\u0440\u043a\u0438 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e nodemon, npm-run-all, rimraf, babel. \u041d\u0438\u0436\u0435 \u043c\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>.eslintrc.json<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"json\">{     \"env\": {         \"node\": true,         \"es2021\": true     },     \"extends\": [         \"eslint:recommended\",         \"airbnb-base\",         \"prettier\"     ],     \"plugins\": [         \"prettier\"     ],     \"rules\": {         \"no-console\": 0,         \"prettier\/prettier\": [\"error\"],         \"import\/extensions\": 0,         \"import\/prefer-default-export\": \"off\",         \"import\/no-unresolved\": 0,         \"no-duplicate-imports\": [\"error\", { \"includeExports\": true }],         \"react\/prop-types\": 0,         \"no-underscore-dangle\": 0,         \"no-param-reassign\": [\"error\", { \"props\": false }],         \"no-case-declarations\": 0,         \"no-plusplus\": [\"error\", { \"allowForLoopAfterthoughts\": true }],         \"space-infix-ops\": [\"error\", { \"int32Hint\": false }],         \"no-unused-vars\": [\"error\", { \"argsIgnorePattern\": \"next\" }]     } }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>.prettierrc<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"json\">{     \"printWidth\": 100,     \"singleQuote\": true,     \"tabWidth\": 4,     \"bracketSpacing\": true,     \"endOfLine\": \"lf\",     \"semi\": true,     \"trailingComma\": \"none\" }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u0441\u0432\u043e\u0439 package.json<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"json\">\"dependencies\": {     \"@babel\/node\": \"^7.13.13\",     \"body-parser\": \"^1.19.0\",     \"connect\": \"^3.7.0\",     \"cookie-parser\": \"^1.4.5\",     \"cors\": \"^2.8.5\",     \"dotenv\": \"^8.2.0\",     \"express\": \"^4.17.1\",     \"express-openapi-validator\": \"^4.12.6\",     \"jsonwebtoken\": \"^8.5.1\",     \"mongoose\": \"^5.12.2\",     \"morgan\": \"^1.10.0\",     \"passport\": \"^0.4.1\",     \"passport-jwt\": \"^4.0.0\",     \"swagger-routes-express\": \"^3.3.0\",     \"swagger-ui-express\": \"^4.1.6\",     \"uuid\": \"^8.3.2\",     \"validator\": \"^13.5.2\",     \"yamljs\": \"^0.3.0\"   },   \"devDependencies\": {     \"@babel\/cli\": \"^7.13.14\",     \"@babel\/core\": \"^7.13.14\",     \"@babel\/preset-env\": \"^7.13.12\",     \"eslint\": \"^7.23.0\",     \"eslint-config-airbnb-base\": \"^14.2.1\",     \"eslint-config-prettier\": \"^8.1.0\",     \"eslint-plugin-import\": \"^2.22.1\",     \"eslint-plugin-prettier\": \"^3.3.1\",     \"nodemon\": \"^2.0.7\",     \"npm-run-all\": \"^4.1.5\",     \"prettier\": \"^2.2.1\",     \"rimraf\": \"^3.0.2\"   },   \"babel\": {     \"presets\": [       \"@babel\/preset-env\"     ]   },   \"scripts\": {     \"transpile\": \"babel .\/src --out-dir bin --copy-files\",     \"clean\": \"rimraf bin\",     \"build\": \"npm-run-all clean transpile\",     \"server\": \"node .\/bin\/app.js\",     \"dev\": \"npm-run-all build server\",     \"start\": \"yarn dev\",     \"watch\": \"nodemon\"   }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 nodemon.json \u0432 \u043a\u043e\u0440\u043d\u0435<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"json\">{     \"watch\": [\"src\/*\"],     \"ext\": \"js, json, yaml\",     \"exec\": \"yarn run dev\" }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0432 npm \u0438\u043b\u0438 yarn.<\/p>\n<h2>\u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c<\/h2>\n<p>\u042f \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0448\u0430\u0433\u043e\u0432\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c. \u041d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0441\u043e\u0431\u0440\u0430\u043b \u0438\u0445 \u0432 <a href=\"https:\/\/disk.yandex.ru\/d\/vk9sCckbPYvnHA\" rel=\"noopener noreferrer nofollow\">PDF<\/a>.<\/p>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0442\u0430\u043a\u0430\u044f:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 JWT access \u0442\u043e\u043a\u0435\u043d \u0438 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u044b\u0439 http-only \u043a\u0443\u043a\u0438 \u0441 refresh \u0442\u043e\u043a\u0435\u043d\u043e\u043c.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u043d\u0435 \u0438\u0441\u0442\u0435\u043a \u043b\u0438 \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 (JWT access token), \u0435\u0441\u043b\u0438 \u0432\u0441\u0435 \u043e\u043a, \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u00abAuthorization\u00bb, \u0435\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u0438\u0441\u0442\u0435\u043a, \u0442\u043e \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u041d\u0438\u0436\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043f\u0440\u043e \u043d\u0430\u0448 \u0431\u044d\u043a\u0435\u043d\u0434.<\/p>\n<\/li>\n<\/ol>\n<h4>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f<\/h4>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/22f\/98c\/450\/22f98c450f437aa5aebe42605a345035.png\" width=\"866\" height=\"735\"><figcaption><\/figcaption><\/figure>\n<ol>\n<li>\n<p>\u041d\u0430 Backend \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u0438\u0434\u0435(<strong>\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e HTTPS<\/strong>) e-mail, \u043f\u0430\u0440\u043e\u043b\u044c, \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u044b (nickname \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435)<\/p>\n<\/li>\n<li>\n<p>\u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u0441\u043e\u043b\u044c \u0438 \u0445\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u043f\u0430\u0440\u043e\u043b\u044c \u0441 \u044d\u0442\u043e\u0439 \u0441\u043e\u043b\u044c\u044e, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0432 \u0431\u0430\u0437\u0443<\/p>\n<\/li>\n<\/ol>\n<h4>\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f<\/h4>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/10b\/b33\/e40\/10bb33e404d4a1caffb5d9c0b9fcb891.png\" alt=\"\" title=\"\" width=\"1156\" height=\"706\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/habrastorage.org\/webt\/_h\/ap\/nn\/_hapnnjoll_whtbp-j5seqzzt28.png\" rel=\"noopener noreferrer nofollow\">\u041f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f<\/a><\/p>\n<ol>\n<li>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u043f\u043e HTTPS email \u0438 \u043f\u0430\u0440\u043e\u043b\u044c<\/p>\n<\/li>\n<li>\n<p>\u041f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0431\u0430\u0437\u044b<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043b\u0438\u0431\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043b\u0438\u0431\u043e undefined<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 undefined \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435, \u0438 \u043d\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043d\u0435\u0432\u0435\u0440\u043d\u0430 \u043f\u043e\u0447\u0442\u0430 \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c<\/p>\n<\/li>\n<li>\n<p>\u0411\u0435\u0440\u0435\u043c \u0438\u0437 \u0431\u0430\u0437\u044b \u0441\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0445\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u0441 \u044d\u0442\u043e\u0439 \u0441\u043e\u043b\u044c\u044e \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u0441\u0432\u0435\u0440\u044f\u0435\u043c \u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u043c \u0432 \u0431\u0430\u0437\u0435 \u0445\u044d\u0448\u0435\u043c. \u0415\u0441\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0432\u0432\u0435\u0434\u0435\u043d \u043d\u0435\u0432\u0435\u0440\u043d\u043e, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435, \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u043f\u0443\u043d\u043a\u0442\u0443 4<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0432\u0432\u0435\u0434\u0435\u043d \u0432\u0435\u0440\u043d\u043e, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c JWT \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0441 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u043c \u0441\u0440\u043e\u043a\u043e\u043c \u0436\u0438\u0437\u043d\u0438 \u0438 Refresh \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0443\u0436\u0435\u043d \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0441 \u0431\u043e\u043b\u0435\u0435 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0441\u0440\u043e\u043a\u043e\u043c \u0436\u0438\u0437\u043d\u0438. <strong>\u042d\u0442\u043e \u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435, \u043d\u043e refresh \u0442\u043e\u043a\u0435\u043d \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0443 \u043a\u0430\u043a \u043e\u0434\u043d\u043e \u0438\u0437 \u043f\u043e\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/strong><\/p>\n<\/li>\n<li>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e JWT \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c HTTP-only cookie (secure, \u0442.\u043a. \u0443 \u043d\u0430\u0441 HTTPS). <\/p>\n<\/li>\n<\/ol>\n<h4>\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 JWT \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/h4>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/442\/109\/733\/4421097338c96652c6e57c3d941df2a5.png\" width=\"1435\" height=\"699\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/habrastorage.org\/webt\/lz\/m3\/-x\/lzm3-xbws1lin0ze6uki2y2xvoc.png\" rel=\"noopener noreferrer nofollow\">\u041f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f<\/a><\/p>\n<ol>\n<li>\n<p>\u041e\u0431\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u0437 HTTP-only cookie refresh \u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 refresh \u0442\u043e\u043a\u0435\u043d\u0430 \u043d\u0435\u0442 \u2013 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u0435\u0441\u0442\u044c, \u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0441\u0442\u044c. \u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0418\u0449\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e refresh \u0442\u043e\u043a\u0435\u043d\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d, \u0442\u043e \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0431\u043e\u043b\u0435\u0435 \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0439\u0434\u0435\u043d, \u0442\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u043f\u0430\u0440\u0443 JWT \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 refresh \u0442\u043e\u043a\u0435\u043d\u0430. <strong>\u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c refresh \u0432 \u0431\u0430\u0437\u0443<\/strong><\/p>\n<\/li>\n<li>\n<p>\u0418 \u043a\u0430\u043a \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c refresh \u0442\u043e\u043a\u0435\u043d \u0432 cookie<\/p>\n<\/li>\n<\/ol>\n<h4>\u0412\u044b\u0445\u043e\u0434 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/h4>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/773\/d53\/b9a\/773d53b9a64e87919d66401d59120aa4.png\" width=\"1424\" height=\"706\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/habrastorage.org\/webt\/m6\/ug\/_9\/m6ug_9kzgg25bqvy2zvryop9p3g.png\" rel=\"noopener noreferrer nofollow\">\u041f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f<\/a><\/p>\n<ol>\n<li>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043d\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435 \u043e\u0448\u0438\u0431\u043a\u0430, \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0447\u0442\u043e-\u0442\u043e \u0442\u0438\u043f\u0430 \/user\/logout<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u0437 HTTP-only cookie refresh \u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 refresh \u0442\u043e\u043a\u0435\u043d\u0430 \u043d\u0435\u0442 \u2013 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u0435\u0441\u0442\u044c, \u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0441\u0442\u044c. \u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0418\u0449\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e refresh \u0442\u043e\u043a\u0435\u043d\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d, \u0442\u043e \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0431\u043e\u043b\u0435\u0435 \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0431\u0430\u0437\u044b refresh \u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<li>\n<p>\u0421\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c cookie \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430<\/p>\n<\/li>\n<\/ol>\n<h4>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<figure class=\"bordered\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ce9\/6d6\/b57\/ce96d6b573f99043a8466c3b412b00ba.png\" width=\"208\" height=\"189\"><figcaption><\/figcaption><\/figure>\n<p>\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0439 \u043a\u043b\u044e\u0447 \u043a \u043d\u0435\u043c\u0443<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043a\u0440\u044b\u0442\u044b\u0439 \u0438 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JWT \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043a\u0440\u044b\u0442\u044b\u0439 \u0438 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JWT refresh \u0442\u043e\u043a\u0435\u043d\u0430. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 refresh \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c uuid, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u043e \u044f \u043d\u0435 \u0438\u0449\u0443 \u043b\u0435\u0433\u043a\u0438\u0445 \u043f\u0443\u0442\u0435\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u043d\u0435\u0442 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430, \u043c\u043e\u0436\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0439, \u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0432 \u0431\u043e\u0435\u0432\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043d\u0435 \u0441\u0442\u043e\u0438\u0442, \u0442\u0430\u043a \u043a\u0430\u043a \u043a self-signed \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430\u043c \u043d\u0435\u0442 \u0434\u043e\u0432\u0435\u0440\u0438\u044f.<\/p>\n<p>\u0418\u0442\u0430\u043a \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430 \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f openssl:<\/p>\n<pre><code>openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout ssl.key -out ssl.crt<\/code><\/pre>\n<p>\u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f JWT:<\/p>\n<pre><code>ssh-keygen -t rsa -b 4096 -m PEM -f jwtPrivate.key openssl rsa -in jwtPrivate.key -pubout -outform PEM -out jwtPublic.pem  ssh-keygen -t rsa -b 4096 -m PEM -f refreshPrivate.key openssl rsa -in refreshPrivate.key -pubout -outform PEM -out refreshPublic.pem<\/code><\/pre>\n<p>\u0412\u0441\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u044b \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c \u0432 .\/src\/crypto\/<\/p>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439:<\/p>\n<details class=\"spoiler\">\n<summary>.\/src\/utils\/cryptoHelper.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import crypto from 'crypto';  \/**  * \u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f  *\/ export function validatePassword(password, hash, salt) {     const hashCandidate = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');     return hash === hashCandidate; }  \/**  * \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u043e\u043b\u0438 \u0438 \u0445\u044d\u0448\u0430 \u043f\u0430\u0440\u043e\u043b\u044f  *\/ export function genHashWithSalt(password) {     const salt = crypto.randomBytes(32).toString('hex');     const hash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');      return {         salt,         hash     }; }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>.\/src\/utils\/jwtHelper.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import fs from 'fs'; import path from 'path'; import jsonwebtoken from 'jsonwebtoken';  \/\/ \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043f\u0443\u0442\u0438 \u0438 \u0447\u0438\u0442\u0430\u0435\u043c \u043a\u043b\u044e\u0447\u0438 const jwtPrivate = path.join(__dirname, '..\/crypto\/', 'jwtPrivate.pem'); const refreshPrivate = path.join(__dirname, '..\/crypto\/', 'refreshPrivate.pem'); const refreshPublic = path.join(__dirname, '..\/crypto\/', 'refreshPublic.pem'); const JWT_PRIV_KEY = fs.readFileSync(jwtPrivate, 'utf8'); const REFRESH_PRIV_KEY = fs.readFileSync(refreshPrivate, 'utf8'); const REFRESH_PUBLIC_KEY = fs.readFileSync(refreshPublic, 'utf8');  \/\/ \u0432\u044b\u043f\u0443\u0441\u043a JWT \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 export function issueJWT(user) {     const { _id } = user;     const expiresIn = '10m';      const payload = {         uid: _id,         iat: Math.floor(Date.now() \/ 1000)     };      const signedToken = jsonwebtoken.sign(payload, JWT_PRIV_KEY, { expiresIn, algorithm: 'RS256' });      return {         token: `Bearer ${signedToken}`,         expires: expiresIn     }; }  \/\/\u0432\u044b\u043f\u0443\u0441\u043a JWT refresh \u0442\u043e\u043a\u0435\u043d\u0430 export function issueRefresh(user) {     const { _id } = user;     const expiresIn = '7d';      const payload = {         uid: _id,         iat: Math.floor(Date.now() \/ 1000)     };      const signedToken = jsonwebtoken.sign(payload, REFRESH_PRIV_KEY, {         expiresIn,         algorithm: 'RS256'     });      return {         token: signedToken,         expires: expiresIn     }; }  \/\/\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f refresh \u0442\u043e\u043a\u0435\u043d\u0430  export function isValidRefresh(token) {     try {         jsonwebtoken.verify(token, REFRESH_PUBLIC_KEY, { algorithm: 'RS256' });     } catch (error) {         return false;     }     return true; } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>.\/src\/utils\/passport.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import { Strategy, ExtractJwt } from 'passport-jwt'; import fs from 'fs'; import path from 'path'; import mongoose from 'mongoose'; import { userSchema } from '..\/db\/models\/User';  const User = mongoose.model('User', userSchema);  const pathToKey = path.join(__dirname, '..\/crypto\/', 'jwtPublic.pem'); const PUB_KEY = fs.readFileSync(pathToKey, 'utf8');  const options = {     jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),     secretOrKey: PUB_KEY,     algorithms: ['RS256'] };  export const strategy = (pass) =&gt; {     pass.use(         new Strategy(options, (jwtPayload, done) =&gt; {             User.findOne({ _id: jwtPayload.uid }, (err, user) =&gt; {                 if (err) {                     return done(err, false);                 }                 if (user) {                     return done(null, user);                 }                 return done(null, null);             });         })     ); };<\/code><\/pre>\n<p>\u042d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 jwt \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438, \u0441\u043b\u0438\u0437\u0430\u043d\u043e \u0438\u0437 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438, \u0441 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c\u0438. \u041e\u0434\u043d\u043e \u0438\u0437 \u0433\u043b\u0430\u0432\u043d\u044b\u0445 &#8212; \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430, \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0438\u0437 \u0442\u043e\u043a\u0435\u043d\u0430.<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>.\/src\/utils\/securityMiddleware.js<\/summary>\n<div class=\"spoiler__content\">\n<p>\u042d\u0442\u043e \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0437\u0430\u0449\u0438\u0442\u044b \u043d\u0430\u0448\u0438\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432<\/p>\n<pre><code class=\"javascript\">export const securityMiddleware = (req, res, next, passport, groups) =&gt; {     passport.authenticate('jwt', { session: false }, (err, user) =&gt; {         if (err) {             return next(err);         }         if (!user) {             return res.status(401).send('Unauthorized');         }         \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 req \u043f\u043e\u043b\u0435 user \u0441 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043d\u0430\u0431\u043e\u0440\u043e\u043c \u043f\u043e\u043b\u0435\u0439, \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c \u0445\u044d\u0448, \u0441\u043e\u043b\u044c \u043d\u0435 \u043d\u0430\u0434\u043e.         const { _id, email, nickname, group } = user;         req.user = {             _id,             email,             nickname,             group         };         if (groups.includes(user.group)) {             return next();         }         return res.status(403).send('Insufficient access rights');     })(req, res, next); };<\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 API<\/h3>\n<p>\u041d\u0430\u0448 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 API \u043e\u043f\u0438\u0448\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438, \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0440\u0430\u0437\u043d\u044b\u0445 \u0433\u0440\u0443\u043f\u043f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u0440\u0430\u0437\u0434\u0435\u043b\u043e\u0432.<\/p>\n<p>\u042d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u043c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432.<\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u043b\u044f operationId \u2013 \u044d\u0442\u043e \u0438\u043c\u0435\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0439-\u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0438 \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b.<\/p>\n<details class=\"spoiler\">\n<summary>.\/src\/api\/apiV1.yaml<\/summary>\n<div class=\"spoiler__content\">\n<pre><code>openapi: 3.0.3 info:     title: Passport test     description: Test of passport.js     version: 1.0.0     license:         name: MIT License         url: https:\/\/opensource.org\/licenses\/MIT paths: # \u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442     \/test\/ping:         get:             description: 'Returns pong'             tags:                 - Test             operationId: ping             responses:                 '200':                     description: OK                     $ref: '#\/components\/responses\/standardResponse'                      # \u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442, \u0434\u043b\u044f \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439                         \/test\/private:         get:             description: 'Testing private section'             tags:                 - Test             operationId: testPrivate             security:                 - access: ['free']             responses:                 '200':                     $ref: '#\/components\/responses\/standardResponse'  # \u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442, \u0434\u043b\u044f \u043f\u043b\u0430\u0442\u043d\u044b\u0445 \u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u043e\u0432     \/test\/subscription:         get:             description: 'Testing subscribers section'             tags:                 - Test             operationId: testSubscription             security:                 - access: ['subscriber']             responses:                 '200':                     $ref: '#\/components\/responses\/standardResponse'  # \u041c\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 # \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u043b\u0435 email, \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440 \u0431\u0443\u0434\u0435\u0442 \u043e\u0436\u0438\u0434\u0430\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442 email     \/user\/register:         post:             description: 'Register user'             tags:                 - User             operationId: userRegister             requestBody:                 required: true                 content:                     application\/json:                         schema:                             type: object                             properties:                                 email:                                     type: string                                     format: email                                 nickname:                                     type: string                                 password:                                     type: string                                     format: password             responses:                 '200':                     description: OK                     $ref: '#\/components\/responses\/standardResponse'                 '400':                     description: Bad Request                     $ref: '#\/components\/responses\/standardResponse'  # \u041c\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438     \/user\/login:         post:             description: 'Login user'             tags:                 - User             operationId: userLogin             requestBody:                 required: true                 content:                     application\/json:                         schema:                             type: object                             properties:                                 email:                                     type: string                                     format: email                                 password:                                     type: string                                     format: password             responses:                 '200':                     description: Returns boolean success state and jwt object                     content:                         application\/json:                             schema:                                 type: object                                 properties:                                     success:                                         type: boolean                                     jwt:                                         type: object                 '400':                     description: Login failed                     $ref: '#\/components\/responses\/standardResponse'                                              \/user\/refresh:         get:             description: 'Refresh token'             tags:                 - User             operationId: userRefreshToken             responses:                 '200':                     description: 'Token refreshed'                     $ref: '#\/components\/responses\/jwtResponse'                 '403':                     description: 'Token refresh error'                     $ref: '#\/components\/responses\/standardResponse'      \/user\/logout:         get:             description: 'Logout user. Remove cookie. Remove refresh token in DB'             tags:                 - User             operationId: userLogout             responses:                 '200':                     description: 'Successfully logged out'                     $ref: '#\/components\/responses\/standardResponse'                 '403':                     description: 'You are not logged in to logout!'                     $ref: '#\/components\/responses\/standardResponse'                         \/user\/profile:         get:             description: 'Returns user object'             tags:                 - User             operationId: userProfile             security:                 - access: [ 'free' ]             responses:                 '200':                     $ref: '#\/components\/responses\/standardResponse'                 '403':                     $ref: '#\/components\/responses\/standardResponse' components:     responses:         standardResponse:             description: Returns boolean success state and string message             content:                 application\/json:                     schema:                         type: object                         properties:                             success:                                 type: boolean                             message:                                 type: string         jwtResponse:             description: Returns boolean success state and jwt object             content:                 application\/json:                     schema:                         type: object                         properties:                             success:                                 type: boolean                             jwt:                                 type: object<\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u041c\u043e\u0434\u0435\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 mongoose<\/h3>\n<p>\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043a \u043d\u0435\u0439, \u044f \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0443, \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u043d \u0432 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438.<\/p>\n<p>\u0412 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b .env \u0438 \u0443\u043a\u0430\u0436\u0438\u0442\u0435 \u0432 \u043d\u0435\u043c \u043f\u043e\u0440\u0442, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0411\u0414.<\/p>\n<details class=\"spoiler\">\n<summary>.env<\/summary>\n<div class=\"spoiler__content\">\n<pre><code>PORT = 3007 DB_HOST = localhost DB_PORT = 27017 DB_NAME = passport DB_USER = passport DB_PASS = passport<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0411\u0414<\/p>\n<details class=\"spoiler\">\n<summary>.\/src\/db\/db.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import Mongoose from 'mongoose';  export const connect = async () =&gt; {     const dbHost = process.env.DB_HOST;     const dbPort = process.env.DB_PORT;     const dbName = process.env.DB_NAME;     const user = process.env.DB_USER;     const pass = process.env.DB_PASS;      const uri = `mongodb:\/\/${dbHost}:${dbPort}\/${dbName}?authSource=dbWithCredentials`;      await Mongoose.connect(uri, {         authSource: dbName,         user,         pass,         useNewUrlParser: true,         useFindAndModify: true,         useUnifiedTopology: true,         useCreateIndex: true     }).catch((err) =&gt; console.error(err));      const db = Mongoose.connection;     db.on('error', () =&gt; {         throw new Error('Error connecting database');     }); };<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f<\/p>\n<details class=\"spoiler\">\n<summary>.\/src\/db\/models\/User.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import mongoose from 'mongoose';  export const userSchema = new mongoose.Schema(     {         email: {             type: String,             required: true,             unique: true         },         nickname: {             type: String,             required: true,             unique: true         },         hash: {             type: String,             required: true         },         salt: {             type: String,             required: false         },         refreshToken: {             type: Object         },         group: {             type: String         }     },     { versionKey: false } );<\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432<\/h3>\n<p>\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0443 \u0432 \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b, \u0435\u0441\u043b\u0438 \u0442\u0430\u043c \u043c\u043d\u043e\u0433\u043e \u0432\u0441\u0435\u0433\u043e, \u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u0430\u0436\u0435 \u043f\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f\u043c. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0445\u0432\u0430\u0442\u0438\u0442 \u0444\u0430\u0439\u043b\u043e\u0432.<\/p>\n<p>\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 user.js \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043b\u043e\u0433\u0438\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0430 \u0432 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430\u0445 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u0440\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c. \u0417\u0434\u0435\u0441\u044c \u0431\u0435\u0437 \u043e\u0441\u043e\u0431\u044b\u0445 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432, \u043a\u043e\u0434 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0432\u043f\u043e\u043b\u043d\u0435 \u043f\u043e\u043d\u044f\u0442\u0435\u043d.<\/p>\n<details class=\"spoiler\">\n<summary>.\/src\/api\/controllers\/user.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import mongoose from 'mongoose';  import { userSchema } from '..\/..\/db\/models\/User'; import * as cryptoHelper from '..\/..\/utils\/cryptoHelper'; import * as jwtHelper from '..\/..\/utils\/jwtHelper';  const User = mongoose.model('User', userSchema);  async function sendAndSetTokens(req, res, user) {     const jwt = jwtHelper.issueJWT(user);     const refresh = jwtHelper.issueRefresh(user);      user.refreshToken = refresh;     await user.save();      res.cookie('refreshToken', refresh, {         secure: true,         httpOnly: true     });     res.status(200).json({         success: true,         jwt: {             token: jwt.token,             expiresIn: jwt.expires         }     }); }  \/\/ \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 refresh \u0442\u043e\u043a\u0435\u043d\u0430 \u0438\u0437 \u0431\u0430\u0437\u044b async function resetRefresh(user) {     user.refreshToken = '';     await user.save(); }  \/\/ \u0421\u0431\u0440\u043e\u0441 \u043a\u0443\u043a\u0438, \u043f\u0443\u0442\u0435\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0441\u0442\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438 \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u0441\u0440\u043e\u043a\u0430 \u0436\u0438\u0437\u043d\u0438 function resetCookie(req, res) {     res.cookie('refreshToken', '', {         maxAge: 1000,         secure: true,         httpOnly: true     });     res.status(200).json({         success: true,         message: 'Successfully logged out'     }); }  export function userRegister(req, res, next) {     const { email, nickname, password } = req.body;     const saltHash = cryptoHelper.genHashWithSalt(password);     const { salt, hash } = saltHash;      const newUser = new User({         email,         nickname,         hash,         salt,         group: 'free'     });      newUser         .save()         .then(() =&gt; {             res.status(200).json({ success: true, message: 'User registered' });         })         .catch((err) =&gt; {             if (err.code === 11000 || err.code === 11001) {                 res.status(409).json({                     success: false,                     message: `E-mail ${email} already registered, try another or log in.`                 });             } else {                 res.status(400).json({                     success: false,                     message: err.message                 });             }         }); }  export function userLogin(req, res, next) {     User.findOne({ email: req.body.email })         .then(async (user) =&gt; {             if (!user) {                 res.status(401).json({                     success: false,                     message: 'Wrong login or password'                 });             }             const isValid = cryptoHelper.validatePassword(req.body.password, user.hash, user.salt);             if (isValid) {                 await sendAndSetTokens(req, res, user);             } else {                 res.status(401).json({                     success: false,                     message: 'Wrong login or password'                 });             }         })         .catch((err) =&gt; next(err)); }  export function userRefreshToken(req, res, next) {     const refreshCandidate = req.cookies.refreshToken;     if (refreshCandidate) {         if (jwtHelper.isValidRefresh(refreshCandidate.token)) {             User.findOne({ refreshToken: refreshCandidate })                 .then(async (user) =&gt; {                     await sendAndSetTokens(req, res, user);                 })                 .catch(() =&gt; {                     res.status(403).json({                         success: false,                         message: 'Invalid Refresh Token!'                     });                 });         } else {             res.status(403).json({                 success: false,                 message: 'Invalid Refresh Token!'             });         }     } else {         res.status(401).json({             success: false,             message: 'Refresh Token Empty!'         });     } }  export function userLogout(req, res, next) {     const refreshCandidate = req.cookies.refreshToken;     if (refreshCandidate) {         if (jwtHelper.isValidRefresh(refreshCandidate.token)) {             User.findOne({ refreshToken: refreshCandidate })                 .then(async (user) =&gt; {                     await resetRefresh(user);                     resetCookie(req, res);                 })                 .catch((err) =&gt; next(err));         } else {             res.status(401).json({                 success: false,                 message: 'You are not logged in to logout!'             });         }     } else {         res.status(401).json({             success: false,             message: 'Refresh Token Empty!!'         });     } }  export function userProfile(req, res, next) {     if (req.user) {         res.status(200).json(req.user);     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 test.js \u2013 \u043d\u0430\u0431\u043e\u0440 \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0435\u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430.<\/p>\n<details class=\"spoiler\">\n<summary>.\/src\/api\/controllers\/test.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">export function ping(req, res) {     res.json({         success: true,         message: 'Pong'     }); }  export function testSubscription(req, res) {     res.status(200).json({         success: true,         message: 'You are subscriber!'     }); }  export function testPrivate(req, res) {     res.status(200).json({         success: true,         message: 'You are in Private!'     }); }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u044d\u0442\u043e \u0440\u0430\u0437\u043e\u043c \u0432 .\/src\/api\/controllers\/index.js<\/p>\n<pre><code class=\"javascript\">export * from '.\/test'; export * from '.\/user';<\/code><\/pre>\n<h3>\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u0441\u0435 \u0432\u043e\u0435\u0434\u0438\u043d\u043e<\/h3>\n<p>\u041d\u0430\u043c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u0435 \u0432 \u043a\u0443\u0447\u0443 \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u043e\u0447\u043a\u0443 \u0432\u0445\u043e\u0434\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c server.js \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u043c \u0435\u0433\u043e \u0432 .\/src\/utils \u0438 app.js, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043b\u043e\u0436\u0438\u043c \u0432 .\/src<\/p>\n<p>\u041d\u0430 \u044d\u0442\u0438\u0445 \u0444\u0430\u0439\u043b\u0430\u0445 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c\u0441\u044f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0438\u043c\u043f\u043e\u0440\u0442\u043e\u0432, \u0447\u0442\u043e \u0434\u043b\u044f \u0447\u0435\u0433\u043e \u043d\u0443\u0436\u043d\u043e:<\/p>\n<ul>\n<li>\n<p>express \u2013 \u0441\u0430\u043c \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0435\u0440<\/p>\n<\/li>\n<li>\n<p>cookieParser \u2013 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0435 \u041f\u041e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u043a\u0443\u043a\u0438<\/p>\n<\/li>\n<li>\n<p>swaggerUI \u2013 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f API \u0432 yaml \u0444\u0430\u0439\u043b\u0435.<\/p>\n<\/li>\n<li>\n<p>swagger-routes-express \u2013 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 (\u043b\u0438\u043d\u043a\u043e\u0432\u043a\u0430 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432 \u043a \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u0432 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u0442\u043e\u0433\u043e \u0436\u0435 yaml \u0444\u0430\u0439\u043b\u0430 API)<\/p>\n<\/li>\n<li>\n<p>yaml \u2013 \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 yaml \u0444\u0430\u0439\u043b\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p>express-openapi-validator \u2013 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 (\u043c\u043e\u0436\u0435\u0442 \u0438 \u043e\u0442\u0432\u0435\u0442\u044b \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043d\u043e \u044f \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0430\u043b. \u0412\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0440\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 true)<\/p>\n<\/li>\n<li>\n<p>morgan \u2013 \u043c\u043e\u0449\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0434\u043b\u044f \u0432\u044b\u0432\u043e\u0434\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c, \u0447\u0442\u043e\u0431\u044b \u0434\u0435\u0431\u0430\u0436\u0438\u0442\u044c \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<\/li>\n<li>\n<p>cors \u2013 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 CORS, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0443\u0447\u043a\u0430\u043c\u0438. \u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043d\u0438\u0436\u0435.<\/p>\n<\/li>\n<li>\n<p>passport \u2013 \u0442\u0430 \u0441\u0430\u043c\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u043d\u0430\u043c \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u043e \u0437\u0430\u0449\u0438\u0442\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b, \u0431\u0430\u0437\u0443, \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044e passport.<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0448\u0443 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044e, \u043f\u0435\u0440\u0435\u0434\u0430\u0432 \u0435\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 passport:<\/p>\n<pre><code class=\"javascript\">strategy(passport);<\/code><\/pre>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u0411\u0414:<\/p>\n<pre><code class=\"javascript\">db.connect()     .then(() =&gt; console.log('MongoDB connected'))     .catch((error) =&gt; console.error(error));<\/code><\/pre>\n<p>\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e API:<\/p>\n<pre><code class=\"javascript\">const yamlSpecFile = '.\/bin\/api\/apiV1.yaml'; const apiDefinition = YAML.load(yamlSpecFile);  const apiSummary = summarise(apiDefinition); console.info(apiSummary);<\/code><\/pre>\n<p>\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0438\u043d\u0441\u0442\u0430\u043d\u0441 express:<\/p>\n<pre><code class=\"javascript\">const server = express();<\/code><\/pre>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430<\/p>\n<pre><code class=\"javascript\">\/\/ \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e morgan server.use(morgan('dev')); \/\/ \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u043c \u0441\u0435\u0431\u0435 \u0447\u0438\u0442\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438\u0437 url server.use(express.urlencoded({ extended: true }));  \/\/ \u044d\u0442\u043e \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0435 \u043f\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0430\u0440\u0441\u0438\u0442\u044c \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441 application\/json server.use(express.json());  \/\/ \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u043a\u0443\u043a\u0438 server.use(cookieParser()); \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 CORS. \u0412 \u0431\u043e\u0435\u0432\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0441\u0442\u043e\u0438\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0430, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043d\u0430\u0448 \u0411\u042d\u041a \/\/var corsOptions = { \/\/  origin: 'http:\/\/example.com', \/\/  optionsSuccessStatus: 200 \/\/ some legacy browsers (IE11, various SmartTVs) choke on 204 \/\/} \/\/ cors(corsOptions) \/\/ \u0411\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u043f\u0430\u043a\u0435\u0442\u0430 \u043d\u0430 npmjs.com server.use(cors()); \/\/ \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c passport.js server.use(passport.initialize());<\/code><\/pre>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432<\/p>\n<pre><code class=\"javascript\">\/\/ \u0427\u0442\u043e\u0431\u044b \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u043e\u0442\u0432\u0435\u0442\u043e\u0432, \u043f\u043e\u043f\u0440\u0430\u0432\u044c\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 validateResponses \/\/ \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c yaml \u0444\u0430\u0439\u043b API  const validatorOptions = {     coerceTypes: false,     apiSpec: yamlSpecFile,     validateRequests: true,     validateResponses: false }; server.use(OpenApiValidator.middleware(validatorOptions));  \/\/ \u041a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0430\u0446\u0438\u044f \u043e\u0448\u0438\u0431\u043e\u043a, \u0435\u0441\u043b\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 server.use((err, req, res, next) =&gt; {     res.status(err.status).json({         error: {             type: 'request_validation',             message: err.message,             errors: err.errors         }     }); });<\/code><\/pre>\n<p>\u0421\u0430\u043c\u044b\u0439 \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u0443\u0447\u0430\u0441\u0442\u043e\u043a  \u2013 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0438 \u0438\u0445 \u0437\u0430\u0449\u0438\u0442\u0430.<\/p>\n<p>\u041a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0443 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u044a\u0435\u043a\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u0432\u0441\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u0432  \u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 API. \u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u044d\u0442\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u043d \u043b\u0438\u043d\u043a\u0443\u0435\u0442 \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0438 \u0433\u0430\u0439\u0434\u0430\u0445 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043a\u0430\u043a <\/p>\n<p><code>server.use('\/route\/to\/something', controllerFunction... <\/code><\/p>\n<p>\u0443 \u043d\u0430\u0441 \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0431\u0443\u0434\u0435\u0442.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043e\u0431\u044a\u0435\u043a\u0442 security, \u043e\u0431\u044a\u0435\u043a\u0442\u044b subscriber \u0438 free,  \u044d\u0442\u043e \u043f\u043e\u043b\u044f \u0438\u0437 yaml \u0444\u0430\u0439\u043b\u0430 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f api, \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 security acess. \u041f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u043c\u0443 \u041f\u041e \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u0434\u043b\u044f middleware + \u043e\u0431\u044a\u0435\u043a\u0442 paspport + \u043c\u0430\u0441\u0441\u0438\u0432 \u0433\u0440\u0443\u043f\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043c, \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u0443\u0440\u043e\u0432\u043d\u0435\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u0430.<\/p>\n<pre><code class=\"javascript\">const connect = connector(api, apiDefinition, {     onCreateRoute: (method, descriptor) =&gt; {         console.log(             `Method ${method} of endpoint ${descriptor[0]} linked to ${descriptor[1].name}`         );     },     security: {         subscriber: (req, res, next) =&gt; {             securityMiddleware(req, res, next, passport, ['subscriber', 'admin']);         },         free: (req, res, next) =&gt; {             securityMiddleware(req, res, next, passport, ['free', 'subscriber', 'admin']);         }     } });<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0435\u0440 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u043e\u043c \u0438 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c<\/p>\n<pre><code class=\"javascript\">connect(server);  module.exports = server;<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>.\/src\/utils\/server.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import express from 'express'; import cookieParser from 'cookie-parser'; import swaggerUi from 'swagger-ui-express';  import { connector, summarise } from 'swagger-routes-express'; import YAML from 'yamljs'; import * as OpenApiValidator from 'express-openapi-validator'; import morgan from 'morgan'; import cors from 'cors'; import passport from 'passport'; import * as api from '..\/api\/controllers'; import * as db from '..\/db\/db'; import { securityMiddleware } from '.\/securityMiddleware';  import { strategy } from '.\/passport';  strategy(passport);  \/\/ connect to DB db.connect()     .then(() =&gt; console.log('MongoDB connected'))     .catch((error) =&gt; console.error(error));  \/\/ load API definition const yamlSpecFile = '.\/bin\/api\/apiV1.yaml'; const apiDefinition = YAML.load(yamlSpecFile);  const apiSummary = summarise(apiDefinition); console.info(apiSummary);  const server = express();   server.use(morgan('dev')); server.use(express.urlencoded({ extended: true })); server.use(express.json()); server.use(cookieParser());  server.use(cors());  server.use(passport.initialize());  \/\/ API Documentation server.use('\/api-docs', swaggerUi.serve, swaggerUi.setup(apiDefinition, { explorer: false }));  \/\/ Automatic validation const validatorOptions = {     coerceTypes: false,     apiSpec: yamlSpecFile,     validateRequests: true,     validateResponses: false }; server.use(OpenApiValidator.middleware(validatorOptions));  \/\/ error customization, if request is invalid server.use((err, req, res, next) =&gt; {     res.status(err.status).json({         error: {             type: 'request_validation',             message: err.message,             errors: err.errors         }     }); });  \/\/ Automatic routing based on api definition const connect = connector(api, apiDefinition, {     onCreateRoute: (method, descriptor) =&gt; {         console.log(             `Method ${method} of endpoint ${descriptor[0]} linked to ${descriptor[1].name}`         );     },     security: {         subscriber: (req, res, next) =&gt; {             securityMiddleware(req, res, next, passport, ['subscriber', 'admin']);         },         free: (req, res, next) =&gt; {             securityMiddleware(req, res, next, passport, ['free', 'subscriber', 'admin']);         }     } });  connect(server);  module.exports = server; <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u2013 app.js. \u0417\u0434\u0435\u0441\u044c \u0432\u0441\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e, \u0440\u0430\u0441\u043f\u0438\u0448\u0443 \u0432\u0441\u0435 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445.<\/p>\n<pre><code class=\"javascript\">import https from 'https'; import fs from 'fs'; import * as dotenv from 'dotenv';  import server from '.\/utils\/server';  \/\/ \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u043c \u0432 process.env \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0438\u0437 .env \u0444\u0430\u0439\u043b\u0430 dotenv.config(); const { PORT } = process.env;  \/\/ \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0439 \u043a\u043b\u044e\u0447 const privateKey = fs.readFileSync('.\/bin\/crypto\/ssl.key'); const certificate = fs.readFileSync('.\/bin\/crypto\/ssl.crt');  const options = { key: privateKey, cert: certificate };  \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c HTTPS \u0441\u0435\u0440\u0432\u0435\u0440 const app = https.createServer(options, server);  \/\/ \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043d\u0430 \u043f\u043e\u0440\u0442\u0443, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u0432 .env \u0444\u0430\u0439\u043b\u0435 app.listen(PORT, () =&gt; {     console.info(`Listening on https:\/\/localhost:${PORT}`);     console.info(`Open https:\/\/localhost:${PORT}\/api-docs for documentation`); });<\/code><\/pre>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0432\u0437\u044f\u0442\u0430 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u0442\u0430\u0442\u0435\u0439:<\/p>\n<p><a href=\"https:\/\/losikov.medium.com\/part-2-express-open-api-3-0-634385c97a4e\" rel=\"noopener noreferrer nofollow\">https:\/\/losikov.medium.com\/part-2-express-open-api-3-0-634385c97a4e<\/a><\/p>\n<p><a href=\"https:\/\/medium.com\/swlh\/everything-you-need-to-know-about-the-passport-jwt-passport-js-strategy-8b69f39014b0\" rel=\"noopener noreferrer nofollow\">https:\/\/medium.com\/swlh\/everything-you-need-to-know-about-the-passport-jwt-passport-js-strategy-8b69f39014b0<\/a><\/p>\n<p>\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u043d\u0430\u0434\u0435\u044e\u0441\u044c \u043a\u043e\u043c\u0443-\u0442\u043e \u044d\u0442\u043e\u0442 \u043b\u043e\u043d\u0433\u0440\u0438\u0434 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 .<\/p>\n<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/559136\/\"> https:\/\/habr.com\/ru\/post\/559136\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<h3>\u041f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0438<\/h3>\n<p>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d RESTful backend \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430 NodeJS + Express, \u043a\u043e\u0442\u043e\u0440\u044b\u0439:<\/p>\n<ul>\n<li>\n<p>\u043b\u0435\u0433\u043a\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u043e\u043c<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0443 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u0438\u043c\u0435\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0443\u044e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e<\/p>\n<\/li>\n<\/ul>\n<p>\u0413\u0430\u0439\u0434 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043e\u0431\u0448\u0438\u0440\u043d\u044b\u0439, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0447\u0430\u0441\u0442\u0438, \u0430 \u0437\u0430\u0442\u0435\u043c \u0441\u043e\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u043e\u0435\u0434\u0438\u043d\u043e. \u0413\u043e\u0442\u043e\u0432\u044b\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 <a href=\"https:\/\/github.com\/DzenDyn\/baseBackend\" rel=\"noopener noreferrer nofollow\">Github<\/a>. <\/p>\n<h3>\u041d\u0430\u0431\u043e\u0440 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432<\/h3>\n<p>\u0421\u0435\u0440\u0434\u0446\u0435 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2013 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f OpenApi 3.0. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 API \u043d\u0430 \u044f\u0437\u044b\u043a\u0435 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 YAML, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0437\u0430\u0449\u0438\u0449\u0430\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c API. <\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b \u0432\u043e\u0437\u044c\u043c\u0435\u043c MongoDB \u0438 mongoose, \u0432 \u0446\u0435\u043b\u043e\u043c \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u043e\u043c\u0435\u0448\u0430\u0435\u0442 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u044d\u0442\u0443 \u0441\u0432\u044f\u0437\u043a\u0443 \u043d\u0430 \u043b\u044e\u0431\u0443\u044e \u0434\u0440\u0443\u0433\u0443\u044e \u0432 \u0441\u0432\u043e\u0451\u043c \u0448\u0430\u0431\u043b\u043e\u043d\u0435.<\/p>\n<p>Passport.js \u2013 \u0437\u0430\u0449\u0438\u0442\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432, \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f. \u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f passport-jwt. \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c jwt-access \u0438 refresh \u0442\u043e\u043a\u0435\u043d\u044b.<\/p>\n<h3>\u041f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430<\/h3>\n<p>\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442, \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0432 npm init \u0438\u043b\u0438 yarn init, \u044f \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u044e yarn.<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0442\u043e\u0438\u0442 \u043f\u043e\u0437\u0430\u0431\u043e\u0442\u0438\u0442\u044c\u0441\u044f \u043e\u0431 \u0443\u0434\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0441\u0442\u0438\u043b\u0435 \u043a\u043e\u0434\u0430 \u0438 \u0434\u043e\u043f\u0443\u0449\u0435\u043d\u0438\u044f\u0445.<br \/>\u0417\u0430 \u0441\u0442\u0438\u043b\u044c \u043a\u043e\u0434\u0430 \u0443 \u043c\u0435\u043d\u044f \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0442 eslint \u0438 prettier. <\/p>\n<p>\u0412 \u043a\u043e\u0440\u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0438 \u0434\u043b\u044f eslint \u0438 prettier. \u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 \u0441\u0431\u043e\u0440\u043a\u0438 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e nodemon, npm-run-all, rimraf, babel. \u041d\u0438\u0436\u0435 \u043c\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>.eslintrc.json<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"json\">{     \"env\": {         \"node\": true,         \"es2021\": true     },     \"extends\": [         \"eslint:recommended\",         \"airbnb-base\",         \"prettier\"     ],     \"plugins\": [         \"prettier\"     ],     \"rules\": {         \"no-console\": 0,         \"prettier\/prettier\": [\"error\"],         \"import\/extensions\": 0,         \"import\/prefer-default-export\": \"off\",         \"import\/no-unresolved\": 0,         \"no-duplicate-imports\": [\"error\", { \"includeExports\": true }],         \"react\/prop-types\": 0,         \"no-underscore-dangle\": 0,         \"no-param-reassign\": [\"error\", { \"props\": false }],         \"no-case-declarations\": 0,         \"no-plusplus\": [\"error\", { \"allowForLoopAfterthoughts\": true }],         \"space-infix-ops\": [\"error\", { \"int32Hint\": false }],         \"no-unused-vars\": [\"error\", { \"argsIgnorePattern\": \"next\" }]     } }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>.prettierrc<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"json\">{     \"printWidth\": 100,     \"singleQuote\": true,     \"tabWidth\": 4,     \"bracketSpacing\": true,     \"endOfLine\": \"lf\",     \"semi\": true,     \"trailingComma\": \"none\" }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u0441\u0432\u043e\u0439 package.json<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"json\">\"dependencies\": {     \"@babel\/node\": \"^7.13.13\",     \"body-parser\": \"^1.19.0\",     \"connect\": \"^3.7.0\",     \"cookie-parser\": \"^1.4.5\",     \"cors\": \"^2.8.5\",     \"dotenv\": \"^8.2.0\",     \"express\": \"^4.17.1\",     \"express-openapi-validator\": \"^4.12.6\",     \"jsonwebtoken\": \"^8.5.1\",     \"mongoose\": \"^5.12.2\",     \"morgan\": \"^1.10.0\",     \"passport\": \"^0.4.1\",     \"passport-jwt\": \"^4.0.0\",     \"swagger-routes-express\": \"^3.3.0\",     \"swagger-ui-express\": \"^4.1.6\",     \"uuid\": \"^8.3.2\",     \"validator\": \"^13.5.2\",     \"yamljs\": \"^0.3.0\"   },   \"devDependencies\": {     \"@babel\/cli\": \"^7.13.14\",     \"@babel\/core\": \"^7.13.14\",     \"@babel\/preset-env\": \"^7.13.12\",     \"eslint\": \"^7.23.0\",     \"eslint-config-airbnb-base\": \"^14.2.1\",     \"eslint-config-prettier\": \"^8.1.0\",     \"eslint-plugin-import\": \"^2.22.1\",     \"eslint-plugin-prettier\": \"^3.3.1\",     \"nodemon\": \"^2.0.7\",     \"npm-run-all\": \"^4.1.5\",     \"prettier\": \"^2.2.1\",     \"rimraf\": \"^3.0.2\"   },   \"babel\": {     \"presets\": [       \"@babel\/preset-env\"     ]   },   \"scripts\": {     \"transpile\": \"babel .\/src --out-dir bin --copy-files\",     \"clean\": \"rimraf bin\",     \"build\": \"npm-run-all clean transpile\",     \"server\": \"node .\/bin\/app.js\",     \"dev\": \"npm-run-all build server\",     \"start\": \"yarn dev\",     \"watch\": \"nodemon\"   }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 nodemon.json \u0432 \u043a\u043e\u0440\u043d\u0435<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"json\">{     \"watch\": [\"src\/*\"],     \"ext\": \"js, json, yaml\",     \"exec\": \"yarn run dev\" }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0432 npm \u0438\u043b\u0438 yarn.<\/p>\n<h2>\u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c<\/h2>\n<p>\u042f \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0448\u0430\u0433\u043e\u0432\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c. \u041d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0441\u043e\u0431\u0440\u0430\u043b \u0438\u0445 \u0432 <a href=\"https:\/\/disk.yandex.ru\/d\/vk9sCckbPYvnHA\" rel=\"noopener noreferrer nofollow\">PDF<\/a>.<\/p>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0442\u0430\u043a\u0430\u044f:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 JWT access \u0442\u043e\u043a\u0435\u043d \u0438 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u044b\u0439 http-only \u043a\u0443\u043a\u0438 \u0441 refresh \u0442\u043e\u043a\u0435\u043d\u043e\u043c.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u043d\u0435 \u0438\u0441\u0442\u0435\u043a \u043b\u0438 \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 (JWT access token), \u0435\u0441\u043b\u0438 \u0432\u0441\u0435 \u043e\u043a, \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u00abAuthorization\u00bb, \u0435\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u0438\u0441\u0442\u0435\u043a, \u0442\u043e \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u041d\u0438\u0436\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043f\u0440\u043e \u043d\u0430\u0448 \u0431\u044d\u043a\u0435\u043d\u0434.<\/p>\n<\/li>\n<\/ol>\n<h4>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f<\/h4>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<ol>\n<li>\n<p>\u041d\u0430 Backend \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u0438\u0434\u0435(<strong>\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e HTTPS<\/strong>) e-mail, \u043f\u0430\u0440\u043e\u043b\u044c, \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u044b (nickname \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435)<\/p>\n<\/li>\n<li>\n<p>\u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u0441\u043e\u043b\u044c \u0438 \u0445\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u043f\u0430\u0440\u043e\u043b\u044c \u0441 \u044d\u0442\u043e\u0439 \u0441\u043e\u043b\u044c\u044e, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0432 \u0431\u0430\u0437\u0443<\/p>\n<\/li>\n<\/ol>\n<h4>\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f<\/h4>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/habrastorage.org\/webt\/_h\/ap\/nn\/_hapnnjoll_whtbp-j5seqzzt28.png\" rel=\"noopener noreferrer nofollow\">\u041f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f<\/a><\/p>\n<ol>\n<li>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u043f\u043e HTTPS email \u0438 \u043f\u0430\u0440\u043e\u043b\u044c<\/p>\n<\/li>\n<li>\n<p>\u041f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0431\u0430\u0437\u044b<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043b\u0438\u0431\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043b\u0438\u0431\u043e undefined<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 undefined \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435, \u0438 \u043d\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043d\u0435\u0432\u0435\u0440\u043d\u0430 \u043f\u043e\u0447\u0442\u0430 \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c<\/p>\n<\/li>\n<li>\n<p>\u0411\u0435\u0440\u0435\u043c \u0438\u0437 \u0431\u0430\u0437\u044b \u0441\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0445\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u0441 \u044d\u0442\u043e\u0439 \u0441\u043e\u043b\u044c\u044e \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u0441\u0432\u0435\u0440\u044f\u0435\u043c \u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u043c \u0432 \u0431\u0430\u0437\u0435 \u0445\u044d\u0448\u0435\u043c. \u0415\u0441\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0432\u0432\u0435\u0434\u0435\u043d \u043d\u0435\u0432\u0435\u0440\u043d\u043e, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435, \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u043f\u0443\u043d\u043a\u0442\u0443 4<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0432\u0432\u0435\u0434\u0435\u043d \u0432\u0435\u0440\u043d\u043e, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c JWT \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0441 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u043c \u0441\u0440\u043e\u043a\u043e\u043c \u0436\u0438\u0437\u043d\u0438 \u0438 Refresh \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0443\u0436\u0435\u043d \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0441 \u0431\u043e\u043b\u0435\u0435 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0441\u0440\u043e\u043a\u043e\u043c \u0436\u0438\u0437\u043d\u0438. <strong>\u042d\u0442\u043e \u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435, \u043d\u043e refresh \u0442\u043e\u043a\u0435\u043d \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0443 \u043a\u0430\u043a \u043e\u0434\u043d\u043e \u0438\u0437 \u043f\u043e\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/strong><\/p>\n<\/li>\n<li>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e JWT \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c HTTP-only cookie (secure, \u0442.\u043a. \u0443 \u043d\u0430\u0441 HTTPS). <\/p>\n<\/li>\n<\/ol>\n<h4>\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 JWT \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/h4>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/habrastorage.org\/webt\/lz\/m3\/-x\/lzm3-xbws1lin0ze6uki2y2xvoc.png\" rel=\"noopener noreferrer nofollow\">\u041f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f<\/a><\/p>\n<ol>\n<li>\n<p>\u041e\u0431\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u0437 HTTP-only cookie refresh \u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 refresh \u0442\u043e\u043a\u0435\u043d\u0430 \u043d\u0435\u0442 \u2013 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u0435\u0441\u0442\u044c, \u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0441\u0442\u044c. \u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0418\u0449\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e refresh \u0442\u043e\u043a\u0435\u043d\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d, \u0442\u043e \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0431\u043e\u043b\u0435\u0435 \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0439\u0434\u0435\u043d, \u0442\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u043f\u0430\u0440\u0443 JWT \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 refresh \u0442\u043e\u043a\u0435\u043d\u0430. <strong>\u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c refresh \u0432 \u0431\u0430\u0437\u0443<\/strong><\/p>\n<\/li>\n<li>\n<p>\u0418 \u043a\u0430\u043a \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c refresh \u0442\u043e\u043a\u0435\u043d \u0432 cookie<\/p>\n<\/li>\n<\/ol>\n<h4>\u0412\u044b\u0445\u043e\u0434 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/h4>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/habrastorage.org\/webt\/m6\/ug\/_9\/m6ug_9kzgg25bqvy2zvryop9p3g.png\" rel=\"noopener noreferrer nofollow\">\u041f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f<\/a><\/p>\n<ol>\n<li>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043d\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435 \u043e\u0448\u0438\u0431\u043a\u0430, \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0447\u0442\u043e-\u0442\u043e \u0442\u0438\u043f\u0430 \/user\/logout<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u0437 HTTP-only cookie refresh \u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 refresh \u0442\u043e\u043a\u0435\u043d\u0430 \u043d\u0435\u0442 \u2013 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u0435\u0441\u0442\u044c, \u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0441\u0442\u044c. \u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0418\u0449\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e refresh \u0442\u043e\u043a\u0435\u043d\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d, \u0442\u043e \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0431\u043e\u043b\u0435\u0435 \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0431\u0430\u0437\u044b refresh \u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<li>\n<p>\u0421\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c cookie \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430<\/p>\n<\/li>\n<\/ol>\n<h4>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<figure class=\"bordered\"><figcaption><\/figcaption><\/figure>\n<p>\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0439 \u043a\u043b\u044e\u0447 \u043a \u043d\u0435\u043c\u0443<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043a\u0440\u044b\u0442\u044b\u0439 \u0438 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JWT \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043a\u0440\u044b\u0442\u044b\u0439 \u0438 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JWT refresh \u0442\u043e\u043a\u0435\u043d\u0430. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 refresh \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c uuid, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u043e \u044f \u043d\u0435 \u0438\u0449\u0443 \u043b\u0435\u0433\u043a\u0438\u0445 \u043f\u0443\u0442\u0435\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u043d\u0435\u0442 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430, \u043c\u043e\u0436\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0439, \u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0432 \u0431\u043e\u0435\u0432\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043d\u0435 \u0441\u0442\u043e\u0438\u0442, \u0442\u0430\u043a \u043a\u0430\u043a \u043a self-signed \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430\u043c \u043d\u0435\u0442 \u0434\u043e\u0432\u0435\u0440\u0438\u044f.<\/p>\n<p>\u0418\u0442\u0430\u043a \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430 \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f openssl:<\/p>\n<pre><code>openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout ssl.key -out ssl.crt<\/code><\/pre>\n<p>\u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f JWT:<\/p>\n<pre><code>ssh-keygen -t rsa -b 4096 -m PEM -f jwtPrivate.key openssl rsa -in jwtPrivate.key -pubout -outform PEM -out jwtPublic.pem  ssh-keygen -t rsa -b 4096 -m PEM -f refreshPrivate.key openssl rsa -in refreshPrivate.key -pubout -outform PEM -out refreshPublic.pem<\/code><\/pre>\n<p>\u0412\u0441\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u044b \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c \u0432 .\/src\/crypto\/<\/p>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439:<\/p>\n<details class=\"spoiler\">\n<summary>.\/src\/utils\/cryptoHelper.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import crypto from 'crypto';  \/**  * \u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f  *\/ export function validatePassword(password, hash, salt) {     const hashCandidate = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');     return hash === hashCandidate; }  \/**  * \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u043e\u043b\u0438 \u0438 \u0445\u044d\u0448\u0430 \u043f\u0430\u0440\u043e\u043b\u044f  *\/ export function genHashWithSalt(password) {     const salt = crypto.randomBytes(32).toString('hex');     const hash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');      return {         salt,         hash     }; }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>.\/src\/utils\/jwtHelper.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import fs from 'fs'; import path from 'path'; import jsonwebtoken from 'jsonwebtoken';  \/\/ \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043f\u0443\u0442\u0438 \u0438 \u0447\u0438\u0442\u0430\u0435\u043c \u043a\u043b\u044e\u0447\u0438 const jwtPrivate = path.join(__dirname, '..\/crypto\/', 'jwtPrivate.pem'); const refreshPrivate = path.join(__dirname, '..\/crypto\/', 'refreshPrivate.pem'); const refreshPublic = path.join(__dirname, '..\/crypto\/', 'refreshPublic.pem'); const JWT_PRIV_KEY = fs.readFileSync(jwtPrivate, 'utf8'); const REFRESH_PRIV_KEY = fs.readFileSync(refreshPrivate, 'utf8'); const REFRESH_PUBLIC_KEY = fs.readFileSync(refreshPublic, 'utf8');  \/\/ \u0432\u044b\u043f\u0443\u0441\u043a JWT \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 export function issueJWT(user) {     const { _id } = user;     const expiresIn = '10m';      const payload = {         uid: _id,         iat: Math.floor(Date.now() \/ 1000)     };      const signedToken = jsonwebtoken.sign(payload, JWT_PRIV_KEY, { expiresIn, algorithm: 'RS256' });      return {         token: `Bearer ${signedToken}`,         expires: expiresIn     }; }  \/\/\u0432\u044b\u043f\u0443\u0441\u043a JWT refresh \u0442\u043e\u043a\u0435\u043d\u0430 export function issueRefresh(user) {     const { _id } = user;     const expiresIn = '7d';      const payload = {         uid: _id,         iat: Math.floor(Date.now() \/ 1000)     };      const signedToken = jsonwebtoken.sign(payload, REFRESH_PRIV_KEY, {         expiresIn,         algorithm: 'RS256'     });      return {         token: signedToken,         expires: expiresIn     }; }  \/\/\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f refresh \u0442\u043e\u043a\u0435\u043d\u0430  export function isValidRefresh(token) {     try {         jsonwebtoken.verify(token, REFRESH_PUBLIC_KEY, { algorithm: 'RS256' });     } catch (error) {         return false;     }     return true; } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>.\/src\/utils\/passport.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import { Strategy, ExtractJwt } from 'passport-jwt'; import fs from 'fs'; import path from 'path'; import mongoose from 'mongoose'; import { userSchema } from<\/code><\/pre>\n<\/div>\n<\/details>\n<p><\/br><\/p>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-323801","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/323801","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=323801"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/323801\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=323801"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=323801"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=323801"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}