diff --git a/backend/internal/mfa.js b/backend/internal/mfa.js
new file mode 100644
index 000000000..79f11bf75
--- /dev/null
+++ b/backend/internal/mfa.js
@@ -0,0 +1,97 @@
+const authModel = require('../models/auth');
+const error     = require('../lib/error');
+const speakeasy = require('speakeasy');
+
+module.exports = {
+	validateMfaTokenForUser: (userId, token) => {
+		return authModel
+			.query()
+			.where('user_id', userId)
+			.first()
+			.then((auth) => {
+				if (!auth || !auth.mfa_enabled) {
+					throw new error.AuthError('MFA is not enabled for this user.');
+				}
+				const verified = speakeasy.totp.verify({
+					secret:   auth.mfa_secret,
+					encoding: 'base32',
+					token:    token,
+					window:   2
+				});
+				if (!verified) {
+					throw new error.AuthError('Invalid MFA token.');
+				}
+				return true;
+			});
+	},
+	isMfaEnabledForUser: (userId) => {
+		return authModel
+			.query()
+			.where('user_id', userId)
+			.first()
+			.then((auth) => {
+				console.log(auth);
+				if (!auth) {
+					throw new error.AuthError('User not found.');
+				}
+				return auth.mfa_enabled === true;
+			});
+	},
+	createMfaSecretForUser: (userId) => {
+		const secret = speakeasy.generateSecret({ length: 20 });
+		console.log(secret);
+		return authModel
+			.query()
+			.where('user_id', userId)
+			.update({
+				mfa_secret: secret.base32
+			})
+			.then(() => secret);
+	},
+	enableMfaForUser: (userId, token) => {
+		return authModel
+			.query()
+			.where('user_id', userId)
+			.first()
+			.then((auth) => {
+				if (!auth || !auth.mfa_secret) {
+					throw new error.AuthError('MFA is not set up for this user.');
+				}
+				const verified = speakeasy.totp.verify({
+					secret:   auth.mfa_secret,
+					encoding: 'base32',
+					token:    token,
+					window:   2
+				});
+				if (!verified) {
+					throw new error.AuthError('Invalid MFA token.');
+				}
+				return authModel
+					.query()
+					.where('user_id', userId)
+					.update({ mfa_enabled: true })
+					.then(() => true);
+			});
+	},
+	disableMfaForUser: (data, userId) => {
+		return authModel
+			.query()
+			.where('user_id', userId)
+			.first()
+			.then((auth) => {
+				if (!auth) {
+					throw new error.AuthError('User not found.');
+				}
+				return auth.verifyPassword(data.secret)
+					.then((valid) => {
+						if (!valid) {
+							throw new error.AuthError('Invalid password.');
+						}
+						return authModel
+							.query()
+							.where('user_id', userId)
+							.update({ mfa_enabled: false, mfa_secret: null });
+					});
+			});
+	},
+};
diff --git a/backend/internal/token.js b/backend/internal/token.js
index 0e6dec5e3..04cf9dd81 100644
--- a/backend/internal/token.js
+++ b/backend/internal/token.js
@@ -4,6 +4,7 @@ const userModel  = require('../models/user');
 const authModel  = require('../models/auth');
 const helpers    = require('../lib/helpers');
 const TokenModel = require('../models/token');
+const mfa        = require('../internal/mfa'); // <-- added MFA import
 
 const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
 
