diff --git a/migrations/mysql/2023-01-31-222222_add_argon2/down.sql b/migrations/mysql/2023-01-31-222222_add_argon2/down.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/migrations/mysql/2023-01-31-222222_add_argon2/up.sql b/migrations/mysql/2023-01-31-222222_add_argon2/up.sql
new file mode 100644
index 0000000000000000000000000000000000000000..35b8189e95ef21a5cd417a364d07f773485e9a33
--- /dev/null
+++ b/migrations/mysql/2023-01-31-222222_add_argon2/up.sql
@@ -0,0 +1,7 @@
+ALTER TABLE users
+    ADD COLUMN
+    client_kdf_memory INTEGER DEFAULT NULL;
+
+ALTER TABLE users
+    ADD COLUMN
+    client_kdf_parallelism INTEGER DEFAULT NULL;
diff --git a/migrations/postgresql/2023-01-31-222222_add_argon2/down.sql b/migrations/postgresql/2023-01-31-222222_add_argon2/down.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/migrations/postgresql/2023-01-31-222222_add_argon2/up.sql b/migrations/postgresql/2023-01-31-222222_add_argon2/up.sql
new file mode 100644
index 0000000000000000000000000000000000000000..35b8189e95ef21a5cd417a364d07f773485e9a33
--- /dev/null
+++ b/migrations/postgresql/2023-01-31-222222_add_argon2/up.sql
@@ -0,0 +1,7 @@
+ALTER TABLE users
+    ADD COLUMN
+    client_kdf_memory INTEGER DEFAULT NULL;
+
+ALTER TABLE users
+    ADD COLUMN
+    client_kdf_parallelism INTEGER DEFAULT NULL;
diff --git a/migrations/sqlite/2023-01-31-222222_add_argon2/down.sql b/migrations/sqlite/2023-01-31-222222_add_argon2/down.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/migrations/sqlite/2023-01-31-222222_add_argon2/up.sql b/migrations/sqlite/2023-01-31-222222_add_argon2/up.sql
new file mode 100644
index 0000000000000000000000000000000000000000..35b8189e95ef21a5cd417a364d07f773485e9a33
--- /dev/null
+++ b/migrations/sqlite/2023-01-31-222222_add_argon2/up.sql
@@ -0,0 +1,7 @@
+ALTER TABLE users
+    ADD COLUMN
+    client_kdf_memory INTEGER DEFAULT NULL;
+
+ALTER TABLE users
+    ADD COLUMN
+    client_kdf_parallelism INTEGER DEFAULT NULL;
diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
index a2816a4c73bfb2363f6002408cb8efd41271f4e2..0354f46489adf78d0de5790fa2a5e6208f38bd7f 100644
--- a/src/api/core/accounts.rs
+++ b/src/api/core/accounts.rs
@@ -49,6 +49,8 @@ pub struct RegisterData {
     Email: String,
     Kdf: Option<i32>,
     KdfIterations: Option<i32>,
+    KdfMemory: Option<i32>,
+    KdfParallelism: Option<i32>,
     Key: String,
     Keys: Option<KeysData>,
     MasterPasswordHash: String,
@@ -153,13 +155,16 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
     // Make sure we don't leave a lingering invitation.
     Invitation::take(&email, &mut conn).await;
 
+    if let Some(client_kdf_type) = data.Kdf {
+        user.client_kdf_type = client_kdf_type;
+    }
+
     if let Some(client_kdf_iter) = data.KdfIterations {
         user.client_kdf_iter = client_kdf_iter;
     }
 
-    if let Some(client_kdf_type) = data.Kdf {
-        user.client_kdf_type = client_kdf_type;
-    }
+    user.client_kdf_parallelism = data.KdfMemory;
+    user.client_kdf_memory = data.KdfParallelism;
 
     user.set_password(&data.MasterPasswordHash, Some(data.Key), true, None);
     user.password_hint = password_hint;
@@ -337,6 +342,8 @@ async fn post_password(
 struct ChangeKdfData {
     Kdf: i32,
     KdfIterations: i32,
+    KdfMemory: Option<i32>,
+    KdfParallelism: Option<i32>,
 
     MasterPasswordHash: String,
     NewMasterPasswordHash: String,
@@ -352,10 +359,31 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
         err!("Invalid password")
     }
 
-    if data.KdfIterations < 100_000 {
-        err!("KDF iterations lower then 100000 are not allowed.")
+    if data.Kdf == UserKdfType::Pbkdf2 as i32 && data.KdfIterations < 100_000 {
+        err!("PBKDF2 KDF iterations must be at least 100000.")
     }
 
+    if data.Kdf == UserKdfType::Argon2id as i32 {
+        if data.KdfIterations < 1 {
+            err!("Argon2 KDF iterations must be at least 1.")
+        }
+        if let Some(m) = data.KdfMemory {
+            if !(15..=1024).contains(&m) {
+                err!("Argon2 memory must be between 15 MB and 1024 MB.")
+            }
+            user.client_kdf_memory = data.KdfMemory;
+        } else {
+            err!("Argon2 memory parameter is required.")
+        }
+        if let Some(p) = data.KdfParallelism {
+            if !(1..=16).contains(&p) {
+                err!("Argon2 parallelism must be between 1 and 16.")
+            }
+            user.client_kdf_parallelism = data.KdfParallelism;
+        } else {
+            err!("Argon2 parallelism parameter is required.")
+        }
+    }
     user.client_kdf_iter = data.KdfIterations;
     user.client_kdf_type = data.Kdf;
     user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None);
@@ -770,15 +798,22 @@ async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
 pub async fn _prelogin(data: JsonUpcase<PreloginData>, mut conn: DbConn) -> Json<Value> {
     let data: PreloginData = data.into_inner().data;
 
-    let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &mut conn).await {
-        Some(user) => (user.client_kdf_type, user.client_kdf_iter),
-        None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT),
+    let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.Email, &mut conn).await {
+        Some(user) => (user.client_kdf_type, user.client_kdf_iter, user.client_kdf_memory, user.client_kdf_parallelism),
+        None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None),
     };
 
