diff --git a/api/utils.ts b/api/utils.ts
index 8c17128e..9769ed6a 100644
--- a/api/utils.ts
+++ b/api/utils.ts
@@ -13,3 +13,5 @@ export {default as axiosInstance} from '../src/utils/axios';
 export {objectKeys} from '../src/utils/utility-types';
 
 export {whereBuilderInterTenantGetEntries} from '../src/db/models/navigation/utils';
+
+export {getEnvCert, getEnvVariable, getEnvTokenVariable, isTrueArg} from '../src/utils/env-utils';
diff --git a/package-lock.json b/package-lock.json
index 694319c6..609bfcdd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +20,7 @@
         "crc-32": "1.2.0",
         "db-errors": "^0.2.3",
         "dotenv": "^8.2.0",
+        "jsonwebtoken": "^9.0.2",
         "knex": "^3.1.0",
         "lodash": "^4.17.21",
         "minimist": "^1.2.5",
@@ -39,8 +40,9 @@
         "@trendyol/jest-testcontainers": "^2.1.1",
         "@types/express": "^4.17.15",
         "@types/jest": "^29.5.12",
+        "@types/jsonwebtoken": "^9.0.7",
         "@types/lodash": "^4.17.4",
-        "@types/node": "^18.14.4",
+        "@types/node": "^20.17.12",
         "@types/pg": "^7.14.11",
         "@types/supertest": "^2.0.10",
         "@types/swagger-ui-express": "^4.1.7",
@@ -2166,6 +2168,16 @@
       "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
       "dev": true
     },
+    "node_modules/@types/jsonwebtoken": {
+      "version": "9.0.7",
+      "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
+      "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/lodash": {
       "version": "4.17.4",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz",
@@ -2179,9 +2191,13 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "18.17.12",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.12.tgz",
-      "integrity": "sha512-d6xjC9fJ/nSnfDeU0AMDsaJyb1iHsqCSOdi84w4u+SlN/UgQdY5tRhpMzaFYsI4mnpvgTivEaQd0yOUhAtOnEQ=="
+      "version": "20.17.12",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz",
+      "integrity": "sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==",
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.19.2"
+      }
     },
     "node_modules/@types/pg": {
       "version": "7.14.11",
@@ -2260,6 +2276,23 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/ssh2/node_modules/@types/node": {
+      "version": "18.19.71",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz",
+      "integrity": "sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/@types/ssh2/node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/stack-utils": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz",
@@ -3433,6 +3466,12 @@
         "node": "*"
       }
     },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -4351,6 +4390,15 @@
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
       "dev": true
     },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -8573,6 +8621,46 @@
         "node": ">=6"
       }
     },
+    "node_modules/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+      "license": "MIT",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/jsonwebtoken/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/jsonwebtoken/node_modules/semver": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/jsx-ast-utils": {
       "version": "3.3.5",
       "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -8588,6 +8676,27 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "license": "MIT",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.3",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz",
@@ -8963,11 +9072,40 @@
       "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
       "dev": true
     },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+      "license": "MIT"
+    },
     "node_modules/lodash.isplainobject": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
-      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
-      "dev": true
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+      "license": "MIT"
     },
     "node_modules/lodash.merge": {
       "version": "4.6.2",
@@ -8975,6 +9113,12 @@
       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
       "dev": true
     },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+      "license": "MIT"
+    },
     "node_modules/lodash.union": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
@@ -11956,6 +12100,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/undici-types": {
+      "version": "6.19.8",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+      "license": "MIT"
+    },
     "node_modules/unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
diff --git a/package.json b/package.json
index 0a0e20ba..ee75c6f7 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
     "crc-32": "1.2.0",
     "db-errors": "^0.2.3",
     "dotenv": "^8.2.0",
+    "jsonwebtoken": "^9.0.2",
     "knex": "^3.1.0",
     "lodash": "^4.17.21",
     "minimist": "^1.2.5",
@@ -41,8 +42,9 @@
     "@trendyol/jest-testcontainers": "^2.1.1",
     "@types/express": "^4.17.15",
     "@types/jest": "^29.5.12",
+    "@types/jsonwebtoken": "^9.0.7",
     "@types/lodash": "^4.17.4",
-    "@types/node": "^18.14.4",
+    "@types/node": "^20.17.12",
     "@types/pg": "^7.14.11",
     "@types/supertest": "^2.0.10",
     "@types/swagger-ui-express": "^4.1.7",