@@ -21,6 +22,8 @@ module.exports = {
 	getTokenFromEmail: (data, issuer) => {
 		let Token = new TokenModel();
 
+		console.log(data);
+
 		data.scope  = data.scope || 'user';
 		data.expiry = data.expiry || '1d';
 
@@ -41,34 +44,66 @@ module.exports = {
 						.then((auth) => {
 							if (auth) {
 								return auth.verifyPassword(data.secret)
-									.then((valid) => {
+									.then(async (valid) => {
 										if (valid) {
-
 											if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) {
-												// The scope requested doesn't exist as a role against the user,
-												// you shall not pass.
 												throw new error.AuthError('Invalid scope: ' + data.scope);
 											}
-
-											// Create a moment of the expiry expression
-											let expiry = helpers.parseDatePeriod(data.expiry);
-											if (expiry === null) {
-												throw new error.AuthError('Invalid expiry time: ' + data.expiry);
-											}
-
-											return Token.create({
-												iss:   issuer || 'api',
-												attrs: {
-													id: user.id
-												},
-												scope:     [data.scope],
-												expiresIn: data.expiry
-											})
-												.then((signed) => {
-													return {
-														token:   signed.token,
-														expires: expiry.toISOString()
-													};
+											return await mfa.isMfaEnabledForUser(user.id)
+												.then((mfaEnabled) => {
+													if (mfaEnabled) {
+														if (!data.mfa_token) {
+															throw new error.AuthError('MFA token required');
+														}
+														console.log(data.mfa_token);
+														return mfa.validateMfaTokenForUser(user.id, data.mfa_token)
+															.then((mfaValid) => {
+																if (!mfaValid) {
+																	throw new error.AuthError('Invalid MFA token');
+																}
+																// Create a moment of the expiry expression
+																let expiry = helpers.parseDatePeriod(data.expiry);
+																if (expiry === null) {
+																	throw new error.AuthError('Invalid expiry time: ' + data.expiry);
+																}
+
+																return Token.create({
+																	iss:   issuer || 'api',
+																	attrs: {
+																		id: user.id
+																	},
+																	scope:     [data.scope],
+																	expiresIn: data.expiry
+																})
+																	.then((signed) => {
+																		return {
+																			token:   signed.token,
+																			expires: expiry.toISOString()
+																		};
+																	});
+															});
+													} else {
+														// Create a moment of the expiry expression
+														let expiry = helpers.parseDatePeriod(data.expiry);
+														if (expiry === null) {
+															throw new error.AuthError('Invalid expiry time: ' + data.expiry);
+														}
+
+														return Token.create({
+															iss:   issuer || 'api',
+															attrs: {
+																id: user.id
+															},
+															scope:     [data.scope],
+															expiresIn: data.expiry
+														})
+															.then((signed) => {
+																return {
+																	token:   signed.token,
+																	expires: expiry.toISOString()
+																};
+															});
+													}
 												});
 										} else {
 											throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
diff --git a/backend/internal/user.js b/backend/internal/user.js
index 742ab65d3..3a28a8ce1 100644
--- a/backend/internal/user.js
+++ b/backend/internal/user.js
@@ -507,7 +507,8 @@ const internalUser = {
 			.then((user) => {
 				return internalToken.getTokenFromUser(user);
 			});
-	}
+	},
+
 };
 
 module.exports = internalUser;
diff --git a/backend/migrations/20250115041439_mfa_integeration.js b/backend/migrations/20250115041439_mfa_integeration.js
new file mode 100644
index 000000000..ed90495e5
--- /dev/null
+++ b/backend/migrations/20250115041439_mfa_integeration.js
@@ -0,0 +1,45 @@
+const migrate_name = 'identifier_for_migrate';
+const logger       = require('../logger').migrate;
+
+/**
+ * Migrate
+ *
+ * @see http://knexjs.org/#Schema
+ *
+ * @param {Object} knex
+ * @param {Promise} Promise
+ * @returns {Promise}
+ */
+exports.up = function (knex/*, Promise*/) {
+
+	logger.info('[' + migrate_name + '] Migrating Up...');
+
+	return knex.schema.alterTable('auth', (table) => {
+		table.string('mfa_secret');
+		table.boolean('mfa_enabled').defaultTo(false);
+	})
+		.then(() => {
+			logger.info('[' + migrate_name + '] User Table altered');
+			logger.info('[' + migrate_name + '] Migrating Up Complete');
+		});
+};
+
+/**
+ * Undo Migrate
+ *
+ * @param {Object} knex
+ * @param {Promise} Promise
+ * @returns {Promise}
+ */
+exports.down = function (knex/*, Promise*/) {
+	logger.info('[' + migrate_name + '] Migrating Down...');
+
+	return knex.schema.alterTable('auth', (table) => {
+		table.dropColumn('mfa_key');
+		table.dropColumn('mfa_enabled');
+	})
+		.then(() => {
+			logger.info('[' + migrate_name + '] User Table altered');
+			logger.info('[' + migrate_name + '] Migrating Down Complete');
+		});
+};
diff --git a/backend/package.json b/backend/package.json
index 30984a332..1b835844d 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -23,8 +23,10 @@
 		"node-rsa": "^1.0.8",
 		"objection": "3.0.1",
 		"path": "^0.12.7",
+		"qrcode": "^1.5.4",
 		"pg": "^8.13.1",
 		"signale": "1.4.0",
+		"speakeasy": "^2.0.0",
 		"sqlite3": "5.1.6",
 		"temp-write": "^4.0.0"
 	},
diff --git a/backend/routes/main.js b/backend/routes/main.js
index b97096d0e..09e1bf760 100644
--- a/backend/routes/main.js
+++ b/backend/routes/main.js
@@ -27,6 +27,7 @@ router.get('/', (req, res/*, next*/) => {
 
 router.use('/schema', require('./schema'));
 router.use('/tokens', require('./tokens'));
+router.use('/mfa', require('./mfa'));
 router.use('/users', require('./users'));
 router.use('/audit-log', require('./audit-log'));
 router.use('/reports', require('./reports'));
diff --git a/backend/routes/mfa.js b/backend/routes/mfa.js
new file mode 100644
index 000000000..100cda1eb
--- /dev/null
+++ b/backend/routes/mfa.js
@@ -0,0 +1,81 @@
+const express      = require('express');
+const jwtdecode    = require('../lib/express/jwt-decode');
+const apiValidator = require('../lib/validator/api');
+const schema       = require('../schema');
+const internalMfa  = require('../internal/mfa');
+const qrcode       = require('qrcode');
+const speakeasy    = require('speakeasy');
+const userModel    = require('../models/user');
+
+let router = express.Router({
+	caseSensitive: true,
+	strict:        true,
+	mergeParams:   true
+});
+
+router
+	.route('/create')
+	.post(jwtdecode(), (req, res, next) => {
+		if (!res.locals.access) {
+			return next(new Error('Invalid token'));
+		}
+		const userId = res.locals.access.token.getUserId();
+		internalMfa.createMfaSecretForUser(userId)
+			.then((secret) => {
+				return userModel.query()
+					.where('id', '=', userId)
+					.first()
+					.then((user) => {
+						if (!user) {
+							return next(new Error('User not found'));
+						}
+						return { secret, user };
+					});
+			})
+			.then(({ secret, user }) => {
+				const otpAuthUrl = speakeasy.otpauthURL({
+					secret: secret.ascii,
+					label:  user.email,
+					issuer: 'Nginx Proxy Manager'
+				});
+				qrcode.toDataURL(otpAuthUrl, (err, dataUrl) => {
+					if (err) {
+						console.error('Error generating QR code:', err);
+						return next(err);
+					}
+					res.status(200).send({ qrCode: dataUrl });
+				});
+			})
+			.catch(next);
+	});
+
+router
+	.route('/enable')
+	.post(jwtdecode(), (req, res, next) => {
+		apiValidator(schema.getValidationSchema('/mfa/enable', 'post'), req.body).then((params) => {
+			internalMfa.enableMfaForUser(res.locals.access.token.getUserId(), params.token)
+				.then(() => res.status(200).send({ success: true }))
+				.catch(next);
+		}
+		).catch(next);
+	});
+
+router
+	.route('/check')
+	.get(jwtdecode(), (req, res, next) => {
+		internalMfa.isMfaEnabledForUser(res.locals.access.token.getUserId())
+			.then((active) => res.status(200).send({ active }))
+			.catch(next);
+	});
+
+router
+	.route('/delete')
+	.delete(jwtdecode(), (req, res, next) => {
+		apiValidator(schema.getValidationSchema('/mfa/delete', 'delete'), req.body).then((params) => {
+			internalMfa.disableMfaForUser(params, res.locals.access.token.getUserId())
+				.then(() => res.status(200).send({ success: true }))
+				.catch(next);
+		}).catch(next);
+	});
+
+module.exports = router;
diff --git a/backend/schema/paths/mfa/delete/delete.json b/backend/schema/paths/mfa/delete/delete.json
new file mode 100644
index 000000000..9b9b14153
--- /dev/null
+++ b/backend/schema/paths/mfa/delete/delete.json
@@ -0,0 +1,44 @@
+{
+    "operationId": "disableMfa",
+    "summary": "Disable multi-factor authentication for a user",
+    "tags": [
+        "MFA"
+    ],
+    "requestBody": {
+        "description": "Payload to disable MFA",
+        "required": true,
+        "content": {
+            "application/json": {
+                "schema": {
+                    "additionalProperties": false,
+                    "properties": {
+                        "secret": {
+                            "type": "string",
+                            "minLength": 1
+                        }
+                    },
+                    "required": [
+                        "secret"
+                    ]
+                }
+            }
+        }
+    },
+    "responses": {
+        "200": {
+            "description": "MFA disabled successfully",
+            "content": {
+                "application/json": {
+                    "schema": {
+                        "type": "object",
+                        "properties": {
+                            "success": {
+                                "type": "boolean"
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/backend/schema/paths/mfa/enable/post.json b/backend/schema/paths/mfa/enable/post.json
new file mode 100644
index 000000000..33228781e
--- /dev/null
+++ b/backend/schema/paths/mfa/enable/post.json
@@ -0,0 +1,44 @@
+{
+	"operationId": "enableMfa",
+	"summary": "Enable multi-factor authentication for a user",
+	"tags": [
+		"MFA"
+	],
+	"requestBody": {
+		"description": "MFA Token Payload",
+		"required": true,
+		"content": {
+			"application/json": {
+				"schema": {
+					"additionalProperties": false,
+					"properties": {
+						"token": {
+							"type": "string",
+							"minLength": 1
+						}
+					},
+					"required": [
+						"token"
+					]
+				}
+			}
+		}
+	},
+	"responses": {
+		"200": {
+			"description": "MFA enabled successfully",
+			"content": {
+				"application/json": {
+					"schema": {
+						"type": "object",
+						"properties": {
+							"success": {
+								"type": "boolean"
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/backend/schema/paths/tokens/post.json b/backend/schema/paths/tokens/post.json
index 99703ff0d..35287f725 100644
--- a/backend/schema/paths/tokens/post.json
+++ b/backend/schema/paths/tokens/post.json
@@ -22,6 +22,10 @@
 						"secret": {
 							"minLength": 1,
 							"type": "string"
+						},
+						"mfa_token": {
+							"minLength": 1,
+							"type": "string"
 						}
 					},
 					"required": ["identity", "secret"],
diff --git a/backend/schema/paths/users/post.json b/backend/schema/paths/users/post.json
index c0213fe05..15aeb9f4d 100644
--- a/backend/schema/paths/users/post.json
+++ b/backend/schema/paths/users/post.json
@@ -14,7 +14,7 @@
 			"application/json": {
 				"schema": {
 					"type": "object",
-					"additionalProperties": false,
+					"additionalProperties": true,
 					"required": ["name", "nickname", "email"],
 					"properties": {
 						"name": {
diff --git a/backend/schema/swagger.json b/backend/schema/swagger.json
index 5a0142bff..b7f2b9a3d 100644
--- a/backend/schema/swagger.json
+++ b/backend/schema/swagger.json
@@ -15,6 +15,16 @@
 				"$ref": "./paths/get.json"
 			}
 		},
+		"/mfa/enable": {
+			"post": {
+				"$ref": "./paths/mfa/enable/post.json"
+			}
+		},
+		"/mfa/delete": {
+			"delete": {
+				"$ref": "./paths/mfa/delete/delete.json"
+			}
+		},
 		"/audit-log": {
 			"get": {
 				"$ref": "./paths/audit-log/get.json"
diff --git a/backend/yarn.lock b/backend/yarn.lock
index cea8210bc..55723d375 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -2735,67 +2735,11 @@ path@^0.12.7:
     process "^0.11.1"
     util "^0.10.3"
 
-pg-cloudflare@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98"
-  integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==
-
 pg-connection-string@2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
   integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
 
-pg-connection-string@^2.7.0:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37"
-  integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==
-
-pg-int8@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
-  integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
-
-pg-pool@^3.7.0:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec"
-  integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==
-
-pg-protocol@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93"
-  integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==
-
-pg-types@^2.1.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
-  integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
-  dependencies:
-    pg-int8 "1.0.1"
-    postgres-array "~2.0.0"
-    postgres-bytea "~1.0.0"
-    postgres-date "~1.0.4"
-    postgres-interval "^1.1.0"
-
-pg@^8.13.1:
-  version "8.13.1"
-  resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.1.tgz#6498d8b0a87ff76c2df7a32160309d3168c0c080"
-  integrity sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==
-  dependencies:
-    pg-connection-string "^2.7.0"
-    pg-pool "^3.7.0"
-    pg-protocol "^1.7.0"
-    pg-types "^2.1.0"
-    pgpass "1.x"
-  optionalDependencies:
-    pg-cloudflare "^1.1.1"
-
-pgpass@1.x:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"
-  integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==
-  dependencies:
-    split2 "^4.1.0"
-
 picomatch@^2.0.4, picomatch@^2.2.1:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
@@ -2814,28 +2758,6 @@ pkg-conf@^2.1.0:
     find-up "^2.0.0"
     load-json-file "^4.0.0"
 
-postgres-array@~2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
-  integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==
-
-postgres-bytea@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
-  integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==
-
-postgres-date@~1.0.4:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8"
-  integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==
-
-postgres-interval@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
-  integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==
-  dependencies:
-    xtend "^4.0.0"
-
 prelude-ls@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -3272,11 +3194,6 @@ socks@^2.6.2:
     ip "^2.0.0"
     smart-buffer "^4.2.0"
 
-split2@^4.1.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
-  integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
-
 sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -3748,11 +3665,6 @@ xdg-basedir@^4.0.0:
   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
   integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
 
-xtend@^4.0.0:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
-  integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
-
 y18n@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
diff --git a/frontend/js/app/api.js b/frontend/js/app/api.js
index 6e33a6dca..12497a6c7 100644
--- a/frontend/js/app/api.js
+++ b/frontend/js/app/api.js
@@ -202,7 +202,49 @@ module.exports = {
         return fetch('get', '');
     },
 
+    Mfa: {
+        create: function () {
+            return fetch('post', 'mfa/create');
+        },
+        enable: function (token) {
+            return fetch('post', 'mfa/enable', {token: token});
+        },
+        check: function () {
+            return fetch('get', 'mfa/check');
+        },
+        delete: function (secret) {
+            return fetch('delete', 'mfa/delete', {secret: secret});
+        }
+    },
+
     Tokens: {
+        
+        /**
+         * 
+         * @param {String} identity 
+         * @param {String} secret 
+         * @param {String} token 
+         * @param {Boolean} wipe 
+         * @returns {Promise}
+         */
+
+        loginWithMFA: function (identity, secret, mfaToken, wipe) {
+            return fetch('post', 'tokens', {identity: identity, secret: secret, mfa_token: mfaToken})
+                .then(response => {
+                    if (response.token) {
+                        if (wipe) {
+                            Tokens.clearTokens();
+                        }
+
+                        // Set storage token
+                        Tokens.addToken(response.token);
+                        return response.token;
+                    } else {
+                        Tokens.clearTokens();
+                        throw(new Error('No token returned'));
+                    }
+                });
+        },
 
         /**
          * @param   {String}  identity
diff --git a/frontend/js/app/user/form.ejs b/frontend/js/app/user/form.ejs
index 9ba84438a..1e7bf9ed5 100644
--- a/frontend/js/app/user/form.ejs
+++ b/frontend/js/app/user/form.ejs
@@ -25,6 +25,27 @@
                         <div class="invalid-feedback secret-error"></div>
                     </div>
                 </div>
+
+                <div class="col-sm-12 col-md-12">
+                    <label class="form-label mfa-label"><%- i18n('mfa', 'mfa') %></label>
+                    <button type="button" class="btn btn-info mfa-add"><%- i18n('mfa', 'mfa-add') %></button>
+                    <button type="button" class="btn btn-danger mfa-remove" style="display: none;"><%- i18n('mfa', 'mfa-remove') %></button>
+                    <div class="mfa-remove-confirm-container" style="display: none;">
+                        <div class="form-group">
+                            <label class="form-label"><%- i18n('mfa', 'confirm-password') %></label>
+                            <input name="mfa_password" type="password" class="form-control mfa-remove-password-field" placeholder="<%- i18n('mfa', 'enter-password') %>">
+                            <div class="invalid-feedback mfa-error"></div>
+                        </div>
+                        <button type="button" class="btn btn-danger mfa-remove-confirm"><%- i18n('mfa', 'confirm-remove-mfa') %></button>
+                    </div>
+                    <p class="qr-instructions" style="display: none;"><%- i18n('mfa', 'mfa-setup-instruction') %></p>
+                    <div class="mfa-validation-container" style="display: none;">
+                        <label class="form-label"><%- i18n('mfa', 'mfa-token') %> <span class="form-required">*</span></label>
+                        <input name="mfa_validation" type="text" class="form-control" placeholder="000000" value="">
+                        <div class="invalid-feedback mfa-error"></div>
+                    </div>
+                </div>
+
                 <% if (isAdmin() && !isSelf()) { %>
                 <div class="col-sm-12 col-md-12">
                     <div class="form-label"><%- i18n('roles', 'title') %></div>
diff --git a/frontend/js/app/user/form.js b/frontend/js/app/user/form.js
index 617a75fc9..157474822 100644
--- a/frontend/js/app/user/form.js
+++ b/frontend/js/app/user/form.js
@@ -14,7 +14,15 @@ module.exports = Mn.View.extend({
         buttons: '.modal-footer button',
         cancel:  'button.cancel',
         save:    'button.save',
-        error:   '.secret-error'
+        error:   '.secret-error',
+        mfaError: '.mfa-error',
+        addMfa:  '.mfa-add',
+        mfaValidation: '.mfa-validation-container',
+        qrInstructions: '.qr-instructions',
+        removeMfa: '.mfa-remove',
+        removeMfaConfirmContainer: '.mfa-remove-confirm-container',
+        removeMfaConfirm: '.mfa-remove-confirm',
+        removeMfaPassword: '.mfa-remove-password-field'
     },
 
     events: {
@@ -25,6 +33,10 @@ module.exports = Mn.View.extend({
             let view = this;
             let data = this.ui.form.serializeJSON();
 
+            let mfaToken = data.mfa_validation;
+            delete data.mfa_validation;
+            delete data.mfa_password;
+
             let show_password = this.model.get('email') === 'admin@example.com';
 
             // admin@example.com is not allowed
@@ -62,6 +74,19 @@ module.exports = Mn.View.extend({
                     }
 
                     view.model.set(result);
+
+                    if (mfaToken) {
+                        return App.Api.Mfa.enable(mfaToken)
+                            .then(() => result)
+                            .catch(err => {
+                                view.ui.mfaError.text(err.message).show();
+                                err.mfaHandled = true;
+                                return Promise.reject(err);
+                            });
+                    }
+                    return result;
+                })
+                .then(result => {
                     App.UI.closeModal(function () {
                         if (method === App.Api.Users.create) {
                             // Show permissions dialog immediately
@@ -72,9 +97,50 @@ module.exports = Mn.View.extend({
                     });
                 })
                 .catch(err => {
-                    this.ui.error.text(err.message).show();
+                    if (!err.mfaHandled) {
+                        this.ui.error.text(err.message).show();
+                    }
                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
                 });
+        },
+        'click @ui.addMfa': function (e) {
+            let view = this;
+            App.Api.Mfa.create()
+                .then(response => {
+                    view.ui.addMfa.replaceWith(`<img class="qr-code" src="${response.qrCode}" alt="QR Code">`);
+                    view.ui.qrInstructions.show();
+                    view.ui.mfaValidation.show();
+                    // Add required attribute once MFA is activated
+                    view.ui.mfaValidation.find('input[name="mfa_validation"]').attr('required', true);
+                })
+                .catch(err => {
+                    view.ui.error.text(err.message).show();
+                });
+        },
+        'click @ui.removeMfa': function (e) {
+            // Show confirmation section with a password field and confirm button
+            this.ui.removeMfa.hide();
+            this.ui.removeMfaConfirmContainer.show();
+        },
+        'click @ui.removeMfaConfirm': function (e) {
+            let view = this;
+            let password = view.ui.removeMfaPassword.val();
+            if (!password) {
+                view.ui.error.text('Password required to remove MFA').show();
+                return;
+            }
+            App.Api.Mfa.delete(password)
+                .then(() => {
+                    view.ui.addMfa.show();
+                    view.ui.qrInstructions.hide();
+                    view.ui.mfaValidation.hide();
+                    view.ui.removeMfaConfirmContainer.hide();
+                    view.ui.removeMfa.hide();
+                    view.ui.mfaValidation.find('input[name="mfa_validation"]').removeAttr('required');
+                })
+                .catch(err => {
+                    view.ui.mfaError.text(err.message).show();
+                });
         }
     },
 
@@ -104,5 +170,30 @@ module.exports = Mn.View.extend({
         if (typeof options.model === 'undefined' || !options.model) {
             this.model = new UserModel.Model();
         }
+    },
+
+    onRender: function () {
+        let view = this;
+        App.Api.Mfa.check()
+            .then(response => {
+                if (response.active) {
+                    view.ui.addMfa.hide();
+                    view.ui.qrInstructions.hide();
+                    view.ui.mfaValidation.hide();
+                    view.ui.removeMfa.show();
+                    view.ui.removeMfaConfirmContainer.hide();
+                    view.ui.mfaValidation.find('input[name="mfa_validation"]').removeAttr('required');
+                } else {
+                    view.ui.addMfa.show();
+                    view.ui.qrInstructions.hide();
+                    view.ui.mfaValidation.hide();
+                    view.ui.removeMfa.hide();
+                    view.ui.removeMfaConfirmContainer.hide();
+                    view.ui.mfaValidation.find('input[name="mfa_validation"]').removeAttr('required');
+                }
+            })
+            .catch(err => {
+                view.ui.error.text(err.message).show();
+            });
     }
 });
diff --git a/frontend/js/i18n/messages.json b/frontend/js/i18n/messages.json
index a154921b5..adb57b4e4 100644
--- a/frontend/js/i18n/messages.json
+++ b/frontend/js/i18n/messages.json
@@ -37,8 +37,19 @@
       "all": "All",
       "any": "Any"
     },
+    "mfa": {
+      "mfa": "Multi Factor Authentication",
+      "mfa-add": "Add Multi Factor Authentication",
+      "mfa-remove": "Remove Multi Factor Authentication",
+      "mfa-setup-instruction": "Scan this QR code in your authenticator app to set up MFA and then enter the current MFA code in the input field.",
+      "mfa-token": "Multi factor authentication token",
+      "confirm-password": "Please enter your password to confirm",
+      "enter-password": "Enter Password",
+      "confirm-remove-mfa": "Confirm Multi Factor Authentication removal"
+    },
     "login": {
-      "title": "Login to your account"
+      "title": "Login to your account",
+      "mfa-required-text": "Please enter your MFA token to continue"
     },
     "main": {
       "app": "Nginx Proxy Manager",
diff --git a/frontend/js/login/ui/login.ejs b/frontend/js/login/ui/login.ejs
index 693bc050c..ec15a82ce 100644
--- a/frontend/js/login/ui/login.ejs
+++ b/frontend/js/login/ui/login.ejs
@@ -1,3 +1,4 @@
+
 <div class="container">
     <div class="row">
         <div class="col col-login mx-auto">
@@ -24,6 +25,12 @@
                                     <input name="secret" type="password" class="form-control" placeholder="<%- i18n('str', 'password') %>" required>
                                     <div class="invalid-feedback secret-error"></div>
                                 </div>
+                                <div class="form-group mfa-group" style="display: none;">
+                                    <p class="mfa-info"><%- i18n('login', 'mfa-required-text') %>:</p>
+                                    <label class="form-label"><%- i18n('mfa', 'mfa-token') %></label>
+                                    <input name="mfa_token" type="text" class="form-control" placeholder="<%- i18n('mfa', 'mfa-token') %>">
+                                    <div class="invalid-feedback mfa-error"></div>
+                                </div>
                                 <div class="form-footer">
                                     <button type="submit" class="btn btn-teal btn-block"><%- i18n('str', 'sign-in') %></button>
                                 </div>
@@ -34,4 +41,4 @@
             </form>
         </div>
     </div>
-</div>
+</div>
\ No newline at end of file
diff --git a/frontend/js/login/ui/login.js b/frontend/js/login/ui/login.js
index 757eb4e31..bbce82040 100644
--- a/frontend/js/login/ui/login.js
+++ b/frontend/js/login/ui/login.js
@@ -13,7 +13,11 @@ module.exports = Mn.View.extend({
         identity: 'input[name="identity"]',
         secret:   'input[name="secret"]',
         error:    '.secret-error',
-        button:   'button'
+        error_mfa:'.mfa-error',
+        button:   'button',
+        mfaGroup: '.mfa-group',    // added MFA group selector
+        mfaToken: 'input[name="mfa_token"]', // added MFA token input
+        mfaInfo:  '.mfa-info' // added MFA info element
     },
 
     events: {
@@ -22,14 +26,36 @@ module.exports = Mn.View.extend({
             this.ui.button.addClass('btn-loading').prop('disabled', true);
             this.ui.error.hide();
 
-            Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true)
+            if(this.ui.mfaToken.val()) {
+                Api.Tokens.loginWithMFA(this.ui.identity.val(), this.ui.secret.val(), this.ui.mfaToken.val(), true)
                 .then(() => {
                     window.location = '/';
                 })
                 .catch(err => {
-                    this.ui.error.text(err.message).show();
+                    if (err.message === 'Invalid MFA token.') {
+                        this.ui.error_mfa.text(err.message).show();
+                    } else {
+                        this.ui.error.text(err.message).show();
+                    }
                     this.ui.button.removeClass('btn-loading').prop('disabled', false);
                 });
+            } else {
+                Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true)
+                .then(() => {
+                    window.location = '/';
+                })
+                .catch(err => {
+                    if (err.message === 'MFA token required') {
+                        this.ui.mfaGroup.show();
+                        this.ui.mfaInfo.show();
+                    } else {
+                        this.ui.error.text(err.message).show();
+                    }
+                    this.ui.button.removeClass('btn-loading').prop('disabled', false);
+                });
+            }
+
+            
         }
     },
 
@@ -40,3 +66,5 @@ module.exports = Mn.View.extend({
         }
     }
 });
+
+