-    Json(json!({
+    let mut result = json!({
         "Kdf": kdf_type,
-        "KdfIterations": kdf_iter
-    }))
+        "KdfIterations": kdf_iter,
+    });
+
+    if kdf_type == UserKdfType::Argon2id as i32 {
+        result["KdfMemory"] = Value::Number(kdf_mem.expect("Argon2 memory parameter is required.").into());
+        result["KdfParallelism"] = Value::Number(kdf_para.expect("Argon2 parallelism parameter is required.").into());
+    }
+
+    Json(result)
 }
 
 // https://github.com/bitwarden/server/blob/master/src/Api/Models/Request/Accounts/SecretVerificationRequestModel.cs
diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs
index 90a5e6b8ea3d243c276e0c8cf0d8ee55d7cce8eb..1730de01babcf9f045973cda6a324cf466067d87 100644
--- a/src/api/core/emergency_access.rs
+++ b/src/api/core/emergency_access.rs
@@ -620,12 +620,22 @@ async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn:
         None => err!("Grantor user not found."),
     };
 
-    Ok(Json(json!({
-      "Kdf": grantor_user.client_kdf_type,
-      "KdfIterations": grantor_user.client_kdf_iter,
-      "KeyEncrypted": &emergency_access.key_encrypted,
-      "Object": "emergencyAccessTakeover",
-    })))
+    let mut result = json!({
+        "Kdf": grantor_user.client_kdf_type,
+        "KdfIterations": grantor_user.client_kdf_iter,
+        "KeyEncrypted": &emergency_access.key_encrypted,
+        "Object": "emergencyAccessTakeover",
+    });
+
+    if grantor_user.client_kdf_type == UserKdfType::Argon2id as i32 {
+        result["KdfMemory"] =
+            Value::Number(grantor_user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
+        result["KdfParallelism"] = Value::Number(
+            grantor_user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into(),
+        );
+    }
+
+    Ok(Json(result))
 }
 
 #[derive(Deserialize)]
