diff --git a/.env.template b/.env.template
index 330f386526e642c7e7e40c466b826b92ad43aeb9..a83bd1713efcec0e85badd5b4c97c5b443d1ab2d 100644
--- a/.env.template
+++ b/.env.template
@@ -6,3 +6,7 @@ JITSI_ISS=
 SECRET_JITSI_KEY=
 ADMIN_API_TOKEN=123
 START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
+# If your Turn server is configured to use the Turn REST API, you should put the shared auth secret here.
+# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
+# Keep empty if you are sharing hard coded / clear text credentials.
+TURN_STATIC_AUTH_SECRET=
diff --git a/README.md b/README.md
index 5945ac489a9a446f0912e74ced13261fa67c6dd2..a8c186b6857fcf2794b84c6ff2ea4f7b3792b769 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,6 @@ Demo here : [https://workadventu.re/](https://workadventu.re/).
 
 # Work Adventure
 
-## Work in progress
-
 Work Adventure is a web-based collaborative workspace for small to medium teams (2-100 people) presented in the form of a
 16-bit video game.
 
diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts
index 95a705fa087612a853a20ad7d53d208596116807..b12f0542b7e7b7bfbd4a35b9d5a1526d0cfe7f2a 100644
--- a/back/src/Enum/EnvironmentVariable.ts
+++ b/back/src/Enum/EnvironmentVariable.ts
@@ -11,6 +11,7 @@ const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || '';
 const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080;
 const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051;
 export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed
+export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || '';
 
 export {
     MINIMUM_DISTANCE,
diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts
index c90b51cf9953b2ec77dbd686bfa8c5758c243cee..194080ecee5a0227624adccff2edf39cbc9fe30e 100644
--- a/back/src/Services/SocketManager.ts
+++ b/back/src/Services/SocketManager.ts
@@ -28,7 +28,13 @@ import {User, UserSocket} from "../Model/User";
 import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
 import {Group} from "../Model/Group";
 import {cpuTracker} from "./CpuTracker";
-import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
+import {
+    GROUP_RADIUS,
+    JITSI_ISS,
+    MINIMUM_DISTANCE,
+    SECRET_JITSI_KEY,
+    TURN_STATIC_AUTH_SECRET
+} from "../Enum/EnvironmentVariable";
 import {Movable} from "../Model/Movable";
 import {PositionInterface} from "../Model/PositionInterface";
 import {adminApi, CharacterTexture} from "./AdminApi";
@@ -40,6 +46,8 @@ import {ZoneSocket} from "../RoomManager";
 import {Zone} from "_Model/Zone";
 import Debug from "debug";
 import {Admin} from "_Model/Admin";
+import crypto from "crypto";
+
 
 const debug = Debug('sockermanager');
 
@@ -487,6 +495,11 @@ export class SocketManager {
             webrtcStartMessage1.setUserid(otherUser.id);
             webrtcStartMessage1.setName(otherUser.name);
             webrtcStartMessage1.setInitiator(true);
+            if (TURN_STATIC_AUTH_SECRET !== '') {
+                const {username, password} = this.getTURNCredentials(''+otherUser.id, TURN_STATIC_AUTH_SECRET);
+                webrtcStartMessage1.setWebrtcusername(username);
+                webrtcStartMessage1.setWebrtcpassword(password);
+            }
 
             const serverToClientMessage1 = new ServerToClientMessage();
             serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
@@ -500,6 +513,11 @@ export class SocketManager {
             webrtcStartMessage2.setUserid(user.id);
             webrtcStartMessage2.setName(user.name);
             webrtcStartMessage2.setInitiator(false);
+            if (TURN_STATIC_AUTH_SECRET !== '') {
+                const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
+                webrtcStartMessage2.setWebrtcusername(username);
+                webrtcStartMessage2.setWebrtcpassword(password);
+            }
 
             const serverToClientMessage2 = new ServerToClientMessage();
             serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
@@ -512,6 +530,25 @@ export class SocketManager {
         }
     }
 
+    /**
+     * Computes a unique user/password for the TURN server, using a shared secret between the WorkAdventure API server
+     * and the Coturn server.
+     * The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey`
+     */
+    private getTURNCredentials(name: string, secret: string): {username: string, password: string} {
+        const unixTimeStamp = Math.floor(Date.now()/1000) + 4*3600;   // this credential would be valid for the next 4 hours
+        const username = [unixTimeStamp, name].join(':');
+        const hmac = crypto.createHmac('sha1', secret);
+        hmac.setEncoding('base64');
+        hmac.write(username);
+        hmac.end();
+        const password = hmac.read();
+        return {
+            username: username,
+            password: password
+        };
+    }
+
     //disconnect user
     private disConnectedUser(user: User, group: Group) {
         // Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
diff --git a/deeployer.libsonnet b/deeployer.libsonnet
index 9d201081c3455161dcbf38ba1df60f87eec6bbbb..5093c86a0c141f624ea280320ac03df595bc50e3 100644
--- a/deeployer.libsonnet
+++ b/deeployer.libsonnet
@@ -22,6 +22,7 @@
          "JITSI_ISS": env.JITSI_ISS,
          "JITSI_URL": env.JITSI_URL,
          "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
+         "TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
        } + if adminUrl != null then {
          "ADMIN_API_URL": adminUrl,
        } else {}
@@ -40,6 +41,7 @@
               "JITSI_ISS": env.JITSI_ISS,
               "JITSI_URL": env.JITSI_URL,
               "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
+              "TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
             } + if adminUrl != null then {
               "ADMIN_API_URL": adminUrl,
             } else {}
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 286c12baf1e66c9bb2c3d45a278c4f555def24b6..9e9e08429d380b7255ad560374cca2b62ad3a3e4 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -32,8 +32,10 @@ services:
       STARTUP_COMMAND_1: ./templater.sh
       STARTUP_COMMAND_2: yarn install
       TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
-      TURN_USER: workadventure
-      TURN_PASSWORD: WorkAdventure123
+      # Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
+      # Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
+      TURN_USER:
+      TURN_PASSWORD:
       START_ROOM_URL: "$START_ROOM_URL"
     command: yarn run start
     volumes:
@@ -108,6 +110,7 @@ services:
       ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
       JITSI_URL: $JITSI_URL
       JITSI_ISS: $JITSI_ISS
+      TURN_STATIC_AUTH_SECRET:
     volumes:
       - ./back:/usr/src/app
     labels:
diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts
index 8eb7462fcbfc30434985347c3cfe1a6e294ffc18..65d4b4dce184772db2d22716d405b7b54c7832b6 100644
--- a/front/src/Connexion/RoomConnection.ts
+++ b/front/src/Connexion/RoomConnection.ts
@@ -427,7 +427,9 @@ export class RoomConnection implements RoomConnection {
             callback({
                 userId: message.getUserid(),
                 name: message.getName(),
-                initiator: message.getInitiator()
+                initiator: message.getInitiator(),
+                webRtcUser: message.getWebrtcpassword() ?? undefined,
+                webRtcPassword: message.getWebrtcpassword() ?? undefined,
             });
         });
     }