diff --git a/scripts/setup-dev-env.js b/scripts/setup-dev-env.js
index 65b82411..91ffece8 100755
--- a/scripts/setup-dev-env.js
+++ b/scripts/setup-dev-env.js
@@ -16,7 +16,6 @@ const appPath = path.join(__dirname, '..');
 const templateFilePath = path.join(appPath, `dev/env/${templateName}`);
 
 let templateContent = readFileSync(templateFilePath).toString();
-const secretsSection = `${SECRETS_SECTION_START}\n${templateContent}\n${SECRETS_SECTION_END}`;
 
 if (envName === 'development') {
     try {
@@ -29,6 +28,8 @@ if (envName === 'development') {
     }
 }
 
+const templateSection = `${SECRETS_SECTION_START}\n${templateContent}\n${SECRETS_SECTION_END}`;
+
 let currentEnv;
 
 try {
@@ -39,4 +40,4 @@ try {
     currentEnv = `${SECRETS_SECTION_START}\n${SECRETS_SECTION_END}`;
 }
 
-writeFileSync(path.join(appPath, '.env'), currentEnv.replace(REPLACE_REGEXP, secretsSection));
+writeFileSync(path.join(appPath, '.env'), currentEnv.replace(REPLACE_REGEXP, templateSection));
diff --git a/src/components/api-docs/utils.ts b/src/components/api-docs/utils.ts
index c1e15f4b..338dc967 100644
--- a/src/components/api-docs/utils.ts
+++ b/src/components/api-docs/utils.ts
@@ -18,7 +18,7 @@ export const getAdditionalHeaders = (
         config.appAuthPolicy === AuthPolicy.disabled ||
         routeDescription.authPolicy === AuthPolicy.disabled;
 
-    if (!authDisabled && config.zitadelEnabled) {
+    if (!authDisabled && (config.zitadelEnabled || config.isAuthEnabled)) {
         headers.push(
             z.strictObject({
                 [AUTHORIZATION_HEADER]: z.string(),
diff --git a/src/components/auth/constants/error-constants.ts b/src/components/auth/constants/error-constants.ts
new file mode 100644
index 00000000..8015a29c
--- /dev/null
+++ b/src/components/auth/constants/error-constants.ts
@@ -0,0 +1,5 @@
+// use `US.AUTH.<name>` prefix for most cases
+
+export const AUTH_ERRORS = {
+    UNAUTHORIZED_ACCESS: 'US.AUTH.UNAUTHORIZED_ACCESS',
+};
diff --git a/src/components/auth/constants/role.ts b/src/components/auth/constants/role.ts
new file mode 100644
index 00000000..94a271f5
--- /dev/null
+++ b/src/components/auth/constants/role.ts
@@ -0,0 +1,7 @@
+export enum UserRole {
+    Editor = 'datalens.editor',
+    Admin = 'datalens.admin',
+    Viewer = 'datalens.viewer',
+    Visitor = 'datalens.visitor',
+    Creator = 'datalens.creator',
+}
diff --git a/src/components/auth/middlewares/app-auth.ts b/src/components/auth/middlewares/app-auth.ts
new file mode 100644
index 00000000..33f1e865
--- /dev/null
+++ b/src/components/auth/middlewares/app-auth.ts
@@ -0,0 +1,52 @@
+import {NextFunction, Request, Response} from '@gravity-ui/expresskit';
+import jwt, {type Algorithm} from 'jsonwebtoken';
+
+import {AUTHORIZATION_HEADER, DL_AUTH_HEADER_KEY} from '../../../const/common';
+import {AUTH_ERRORS} from '../constants/error-constants';
+import type {AccessTokenPayload} from '../types/token';
+
+const ALGORITHMS: Algorithm[] = ['PS256'];
+
+export const appAuth = async (req: Request, res: Response, next: NextFunction) => {
+    req.ctx.log('AUTH');
+
+    const authorization = req.headers[AUTHORIZATION_HEADER];
+
+    if (authorization) {
+        const accessToken = authorization.slice(DL_AUTH_HEADER_KEY.length + 1);
+
+        if (accessToken) {
+            try {
+                req.ctx.log('CHECK_ACCESS_TOKEN');
+
+                const {userId, sessionId, roles} = jwt.verify(
+                    accessToken,
+                    req.ctx.config.authTokenPublicKey || '',
+                    {
+                        algorithms: ALGORITHMS,
+                    },
+                ) as AccessTokenPayload;
+
+                req.originalContext.set('user', {
+                    userId,
+                    sessionId,
+                    accessToken,
+                    roles,
+                });
+
+                // for ctx info
+                res.locals.userId = userId;
+                res.locals.login = userId;
+
+                req.ctx.log('CHECK_ACCESS_TOKEN_SUCCESS');
+
+                next();
+                return;
+            } catch (err) {
+                req.ctx.logError('CHECK_ACCESS_TOKEN_ERROR', err);
+            }
+        }
+    }
+
+    res.status(401).send({code: AUTH_ERRORS.UNAUTHORIZED_ACCESS, message: 'Unauthorized access'});
+};
diff --git a/src/components/auth/types/token.ts b/src/components/auth/types/token.ts
new file mode 100644
index 00000000..965f2208
--- /dev/null
+++ b/src/components/auth/types/token.ts
@@ -0,0 +1,12 @@
+import type {UserRole} from '../constants/role';
+
+export interface ExpirableTokenPayload {
+    iat: number;
+    exp: number;
+}
+
+export interface AccessTokenPayload extends ExpirableTokenPayload {
+    userId: string;
+    sessionId: string;
+    roles: `${UserRole}`[];
+}
diff --git a/src/components/auth/types/user.ts b/src/components/auth/types/user.ts
new file mode 100644
index 00000000..e66f6d26
--- /dev/null
+++ b/src/components/auth/types/user.ts
@@ -0,0 +1,8 @@
+import type {UserRole} from '../constants/role';
+
+export interface CtxUser {
+    userId: string;
+    sessionId: string;
+    accessToken: string;
+    roles: `${UserRole}`[];
+}
diff --git a/src/components/zod/custom-types/string-boolean.ts b/src/components/zod/custom-types/string-boolean.ts
index d3063d6f..498504fc 100644
--- a/src/components/zod/custom-types/string-boolean.ts
+++ b/src/components/zod/custom-types/string-boolean.ts
@@ -1,6 +1,5 @@
 import {z} from 'zod';
 
-import Utils from '../../../utils';
+import {isTrueArg} from '../../../utils/env-utils';
 
-export const stringBoolean = () =>
-    z.string().toLowerCase().transform(Utils.isTrueArg).pipe(z.boolean());
+export const stringBoolean = () => z.string().toLowerCase().transform(isTrueArg).pipe(z.boolean());
diff --git a/src/configs/common.ts b/src/configs/common.ts
index 2c4b8f59..e911b379 100644
--- a/src/configs/common.ts
+++ b/src/configs/common.ts
@@ -2,7 +2,11 @@ import {AuthPolicy} from '@gravity-ui/expresskit';
 import {AppConfig} from '@gravity-ui/nodekit';
 
 import {APP_NAME, US_MASTER_TOKEN_HEADER} from '../const';
-import Utils from '../utils';
+import {getEnvCert, getEnvTokenVariable, getEnvVariable, isTrueArg} from '../utils/env-utils';
+
+const isZitadelEnabled = isTrueArg(getEnvVariable('ZITADEL'));
+const isAuthEnabled = isTrueArg(getEnvVariable('AUTH_ENABLED'));
+const isAuthServiceEnabled = isZitadelEnabled || isAuthEnabled;
 
 export default {
     appName: APP_NAME,
@@ -18,30 +22,32 @@ export default {
         extended: false,
     },
 
-    appAuthPolicy: Utils.isTrueArg(Utils.getEnvVariable('ZITADEL'))
-        ? AuthPolicy.required
-        : AuthPolicy.disabled,
+    appAuthPolicy: isAuthServiceEnabled ? AuthPolicy.required : AuthPolicy.disabled,
 
     appSensitiveKeys: [US_MASTER_TOKEN_HEADER],
 
-    zitadelEnabled: Utils.isTrueArg(Utils.getEnvVariable('ZITADEL')),
-    zitadelUri: Utils.getEnvVariable('ZITADEL_URI') || 'http://localhost:8080',
+    // zitadel
+    zitadelEnabled: isZitadelEnabled,
+    zitadelUri: getEnvVariable('ZITADEL_URI') || 'http://localhost:8080',
+    clientId: getEnvVariable('CLIENT_ID') || '',
+    clientSecret: getEnvVariable('CLIENT_SECRET') || '',
 
-    clientId: Utils.getEnvVariable('CLIENT_ID') || '',
-    clientSecret: Utils.getEnvVariable('CLIENT_SECRET') || '',
+    // auth
+    isAuthEnabled: isAuthEnabled,
+    authTokenPublicKey: getEnvCert(process.env.AUTH_TOKEN_PUBLIC_KEY),
 
     multitenant: false,
     tenantIdOverride: 'common',
 
     dlsEnabled: false,
-    accessServiceEnabled: Utils.isTrueArg(Utils.getEnvVariable('ZITADEL')),
-    accessBindingsServiceEnabled: Utils.isTrueArg(Utils.getEnvVariable('ZITADEL')),
+    accessServiceEnabled: isAuthServiceEnabled,
+    accessBindingsServiceEnabled: isAuthServiceEnabled,
 
-    masterToken: Utils.getEnvTokenVariable('MASTER_TOKEN'),
+    masterToken: getEnvTokenVariable('MASTER_TOKEN'),
 
     features: {},
 
-    debug: Utils.isTrueArg(Utils.getEnvVariable('DEBUG')),
+    debug: isTrueArg(getEnvVariable('DEBUG')),
 
-    swaggerEnabled: !Utils.isTrueArg(Utils.getEnvVariable('DISABLE_SWAGGER')),
+    swaggerEnabled: !isTrueArg(getEnvVariable('DISABLE_SWAGGER')),
 } as Partial<AppConfig>;
diff --git a/src/controllers/entries/index.ts b/src/controllers/entries/index.ts
index af1666ba..aa21d812 100644
--- a/src/controllers/entries/index.ts
+++ b/src/controllers/entries/index.ts
@@ -32,7 +32,7 @@ import {
     formatGetEntryResponse,
 } from '../../services/new/entry/formatters';
 import * as ST from '../../types/services.types';
-import Utils from '../../utils';
+import {isTrueArg} from '../../utils/env-utils';
 
 import {getEntriesData} from './get-entries-data';
 
@@ -46,8 +46,8 @@ export default {
                 entryId: params.entryId,
                 branch: query.branch as GetEntryArgs['branch'],
                 revId: query.revId as GetEntryArgs['revId'],
-                includePermissionsInfo: Utils.isTrueArg(query.includePermissionsInfo),
-                includeLinks: Utils.isTrueArg(query.includeLinks),
+                includePermissionsInfo: isTrueArg(query.includePermissionsInfo),
+                includeLinks: isTrueArg(query.includeLinks),
             },
         );
         const formattedResponse = await formatGetEntryResponse(req.ctx, result);
@@ -133,7 +133,7 @@ export default {
             unversionedData: body.unversionedData,
             links: body.links,
             permissionsMode: body.permissionsMode,
-            includePermissionsInfo: Utils.isTrueArg(body.includePermissionsInfo),
+            includePermissionsInfo: isTrueArg(body.includePermissionsInfo),
             initialPermissions: body.initialPermissions,
             initialParentId: body.initialParentId,
             ctx: req.ctx,
@@ -246,7 +246,7 @@ export default {
             {
                 entryId: params.entryId,
                 direction: query.direction as Optional<RelationDirection>,
-                includePermissionsInfo: Utils.isTrueArg(query.includePermissionsInfo),
+                includePermissionsInfo: isTrueArg(query.includePermissionsInfo),
                 page: (query.page && Number(query.page)) as number | undefined,
                 pageSize: (query.pageSize && Number(query.pageSize)) as number | undefined,
                 scope: query.scope as EntryScope | undefined,
@@ -310,11 +310,11 @@ export default {
             filters: query.filters,
             page: query.page && Number(query.page),
             pageSize: query.pageSize && Number(query.pageSize),
-            includePermissionsInfo: Utils.isTrueArg(query.includePermissionsInfo),
-            ignoreWorkbookEntries: Utils.isTrueArg(query.ignoreWorkbookEntries),
-            includeData: Utils.isTrueArg(query.includeData),
-            includeLinks: Utils.isTrueArg(query.includeLinks),
-            excludeLocked: Utils.isTrueArg(query.excludeLocked),
+            includePermissionsInfo: isTrueArg(query.includePermissionsInfo),
+            ignoreWorkbookEntries: isTrueArg(query.ignoreWorkbookEntries),
+            includeData: isTrueArg(query.includeData),
+            includeLinks: isTrueArg(query.includeLinks),
+            excludeLocked: isTrueArg(query.excludeLocked),
             ctx: req.ctx,
         });
 
diff --git a/src/controllers/favorites.ts b/src/controllers/favorites.ts
index 8f827dd7..da3292a9 100644
--- a/src/controllers/favorites.ts
+++ b/src/controllers/favorites.ts
@@ -4,7 +4,7 @@ import {prepareResponseAsync} from '../components/response-presenter';
 import Entry from '../db/models/entry';
 import FavoriteService from '../services/favorite.service';
 import * as ST from '../types/services.types';
-import Utils from '../utils';
+import {isTrueArg} from '../utils/env-utils';
 
 export default {
     getFavorites: async (req: Request, res: Response) => {
@@ -16,8 +16,8 @@ export default {
             page: query.page && Number(query.page),
             pageSize: query.pageSize && Number(query.pageSize),
             scope: query.scope,
-            includePermissionsInfo: Utils.isTrueArg(query.includePermissionsInfo),
-            ignoreWorkbookEntries: Utils.isTrueArg(query.ignoreWorkbookEntries),
+            includePermissionsInfo: isTrueArg(query.includePermissionsInfo),
+            ignoreWorkbookEntries: isTrueArg(query.ignoreWorkbookEntries),
             ctx: req.ctx,
         });
 
diff --git a/src/controllers/structure-items.ts b/src/controllers/structure-items.ts
index 174155d0..40ade930 100644
--- a/src/controllers/structure-items.ts
+++ b/src/controllers/structure-items.ts
@@ -3,7 +3,7 @@ import {Request, Response} from '@gravity-ui/expresskit';
 import {prepareResponseAsync} from '../components/response-presenter';
 import {Mode, OrderDirection, OrderField, getStructureItems} from '../services/new/structure-item';
 import {formatStructureItems} from '../services/new/structure-item/formatters/format-structure-items';
-import Utils from '../utils';
+import {isTrueArg} from '../utils/env-utils';
 
 export default {
     getStructureItems: async (req: Request, res: Response) => {
@@ -13,13 +13,13 @@ export default {
             {ctx: req.ctx},
             {
                 collectionId: (query.collectionId as Optional<string>) ?? null,
-                includePermissionsInfo: Utils.isTrueArg(query.includePermissionsInfo),
+                includePermissionsInfo: isTrueArg(query.includePermissionsInfo),
                 filterString: query.filterString as Optional<string>,
                 page: query.page ? parseInt(query.page as string, 10) : undefined,
                 pageSize: query.pageSize ? parseInt(query.pageSize as string, 10) : undefined,
                 orderField: query.orderField as Optional<OrderField>,
                 orderDirection: query.orderDirection as Optional<OrderDirection>,
-                onlyMy: Utils.isTrueArg(query.onlyMy),
+                onlyMy: isTrueArg(query.onlyMy),
                 mode: query.mode as Optional<Mode>,
             },
         );
diff --git a/src/controllers/workbooks/index.ts b/src/controllers/workbooks/index.ts
index fee5598d..ca117a5f 100644
--- a/src/controllers/workbooks/index.ts
+++ b/src/controllers/workbooks/index.ts
@@ -23,7 +23,7 @@ import {
     formatWorkbooksList,
 } from '../../services/new/workbook/formatters';
 import {getWorkbooksListByIds} from '../../services/new/workbook/get-workbooks-list-by-ids';
-import Utils from '../../utils';
+import {isTrueArg} from '../../utils/env-utils';
 
 import {copyWorkbook} from './copy-workbook';
 import {createWorkbook} from './create-workbook';
@@ -51,7 +51,7 @@ export default {
             },
             {
                 workbookId: params.workbookId,
-                includePermissionsInfo: Utils.isTrueArg(query.includePermissionsInfo),
+                includePermissionsInfo: isTrueArg(query.includePermissionsInfo),
             },
         );
 
@@ -67,7 +67,7 @@ export default {
             {ctx: req.ctx},
             {
                 workbookId: params.workbookId,
-                includePermissionsInfo: Utils.isTrueArg(query.includePermissionsInfo),
+                includePermissionsInfo: isTrueArg(query.includePermissionsInfo),
                 page: (query.page && Number(query.page)) as number | undefined,
                 pageSize: (query.pageSize && Number(query.pageSize)) as number | undefined,
                 createdBy: query.createdBy as any,
@@ -89,13 +89,13 @@ export default {
             {ctx: req.ctx},
             {
                 collectionId: (query.collectionId as Optional<string>) ?? null,
-                includePermissionsInfo: Utils.isTrueArg(query.includePermissionsInfo),
+                includePermissionsInfo: isTrueArg(query.includePermissionsInfo),
                 filterString: query.filterString as Optional<string>,
                 page: (query.page && Number(query.page)) as number | undefined,
                 pageSize: (query.pageSize && Number(query.pageSize)) as number | undefined,
                 orderField: query.orderField as Optional<OrderField>,
                 orderDirection: query.orderDirection as Optional<OrderDirection>,
-                onlyMy: Utils.isTrueArg(query.onlyMy),
+                onlyMy: isTrueArg(query.onlyMy),
             },
         );
 
diff --git a/src/db/init-db.ts b/src/db/init-db.ts
index b085ed23..ee2fc66b 100644
--- a/src/db/init-db.ts
+++ b/src/db/init-db.ts
@@ -6,6 +6,7 @@ import {initDB as initPosgresDB} from '@gravity-ui/postgreskit';
 import {AppEnv, DEFAULT_QUERY_TIMEOUT} from '../const';
 import {getTestDsnList} from '../tests/int/db';
 import Utils from '../utils';
+import {isTrueArg} from '../utils/env-utils';
 
 interface OrigImplFunction {
     (snakeCaseFormat: string): string;
@@ -61,7 +62,7 @@ export const getKnexOptions = () => ({
 
         return origImpl(snakeCaseFormat);
     },
-    debug: Utils.isTrueArg(process.env.SQL_DEBUG),
+    debug: isTrueArg(process.env.SQL_DEBUG),
 });
 
 export function initDB(nodekit: NodeKit) {
@@ -72,7 +73,7 @@ export function initDB(nodekit: NodeKit) {
         dsnList = Utils.getDsnList();
     }
 
-    const suppressStatusLogs = Utils.isTrueArg(process.env.US_SURPRESS_DB_STATUS_LOGS);
+    const suppressStatusLogs = isTrueArg(process.env.US_SURPRESS_DB_STATUS_LOGS);
 
     const dispatcherOptions = {
         healthcheckInterval: 5000,
diff --git a/src/index.ts b/src/index.ts
index e60a8f72..f948a767 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -15,6 +15,7 @@ import {
     setCiEnv,
     waitDatabase,
 } from './components/middlewares';
+import {appAuth} from './components/auth/middlewares/app-auth';
 import {AppEnv} from './const';
 import {registry} from './registry';
 import {getRoutes} from './routes';
@@ -56,6 +57,10 @@ if (nodekit.config.zitadelEnabled) {
     nodekit.config.appAuthHandler = authZitadel;
 }
 
+if (nodekit.config.isAuthEnabled) {
+    nodekit.config.appAuthHandler = appAuth;
+}
+
 nodekit.config.appFinalErrorHandler = finalRequestHandler;
 
 const extendedRoutes = getRoutes(nodekit, {beforeAuth, afterAuth});
diff --git a/src/registry/common/components/iam/utils.ts b/src/registry/common/components/iam/utils.ts
index 9909ccdd..c604eec7 100644
--- a/src/registry/common/components/iam/utils.ts
+++ b/src/registry/common/components/iam/utils.ts
@@ -1,5 +1,6 @@
 import {AppContext, AppError} from '@gravity-ui/nodekit';
 
+import {UserRole} from '../../../../components/auth/constants/role';
 import {OrganizationPermission} from '../../../../components/iam';
 import {US_ERRORS} from '../../../../const';
 import {ZitadelUserRole} from '../../../../types/zitadel';
@@ -12,31 +13,78 @@ const throwAccessServicePermissionDenied = () => {
     });
 };
 
-export const checkOrganizationPermission: CheckOrganizationPermission = async (args: {
+const checkAuthOrganizationPermission: CheckOrganizationPermission = async (args: {
     ctx: AppContext;
     permission: OrganizationPermission;
 }) => {
     const {ctx, permission} = args;
-    const {zitadelUserRole: role} = ctx.get('info');
+    const roles = ctx.get('user')?.roles || [];
 
     switch (permission) {
         case OrganizationPermission.UseInstance:
             break;
 
-        case OrganizationPermission.ManageInstance:
-            if (role !== ZitadelUserRole.Admin) {
+        case OrganizationPermission.ManageInstance: {
+            if (roles.every((role) => role !== UserRole.Admin)) {
                 throwAccessServicePermissionDenied();
             }
             break;
+        }
 
         case OrganizationPermission.CreateCollectionInRoot:
-        case OrganizationPermission.CreateWorkbookInRoot:
-            if (role !== ZitadelUserRole.Editor && role !== ZitadelUserRole.Admin) {
+        case OrganizationPermission.CreateWorkbookInRoot: {
+            if (roles.every((role) => role !== UserRole.Editor && role !== UserRole.Admin)) {
                 throwAccessServicePermissionDenied();
             }
             break;
+        }
 
         default:
             throwAccessServicePermissionDenied();
     }
 };
+
+const checkZitadelOrganizationPermission: CheckOrganizationPermission = async (args: {
+    ctx: AppContext;
+    permission: OrganizationPermission;
+}) => {
+    const {ctx, permission} = args;
+    const {zitadelUserRole} = ctx.get('info');
+
+    switch (permission) {
+        case OrganizationPermission.UseInstance:
+            break;
+
+        case OrganizationPermission.ManageInstance: {
+            if (zitadelUserRole !== ZitadelUserRole.Admin) {
+                throwAccessServicePermissionDenied();
+            }
+            break;
+        }
+
+        case OrganizationPermission.CreateCollectionInRoot:
+        case OrganizationPermission.CreateWorkbookInRoot: {
+            if (
+                zitadelUserRole !== ZitadelUserRole.Editor &&
+                zitadelUserRole !== ZitadelUserRole.Admin
+            ) {
+                throwAccessServicePermissionDenied();
+            }
+            break;
+        }
+
+        default:
+            throwAccessServicePermissionDenied();
+    }
+};
+
+export const checkOrganizationPermission: CheckOrganizationPermission = async (args: {
+    ctx: AppContext;
+    permission: OrganizationPermission;
+}) => {
+    if (args.ctx.config.isAuthEnabled) {
+        await checkAuthOrganizationPermission(args);
+    } else {
+        await checkZitadelOrganizationPermission(args);
+    }
+};
diff --git a/src/registry/common/entities/collection/collection.ts b/src/registry/common/entities/collection/collection.ts
index 4b9dfbed..cb5b0225 100644
--- a/src/registry/common/entities/collection/collection.ts
+++ b/src/registry/common/entities/collection/collection.ts
@@ -1,6 +1,7 @@
 import type {AppContext} from '@gravity-ui/nodekit';
 import {AppError} from '@gravity-ui/nodekit';
 
+import {UserRole} from '../../../../components/auth/constants/role';
 import {US_ERRORS} from '../../../../const';
 import type {CollectionModel} from '../../../../db/models/new/collection';
 import {CollectionPermission, Permissions} from '../../../../entities/collection/types';
@@ -76,8 +77,15 @@ export const Collection: CollectionConstructor = class Collection implements Col
     }
 
     private isEditorOrAdmin() {
-        const {zitadelUserRole: role} = this.ctx.get('info');
-        return role === ZitadelUserRole.Editor || role === ZitadelUserRole.Admin;
+        const {isAuthEnabled} = this.ctx.config;
+        const user = this.ctx.get('user');
+        const {zitadelUserRole} = this.ctx.get('info');
+        return isAuthEnabled
+            ? (user?.roles || []).some(
+                  (role) => role === UserRole.Editor || role === UserRole.Admin,
+              )
+            : zitadelUserRole === ZitadelUserRole.Editor ||
+                  zitadelUserRole === ZitadelUserRole.Admin;
     }
 
     private getAllPermissions() {
diff --git a/src/registry/common/entities/workbook/workbook.ts b/src/registry/common/entities/workbook/workbook.ts
index b241c0c1..86ce3563 100644
--- a/src/registry/common/entities/workbook/workbook.ts
+++ b/src/registry/common/entities/workbook/workbook.ts
@@ -1,6 +1,7 @@
 import type {AppContext} from '@gravity-ui/nodekit';
 import {AppError} from '@gravity-ui/nodekit';
 
+import {UserRole} from '../../../../components/auth/constants/role';
 import {US_ERRORS} from '../../../../const';
 import type {WorkbookModel} from '../../../../db/models/new/workbook';
 import {getMockedOperation} from '../../../../entities/utils';
@@ -78,8 +79,15 @@ export const Workbook: WorkbookConstructor<WorkbookInstance> = class Workbook
     }
 
     private isEditorOrAdmin() {
-        const {zitadelUserRole: role} = this.ctx.get('info');
-        return role === ZitadelUserRole.Editor || role === ZitadelUserRole.Admin;
+        const {isAuthEnabled} = this.ctx.config;
+        const user = this.ctx.get('user');
+        const {zitadelUserRole} = this.ctx.get('info');
+        return isAuthEnabled
+            ? (user?.roles || []).some(
+                  (role) => role === UserRole.Editor || role === UserRole.Admin,
+              )
+            : zitadelUserRole === ZitadelUserRole.Editor ||
+                  zitadelUserRole === ZitadelUserRole.Admin;
     }
 
     private getAllPermissions() {
diff --git a/src/utils/env-utils.ts b/src/utils/env-utils.ts
new file mode 100644
index 00000000..4ba138a8
--- /dev/null
+++ b/src/utils/env-utils.ts
@@ -0,0 +1,40 @@
+import fs from 'node:fs';
+
+import {TRUE_FLAGS} from '../const/common';
+
+export const getEnvCert = (envCert?: string) => envCert?.replace(/\\n/g, '\n');
+
+export function getEnvVariable(envVariableName: string) {
+    const valueFromEnv = process.env[envVariableName];
+    if (valueFromEnv) {
+        return valueFromEnv;
+    }
+    const FILE_PATH_POSTFIX = '_FILE_PATH';
+    const filePath = process.env[`${envVariableName}${FILE_PATH_POSTFIX}`];
+    if (filePath) {
+        return fs.readFileSync(filePath, 'utf8').toString();
+    }
+    return undefined;
+}
+
+export function getEnvTokenVariable(envTokenVariableName: string) {
+    const TOKEN_SEPARATOR = ',';
+    const valueFromEnv = getEnvVariable(envTokenVariableName);
+
+    if (!valueFromEnv) {
+        return undefined;
+    }
+
+    if (valueFromEnv.includes(TOKEN_SEPARATOR)) {
+        return valueFromEnv
+            .split(TOKEN_SEPARATOR)
+            .map((token) => token && token.trim())
+            .filter((token) => token);
+    }
+
+    return [valueFromEnv.trim()];
+}
+
+export function isTrueArg(arg: any): boolean {
+    return TRUE_FLAGS.includes(arg);
+}
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index 367c7561..29a42c2e 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -309,6 +309,7 @@ export class Utils {
         );
     }
 
+    /** @deprecated moved to env-utils */
     static isTrueArg(arg: any): boolean {
         return TRUE_FLAGS.includes(arg);
     }
@@ -370,6 +371,7 @@ export class Utils {
         return response && response.body;
     }
 
+    /** @deprecated moved to env-utils */
     static getEnvVariable(envVariableName: string) {
         const valueFromEnv = process.env[envVariableName];
         if (valueFromEnv) {
@@ -383,6 +385,7 @@ export class Utils {
         return undefined;
     }
 
+    /** @deprecated moved to env-utils */
     static getEnvTokenVariable(envTokenVariableName: string) {
         const TOKEN_SEPARATOR = ',';
         const valueFromEnv = Utils.getEnvVariable(envTokenVariableName);
diff --git a/typings/nodekit.d.ts b/typings/nodekit.d.ts
index 424cf554..b48018fc 100644
--- a/typings/nodekit.d.ts
+++ b/typings/nodekit.d.ts
@@ -1,5 +1,6 @@
 import type {RouteConfig as ZodOpenApiRouteConfig} from '@asteasolutions/zod-to-openapi';
 
+import type {CtxUser} from '../src/components/auth/types/user';
 import {FeaturesConfig} from '../src/components/features/types';
 import type {Registry} from '../src/registry';
 import {CtxInfo} from '../src/types/ctx';
@@ -17,14 +18,26 @@ export interface SharedAppConfig {
 
     masterToken: string[];
 
+    // zitadel
     zitadelEnabled?: boolean;
     zitadelUri?: string;
     clientId?: string;
     clientSecret?: string;
 
+    // auth
+    isAuthEnabled?: boolean;
+    authTokenPublicKey?: string;
+
     swaggerEnabled?: boolean;
 }
 
+export interface SharedAppContextParams {
+    info: CtxInfo;
+    registry: Registry;
+    // auth
+    user?: CtxUser;
+}
+
 declare module '@gravity-ui/nodekit' {
     export interface AppConfig extends SharedAppConfig {}
 
@@ -32,10 +45,7 @@ declare module '@gravity-ui/nodekit' {
         features?: FeaturesConfig;
     }
 
-    interface AppContextParams {
-        info: CtxInfo;
-        registry: Registry;
-    }
+    export interface AppContextParams extends SharedAppContextParams {}
 }
 
 declare module '@gravity-ui/expresskit' {