diff --git a/src/api/identity.rs b/src/api/identity.rs
index e52608e9ac426ffd0cea172fa339754aa4fcc4b0..039e61d5dc4bbe0aa2708870db2a5a5d0b04c2fd 100644
--- a/src/api/identity.rs
+++ b/src/api/identity.rs
@@ -96,7 +96,7 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
     let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec);
     device.save(conn).await?;
 
-    Ok(Json(json!({
+    let mut result = json!({
         "access_token": access_token,
         "expires_in": expires_in,
         "token_type": "Bearer",
@@ -109,7 +109,16 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
         "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
         "scope": scope,
         "unofficialServer": true,
-    })))
+    });
+
+    if user.client_kdf_type == UserKdfType::Argon2id as i32 {
+        result["KdfMemory"] =
+            Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
+        result["KdfParallelism"] =
+            Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into());
+    }
+
+    Ok(Json(result))
 }
 
 async fn _password_login(
@@ -249,6 +258,13 @@ async fn _password_login(
         result["TwoFactorToken"] = Value::String(token);
     }
 
+    if user.client_kdf_type == UserKdfType::Argon2id as i32 {
+        result["KdfMemory"] =
+            Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
+        result["KdfParallelism"] =
+            Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into());
+    }
+
     info!("User {} logged in successfully. IP: {}", username, ip.ip);
     Ok(Json(result))
 }
@@ -333,7 +349,7 @@ async fn _api_key_login(
 
     // Note: No refresh_token is returned. The CLI just repeats the
     // client_credentials login flow when the existing token expires.
-    Ok(Json(json!({
+    let mut result = json!({
         "access_token": access_token,
         "expires_in": expires_in,
         "token_type": "Bearer",
@@ -345,7 +361,16 @@ async fn _api_key_login(
         "ResetMasterPassword": false, // TODO: Same as above
         "scope": scope,
         "unofficialServer": true,
-    })))
+    });
+
+    if user.client_kdf_type == UserKdfType::Argon2id as i32 {
+        result["KdfMemory"] =
+            Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
+        result["KdfParallelism"] =
+            Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into());
+    }
+
+    Ok(Json(result))
 }
 
 /// Retrieves an existing device or creates a new device from ConnectData and the User
diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs
index 274d48e88760e85d9de529bf63524975977575c9..96dc27ce5977fd70727dbf17172e5e542089216a 100644
--- a/src/db/models/mod.rs
+++ b/src/db/models/mod.rs
@@ -28,4 +28,4 @@ pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrgan
 pub use self::send::{Send, SendType};
 pub use self::two_factor::{TwoFactor, TwoFactorType};
 pub use self::two_factor_incomplete::TwoFactorIncomplete;
-pub use self::user::{Invitation, User, UserStampException};
+pub use self::user::{Invitation, User, UserKdfType, UserStampException};
diff --git a/src/db/models/user.rs b/src/db/models/user.rs
index 5ce87e14d79fe8b8bc27165cf3b3b0efda60977a..83a595246c90ea250ed582b06882f13f89c76e14 100644
--- a/src/db/models/user.rs
+++ b/src/db/models/user.rs
@@ -44,6 +44,8 @@ db_object! {
 
         pub client_kdf_type: i32,
         pub client_kdf_iter: i32,
+        pub client_kdf_memory: Option<i32>,
+        pub client_kdf_parallelism: Option<i32>,
 
         pub api_key: Option<String>,
 
@@ -58,6 +60,11 @@ db_object! {
     }
 }
 