@@ -584,7 +586,7 @@ export class RoomConnection implements RoomConnection {
     public hasTag(tag: string): boolean {
         return this.tags.includes(tag);
     }
-    
+
     public isAdmin(): boolean {
         return this.hasTag('admin');
     }
diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts
index 1b8680cff9c94f08298b6c974a4b574d26cdda07..2491d1e61e36500938875711a26f3e1f44cd9c36 100644
--- a/front/src/WebRtc/ScreenSharingPeer.ts
+++ b/front/src/WebRtc/ScreenSharingPeer.ts
@@ -17,7 +17,7 @@ export class ScreenSharingPeer extends Peer {
     public toClose: boolean = false;
     public _connected: boolean = false;
 
-    constructor(private userId: number, initiator: boolean, private connection: RoomConnection) {
+    constructor(private userId: number, initiator: boolean, private connection: RoomConnection, webRtcUser: string | undefined, webRtcPassword: string | undefined) {
         super({
             initiator: initiator ? initiator : false,
             reconnectTimer: 10000,
@@ -28,8 +28,8 @@ export class ScreenSharingPeer extends Peer {
                     },
                     {
                         urls: TURN_SERVER.split(','),
-                        username: TURN_USER,
-                        credential: TURN_PASSWORD
+                        username: webRtcUser || TURN_USER,
+                        credential: webRtcPassword || TURN_PASSWORD
                     },
                 ]
             }
diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts
index 98f83b0c657677cb8ac73d727c33a3f34185aa73..889d374888175ee7b3a23a701702c34536c1282a 100644
--- a/front/src/WebRtc/SimplePeer.ts
+++ b/front/src/WebRtc/SimplePeer.ts
@@ -19,6 +19,8 @@ export interface UserSimplePeerInterface{
     userId: number;
     name?: string;
     initiator?: boolean;
+    webRtcUser?: string|undefined;
+    webRtcPassword?: string|undefined;
 }
 
 export interface PeerConnectionListener {
@@ -99,7 +101,7 @@ export class SimplePeer {
         // Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group)
         // So we can receive a request we already had before. (which will abort at the first line of createPeerConnection)
         // This would be symmetrical to the way we handle disconnection.
-        
+
         //start connection
         console.log('receiveWebrtcStart. Initiator: ', user.initiator)
         if(!user.initiator){
@@ -189,7 +191,7 @@ export class SimplePeer {
             mediaManager.addScreenSharingActiveVideo("" + user.userId);
         }
 
-        const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
+        const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection, user.webRtcUser, user.webRtcPassword);
         this.PeerScreenSharingConnectionArray.set(user.userId, peer);
 
         for (const peerConnectionListener of this.peerConnectionListeners) {
diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts
index b2df80c2081911707791392d9933742bc6affa7d..350b046f2f4a942895cac70c1e4351cc7e1ea791 100644
--- a/front/src/WebRtc/VideoPeer.ts
+++ b/front/src/WebRtc/VideoPeer.ts
@@ -36,8 +36,8 @@ export class VideoPeer extends Peer {
                     },
                     {
                         urls: TURN_SERVER.split(','),
-                        username: TURN_USER,
-                        credential: TURN_PASSWORD
+                        username: user.webRtcUser || TURN_USER,
+                        credential: user.webRtcPassword || TURN_PASSWORD
                     },
                 ]
             }
@@ -89,7 +89,7 @@ export class VideoPeer extends Peer {
                     mediaManager.addNewMessage(message.name, message.message);
                 }
             } else if(message.type === MESSAGE_TYPE_BLOCKED) {
-                //FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream. 
+                //FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream.
                 // Find a way to block A's output stream in A's js client
                 //However, the output stream stream B is correctly blocked in A client
                 this.blocked = true;
@@ -117,7 +117,7 @@ export class VideoPeer extends Peer {
                 this.sendBlockMessage(false);
             }
         });
-        
+
         if (blackListManager.isBlackListed(this.userId)) {
             this.sendBlockMessage(true)
         }
diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto
index 39f575be53cfd19b1e2ec54ab1090f4a981b8a99..a1e7688ec27747b3eca4da26dbeda3169b9e9a57 100644
--- a/messages/protos/messages.proto
+++ b/messages/protos/messages.proto
@@ -168,6 +168,8 @@ message WebRtcStartMessage {
   int32 userId = 1;
   string name = 2;
   bool initiator = 3;
+  string webrtcUserName = 4;
+  string webrtcPassword = 5;
 }
 
 message WebRtcDisconnectMessage {