diff --git a/Cargo.lock b/Cargo.lock
index 293bcff79732cd8085ec954825342da832a16f60..68293896957356cd8aaf259ca9b385fde163b1e4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -245,6 +245,7 @@ dependencies = [
  "crossbeam",
  "directories",
  "heed",
+ "hmac",
  "http",
  "image",
  "jsonwebtoken",
@@ -266,6 +267,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_yaml",
+ "sha-1",
  "sled",
  "thiserror",
  "thread_local",
@@ -428,6 +430,16 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "crypto-mac"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
 [[package]]
 name = "curve25519-dalek"
 version = "3.2.0"
@@ -897,6 +909,16 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "hmac"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+dependencies = [
+ "crypto-mac",
+ "digest",
+]
+
 [[package]]
 name = "hostname"
 version = "0.3.1"
@@ -2422,6 +2444,19 @@ dependencies = [
  "yaml-rust",
 ]
 
+[[package]]
+name = "sha-1"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
+dependencies = [
+ "block-buffer",
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
 [[package]]
 name = "sha1"
 version = "0.6.0"
diff --git a/Cargo.toml b/Cargo.toml
index 13a7af44fa5c4c22540b3e1be5085edc7004e453..fc83d11be8160dcc54cf0c1ef862f9064af7655d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -79,6 +79,9 @@ num_cpus = "1.13.0"
 threadpool = "1.8.1"
 heed = { git = "https://github.com/timokoesters/heed.git", rev = "f6f825da7fb2c758867e05ad973ef800a6fe1d5d", optional = true }
 thread_local = "1.1.3"
+# used for TURN server authentication
+hmac = "0.11.0"
+sha-1 = "0.9.8"
 
 [features]
 default = ["conduit_bin", "backend_sqlite"]
diff --git a/src/client_server/voip.rs b/src/client_server/voip.rs
index 83f39a48755eb4a8ba05f9e41dc64e32d71e6237..9c3b20d471b39c439d9df7840444bc0b85401363 100644
--- a/src/client_server/voip.rs
+++ b/src/client_server/voip.rs
@@ -1,6 +1,11 @@
-use crate::{database::DatabaseGuard, ConduitResult};
+use crate::{database::DatabaseGuard, ConduitResult, Ruma};
+use hmac::{Hmac, Mac, NewMac};
 use ruma::api::client::r0::voip::get_turn_server_info;
-use std::time::Duration;
+use ruma::SecondsSinceUnixEpoch;
+use sha1::Sha1;
+use std::time::{Duration, SystemTime};
+
+type HmacSha1 = Hmac<Sha1>;
 
 #[cfg(feature = "conduit_bin")]
 use rocket::get;
@@ -8,12 +13,44 @@
 /// # `GET /_matrix/client/r0/voip/turnServer`
 ///
 /// TODO: Returns information about the recommended turn server.
-#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/voip/turnServer"))]
-#[tracing::instrument(skip(db))]
-pub async fn turn_server_route(db: DatabaseGuard) -> ConduitResult<get_turn_server_info::Response> {
+#[cfg_attr(
+    feature = "conduit_bin",
+    get("/_matrix/client/r0/voip/turnServer", data = "<body>")
+)]
+#[tracing::instrument(skip(body, db))]
+pub async fn turn_server_route(
+    body: Ruma<get_turn_server_info::Request>,
+    db: DatabaseGuard,
+) -> ConduitResult<get_turn_server_info::Response> {
+    let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+
+    let turn_secret = db.globals.turn_secret();
+
+    let (username, password) = if turn_secret != "" {
+        let expiry = SecondsSinceUnixEpoch::from_system_time(
+            SystemTime::now() + Duration::from_secs(db.globals.turn_ttl()),
+        )
+        .expect("time is valid");
+
+        let username: String = format!("{}:{}", expiry.get(), sender_user);
+
+        let mut mac = HmacSha1::new_from_slice(turn_secret.as_bytes())
+            .expect("HMAC can take key of any size");
+        mac.update(username.as_bytes());
+
+        let password: String = base64::encode_config(mac.finalize().into_bytes(), base64::STANDARD);
+
+        (username, password)
+    } else {
+        (
+            db.globals.turn_username().clone(),
+            db.globals.turn_password().clone(),
+        )
+    };
+
     Ok(get_turn_server_info::Response {
-        username: db.globals.turn_username().clone(),
-        password: db.globals.turn_password().clone(),
+        username: username,
+        password: password,
         uris: db.globals.turn_uris().to_vec(),
         ttl: Duration::from_secs(db.globals.turn_ttl()),
     }
diff --git a/src/database.rs b/src/database.rs
index 85213c0007a0fd2956baac25992d21936e6001f2..080e24b3e9cc4a8c275d36d36efd1d72c4b05289 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -80,6 +80,8 @@ pub struct Config {
     turn_password: String,
     #[serde(default = "Vec::new")]
     turn_uris: Vec<String>,
+    #[serde(default)]
+    turn_secret: String,
     #[serde(default = "default_turn_ttl")]
     turn_ttl: u64,
 
diff --git a/src/database/globals.rs b/src/database/globals.rs
index 7338f1edb1e35022fd2a7492d3a075b69e7340e9..05ecb568821886245bea5e15109a0118f1a6bbfa 100644
--- a/src/database/globals.rs
+++ b/src/database/globals.rs
@@ -242,6 +242,10 @@ pub fn turn_username(&self) -> &String {
         &self.config.turn_username
     }
 
+    pub fn turn_secret(&self) -> &String {
+        &self.config.turn_secret
+    }
+
     /// TODO: the key valid until timestamp is only honored in room version > 4
     /// Remove the outdated keys and insert the new ones.
     ///