+pub enum UserKdfType {
+    Pbkdf2 = 0,
+    Argon2id = 1,
+}
+
 enum UserStatus {
     Enabled = 0,
     Invited = 1,
@@ -73,7 +80,7 @@ pub struct UserStampException {
 
 /// Local methods
 impl User {
-    pub const CLIENT_KDF_TYPE_DEFAULT: i32 = 0; // PBKDF2: 0
+    pub const CLIENT_KDF_TYPE_DEFAULT: i32 = UserKdfType::Pbkdf2 as i32;
     pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000;
 
     pub fn new(email: String) -> Self {
@@ -113,6 +120,8 @@ impl User {
 
             client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
             client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
+            client_kdf_memory: None,
+            client_kdf_parallelism: None,
 
             api_key: None,
 
diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs
index cdb3e059dbf8fc500117b70260896536befe290e..da51799a29463eb0b438a988110d351aa6fef65c 100644
--- a/src/db/schemas/mysql/schema.rs
+++ b/src/db/schemas/mysql/schema.rs
@@ -199,6 +199,8 @@ table! {
         excluded_globals -> Text,
         client_kdf_type -> Integer,
         client_kdf_iter -> Integer,
+        client_kdf_memory -> Nullable<Integer>,
+        client_kdf_parallelism -> Nullable<Integer>,
         api_key -> Nullable<Text>,
         avatar_color -> Nullable<Text>,
     }
diff --git a/src/db/schemas/postgresql/schema.rs b/src/db/schemas/postgresql/schema.rs
index 6ec8a979de3d5e6bc53b2c2a8b9e2a5fe18cdbe7..aef644921cc64e2d0995b6ca9e42393d5cc43f8f 100644
--- a/src/db/schemas/postgresql/schema.rs
+++ b/src/db/schemas/postgresql/schema.rs
@@ -199,6 +199,8 @@ table! {
         excluded_globals -> Text,
         client_kdf_type -> Integer,
         client_kdf_iter -> Integer,
+        client_kdf_memory -> Nullable<Integer>,
+        client_kdf_parallelism -> Nullable<Integer>,
         api_key -> Nullable<Text>,
         avatar_color -> Nullable<Text>,
     }
diff --git a/src/db/schemas/sqlite/schema.rs b/src/db/schemas/sqlite/schema.rs
index faaf6fae9ea324303234775f49e9c21379ba7a81..a30b74338f0989012c30ef69cc1f612fe4d1a1a6 100644
--- a/src/db/schemas/sqlite/schema.rs
+++ b/src/db/schemas/sqlite/schema.rs
@@ -199,6 +199,8 @@ table! {
         excluded_globals -> Text,
         client_kdf_type -> Integer,
         client_kdf_iter -> Integer,
+        client_kdf_memory -> Nullable<Integer>,
+        client_kdf_parallelism -> Nullable<Integer>,
         api_key -> Nullable<Text>,
         avatar_color -> Nullable<Text>,
     }
diff --git a/src/main.rs b/src/main.rs
index 3c648231577fbdb9b873b9d7cfc36e8a64edbfe2..cd17a2f5d35ab2054c683582d09c9a39f5b6ff3c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,7 +34,7 @@
 // The more key/value pairs there are the more recursion occurs.
 // We want to keep this as low as possible, but not higher then 128.
 // If you go above 128 it will cause rust-analyzer to fail,
-#![recursion_limit = "97"]
+#![recursion_limit = "103"]
 
 // When enabled use MiMalloc as malloc instead of the default malloc
 #[cfg(feature = "enable_mimalloc")]
diff --git a/src/util.rs b/src/util.rs
index fe99e2d3b4e12fa64935d3fe0f3c96d24b194047..18adea70aea650f56bf33cb71f13680c4fcbc615 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -58,7 +58,7 @@ impl Fairing for AppHeaders {
                 base-uri 'self'; \
                 form-action 'self'; \
                 object-src 'self' blob:; \
-                script-src 'self'; \
+                script-src 'self' 'wasm-unsafe-eval'; \
                 style-src 'self' 'unsafe-inline'; \
                 child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
                 frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \