diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php
--- a/src/app/Http/Controllers/API/V4/OpenViduController.php
+++ b/src/app/Http/Controllers/API/V4/OpenViduController.php
@@ -103,11 +103,12 @@
         }
 
         $user = Auth::guard()->user();
+        $isOwner = $user && $user->id == $room->user_id;
 
         // There's no existing session
         if (!$room->hasSession()) {
             // Participants can't join the room until the session is created by the owner
-            if (!$user || $user->id != $room->user_id) {
+            if (!$isOwner) {
                 return $this->errorResponse(423, \trans('meet.session-not-found'));
             }
 
@@ -123,6 +124,29 @@
             }
         }
 
+        $password = (string) $room->getSetting('password');
+
+        $config = [
+            'locked' => $room->getSetting('locked') === 'true',
+            'password' => $isOwner ? $password : '',
+            'requires_password' => !$isOwner && strlen($password),
+        ];
+
+        // Validate room password
+        if (!$isOwner && strlen($password)) {
+            $request_password = request()->input('password');
+            if ($request_password !== $password) {
+                // Note: We send the config to the client so it knows to display the password field
+                $response = [
+                    'config' => $config,
+                    'message' => \trans('meet.session-password-error'),
+                    'status' => 'error',
+                ];
+
+                return response()->json($response, 425);
+            }
+        }
+
         // Create session token for the current user/connection
         $response = $room->getSessionToken('PUBLISHER');
 
@@ -138,11 +162,74 @@
         }
 
         // Tell the UI who's the room owner
-        $response['owner'] = $user && $user->id == $room->user_id;
+        $response['owner'] = $isOwner;
+
+        // Append the room configuration
+
+        $response['config'] = $config;
 
         return response()->json($response);
     }
 
+    /**
+     * Set the domain configuration.
+     *
+     * @param string $id Room identifier (name)
+     *
+     * @return \Illuminate\Http\JsonResponse|void
+     */
+    public function setRoomConfig($id)
+    {
+        $room = Room::where('name', $id)->first();
+
+        // Room does not exist, or the owner is deleted
+        if (!$room || !$room->owner) {
+            return $this->errorResponse(404);
+        }
+
+        $user = Auth::guard()->user();
+
+        // Only room owner can configure the room
+        if ($user->id != $room->user_id) {
+            return $this->errorResponse(403);
+        }
+
+        $input = request()->input();
+        $errors = [];
+
+        foreach ($input as $key => $value) {
+            switch ($key) {
+                case 'password':
+                    if ($value === null || $value === '') {
+                        $input[$key] = null;
+                    } else {
+                        // TODO: Do we have to validate the password in any way?
+                    }
+                    break;
+
+                case 'locked':
+                    $input[$key] = $value ? 'true' : null;
+                    break;
+
+                default:
+                    $errors[$key] = \trans('meet.room-unsupported-option-error');
+            }
+        }
+
+        if (!empty($errors)) {
+            return response()->json(['status' => 'error', 'errors' => $errors], 422);
+        }
+
+        if (!empty($input)) {
+            $room->setSettings($input);
+        }
+
+        return response()->json([
+                'status' => 'success',
+                'message' => \trans('meet.room-setconfig-success'),
+        ]);
+    }
+
     /**
      * Webhook as triggered from OpenVidu server
      *
diff --git a/src/app/OpenVidu/Room.php b/src/app/OpenVidu/Room.php
--- a/src/app/OpenVidu/Room.php
+++ b/src/app/OpenVidu/Room.php
@@ -5,6 +5,14 @@
 use App\Traits\SettingsTrait;
 use Illuminate\Database\Eloquent\Model;
 
+/**
+ * The eloquent definition of a Room.
+ *
+ * @property int     $id         Room identifier
+ * @property string  $name       Room name
+ * @property int     $user_id    Room owner
+ * @property ?string $session_id OpenVidu session identifier
+ */
 class Room extends Model
 {
     use SettingsTrait;
diff --git a/src/resources/js/meet.js b/src/resources/js/meet.js
--- a/src/resources/js/meet.js
+++ b/src/resources/js/meet.js
@@ -20,6 +20,7 @@
     faMicrophone,
     faPowerOff,
     faUser,
+    faShieldAlt,
     faVideo,
     faVolumeMute
 } from '@fortawesome/free-solid-svg-icons'
@@ -33,6 +34,7 @@
     faMicrophone,
     faPowerOff,
     faUser,
+    faShieldAlt,
     faVideo,
     faVolumeMute
 )
diff --git a/src/resources/lang/en/meet.php b/src/resources/lang/en/meet.php
--- a/src/resources/lang/en/meet.php
+++ b/src/resources/lang/en/meet.php
@@ -14,9 +14,13 @@
     */
 
     'room-not-found' => 'The room does not exist.',
+    'room-setconfig-success' => 'Room configuration updated successfully.',
+    'room-unsupported-option-error' => 'Invalid room configuration option.',
     'session-not-found' => 'The session does not exist.',
     'session-create-error' => 'Failed to create the session.',
     'session-join-error' => 'Failed to join the session.',
     'session-close-error' => 'Failed to close the session.',
     'session-close-success' => 'The session has been closed successfully.',
+    'session-password-error' => 'Failed to join the session. Invalid password.',
+
 ];
diff --git a/src/resources/themes/forms.scss b/src/resources/themes/forms.scss
--- a/src/resources/themes/forms.scss
+++ b/src/resources/themes/forms.scss
@@ -36,6 +36,36 @@
     }
 }
 
+.input-group-activable {
+    &.active {
+        :not(.input-group-append):not(.activable) {
+            display: none;
+        }
+    }
+
+    &:not(.active) {
+        .activable {
+            display: none;
+        }
+    }
+
+    // Label is always visible
+    .label {
+        color: $body-color;
+        display: initial !important;
+    }
+
+    .input-group-text {
+        border-color: transparent;
+        background: transparent;
+        padding-left: 0;
+
+        &:not(.label) {
+            flex: 1;
+        }
+    }
+}
+
 .form-control-plaintext .btn-sm {
     margin-top: -0.25rem;
 }
diff --git a/src/resources/themes/meet.scss b/src/resources/themes/meet.scss
--- a/src/resources/themes/meet.scss
+++ b/src/resources/themes/meet.scss
@@ -138,6 +138,10 @@
 
 #meet-setup {
     max-width: 720px;
+
+    .input-group svg {
+        width: 1em;
+    }
 }
 
 #meet-auth {
diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue
--- a/src/resources/vue/Meet/Room.vue
+++ b/src/resources/vue/Meet/Room.vue
@@ -20,6 +20,9 @@
                 <button class="btn btn-link link-fullscreen open hidden" @click="switchFullscreen" title="Full screen">
                     <svg-icon icon="compress"></svg-icon>
                 </button>
+                <button class="btn btn-link link-security" v-if="session && session.owner" @click="securityOptions" title="Security options">
+                    <svg-icon icon="shield-alt"></svg-icon>
+                </button>
                 <button class="btn btn-link link-logout" @click="logout" title="Leave session">
                     <svg-icon icon="power-off"></svg-icon>
                 </button>
@@ -35,38 +38,52 @@
                             <video class="rounded"></video>
                             <div class="volume"><div class="bar"></div></div>
                         </div>
-                        <div class="col-sm-6">
-                            <div class="form-group">
-                                <label for="setup-microphone">Microphone</label>
+                        <div class="col-sm-6 align-self-center">
+                            <div class="input-group">
+                                <label for="setup-microphone" class="input-group-prepend mb-0">
+                                    <span class="input-group-text" title="Microphone"><svg-icon icon="microphone"></svg-icon></span>
+                                </label>
                                 <select class="custom-select" id="setup-microphone" v-model="microphone" @change="setupMicrophoneChange">
                                     <option value="">None</option>
                                     <option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option>
                                 </select>
                             </div>
-                            <div class="form-group">
-                                <label for="setup-camera">Camera</label>
+                            <div class="input-group mt-2">
+                                <label for="setup-camera" class="input-group-prepend mb-0">
+                                    <span class="input-group-text" title="Camera"><svg-icon icon="video"></svg-icon></span>
+                                </label>
                                 <select class="custom-select" id="setup-camera" v-model="camera" @change="setupCameraChange">
                                     <option value="">None</option>
                                     <option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option>
                                 </select>
                             </div>
-                            <div class="form-group mb-0">
-                                <label for="setup-nickname">Nickname</label>
-                                <input class="form-control" type="text" id="setup-nickname" v-model="nickname">
+                            <div class="input-group mt-2">
+                                <label for="setup-nickname" class="input-group-prepend mb-0">
+                                    <span class="input-group-text" title="Nickname"><svg-icon icon="user"></svg-icon></span>
+                                </label>
+                                <input class="form-control" type="text" id="setup-nickname" v-model="nickname" placeholder="Your name">
+                            </div>
+                            <div class="input-group mt-2" v-if="session.config && session.config.requires_password">
+                                <label for="setup-password" class="input-group-prepend mb-0">
+                                    <span class="input-group-text" title="Password"><svg-icon icon="key"></svg-icon></span>
+                                </label>
+                                <input type="password" class="form-control" id="setup-password" v-model="password" placeholder="Password">
+                            </div>
+                            <div class="mt-3">
+                                <button v-if="roomState == 'ready' || roomState == 424 || roomState == 425"
+                                        type="button"
+                                        @click="joinSession"
+                                        :class="'btn w-100 btn-' + (roomState == 'ready' ? 'success' : 'primary')"
+                                >JOIN</button>
+                                <button v-if="roomState == 423"
+                                        type="button"
+                                        @click="joinSession"
+                                        class="btn btn-primary w-100"
+                                >I'm the owner</button>
                             </div>
                         </div>
-                        <div class="text-center mt-4 col-sm-12">
-                            <status-message :status="roomState" :status-labels="roomStateLabels" class="mb-3"></status-message>
-                            <button v-if="roomState == 'ready' || roomState == 424"
-                                    type="button"
-                                    @click="joinSession"
-                                    class="btn btn-primary pl-5 pr-5"
-                            >JOIN</button>
-                            <button v-if="roomState == 423"
-                                    type="button"
-                                    @click="joinSession"
-                                    class="btn btn-primary pl-5 pr-5"
-                            >I'm the owner</button>
+                        <div class="mt-4 col-sm-12">
+                            <status-message :status="roomState" :status-labels="roomStateLabels"></status-message>
                         </div>
                     </form>
                 </div>
@@ -103,6 +120,8 @@
                 </div>
             </div>
         </div>
+
+        <session-security-options v-if="session.config" :config="session.config" :room="room" @config-update="configUpdate"></session-security-options>
     </div>
 </template>
 
@@ -110,10 +129,12 @@
     import Meet from '../../js/meet/app.js'
     import StatusMessage from '../Widgets/StatusMessage'
     import LogonForm from '../Login'
+    import SessionSecurityOptions from './SessionSecurityOptions'
 
     export default {
         components: {
             LogonForm,
+            SessionSecurityOptions,
             StatusMessage
         },
         data() {
@@ -127,6 +148,7 @@
                 meet: null,
                 microphone: '',
                 nickname: '',
+                password: '',
                 room: null,
                 roomState: 'init',
                 roomStateLabels: {
@@ -134,6 +156,7 @@
                     404: 'The room does not exist.',
                     423: 'The room is closed. Please, wait for the owner to start the session.',
                     424: 'The room is closed. It will be open for others after you join.',
+                    425: 'The room is ready. Please, provide a valid password.',
                     500: 'Failed to create a session. Server error.'
                 },
                 session: {}
@@ -168,20 +191,20 @@
                 $('#meet-setup').removeClass('hidden')
                 $('#meet-auth').addClass('hidden')
             },
+            configUpdate(config) {
+                this.session.config = Object.assign({}, this.session.config, config)
+            },
             initSession(init) {
-                let params = []
-
-                if (this.canShareScreen) {
-                    params.push('screenShare=1')
+                this.post = {
+                    password: this.password,
+                    nickname: this.nickname,
+                    screenShare: this.canShareScreen ? 1 : 0,
+                    init: init ? 1 : 0
                 }
 
-                if (init) {
-                    params.push('init=1')
-                }
+                $('#setup-password').removeClass('is-invalid')
 
-                const url = '/api/v4/openvidu/rooms/' + this.room + '?' + params.join('&')
-
-                axios.get(url, { ignoreErrors: true })
+                axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true })
                     .then(response => {
                         // Response data contains: session, token and shareToken
                         this.roomState = 'ready'
@@ -194,10 +217,23 @@
                     .catch(error => {
                         this.roomState = String(error.response.status)
 
-                        // Waiting for the owner to open the room...
-                        if (error.response.status == 423) {
-                            // Update room state every 10 seconds
-                            window.roomRequest = setTimeout(() => { this.initSession() }, 10000)
+                        if (error.response.data && error.response.data.config) {
+                            this.session.config = error.response.data.config
+                        }
+
+                        switch (this.roomState) {
+                            case '423':
+                                // Waiting for the owner to open the room...
+                                // Update room state every 10 seconds
+                                window.roomRequest = setTimeout(() => { this.initSession() }, 10000)
+                                break;
+
+                            case '425':
+                                // Missing/invalid password
+                                if (init) {
+                                    $('#setup-password').addClass('is-invalid').focus()
+                                }
+                                break;
                         }
                     })
 
@@ -212,7 +248,7 @@
                     return
                 }
 
-                if (this.roomState == 424) {
+                if (this.roomState == 424 || this.roomState == 425) {
                     this.initSession(true)
                     return
                 }
@@ -223,6 +259,10 @@
                 $('#meet-setup').addClass('hidden')
                 $('#meet-session-toolbar,#meet-session-layout').removeClass('hidden')
 
+                if (!this.canShareScreen) {
+                    this.setMenuItem('screen', false, true)
+                }
+
                 this.session.nickname = this.nickname
                 this.session.menuElement = $('#meet-session-menu')[0]
                 this.session.chatElement = $('#meet-chat')[0]
@@ -255,6 +295,9 @@
                     window.location = window.config['app.url']
                 }
             },
+            securityOptions() {
+                $('#security-options-dialog').modal()
+            },
             setMenuItem(type, state, disabled) {
                 let button = $('#meet-session-menu').find('.link-' + type)
 
@@ -338,7 +381,9 @@
                     // After one screen sharing session ended request a new token
                     // for the next screen sharing session
                     if (!enabled) {
-                        axios.get('/api/v4/openvidu/rooms/' + this.room, { ignoreErrors: true })
+                        // TODO: This might need to be a different route. E.g. the room password might have
+                        //       changed since user joined the session
+                        axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true })
                             .then(response => {
                                 // Response data contains: session, token and shareToken
                                 this.session.shareToken = response.data.token
diff --git a/src/resources/vue/Meet/SessionSecurityOptions.vue b/src/resources/vue/Meet/SessionSecurityOptions.vue
new file mode 100644
--- /dev/null
+++ b/src/resources/vue/Meet/SessionSecurityOptions.vue
@@ -0,0 +1,110 @@
+<template>
+    <div v-if="config">
+        <div id="security-options-dialog" class="modal" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h5 class="modal-title">Security options</h5>
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                    </div>
+                    <div class="modal-body">
+                        <form id="security-options-password">
+                            <div id="password-input" class="input-group input-group-activable">
+                                <span class="input-group-text label">Password:</span>
+                                <span v-if="config.password" id="password-input-text" class="input-group-text">{{ config.password }}</span>
+                                <span v-else id="password-input-text" class="input-group-text text-muted">none</span>
+                                <input type="text" :value="config.password" name="password" class="form-control rounded-left activable">
+                                <div class="input-group-append">
+                                    <button type="button" @click="passwordSave" id="password-save-btn" class="btn btn-outline-primary activable rounded-right">Save</button>
+                                    <button type="button" v-if="config.password" id="password-clear-btn" @click="passwordClear" class="btn btn-outline-danger rounded">Clear password</button>
+                                    <button type="button" v-else @click="passwordSet" id="password-set-btn" class="btn btn-outline-primary rounded">Set password</button>
+                                </div>
+                            </div>
+                            <small class="form-text text-muted">
+                                You can add a password to your meeting. Participants will have to provide
+                                the password before they are allowed to join the meeting.
+                            </small>
+                        </form>
+                        <hr v-if="false">
+                        <form v-if="false" id="security-options-lock">
+                            <div id="room-lock" class="">
+                                <span class="">Locked room:</span>
+                                <input type="checkbox" name="lock" value="1" :checked="config.locked" @click="lockSave">
+                            </div>
+                            <small class="form-text text-muted">
+                                When the room is locked participants have to be approved by you
+                                before they could join the meeting.
+                            </small>
+                        </form>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary modal-action" data-dismiss="modal">Close</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        props: {
+            config: { type: Object, default: () => null },
+            room: { type: String, default: () => null }
+        },
+        data() {
+            return {
+            }
+        },
+        mounted() {
+            $('#security-options-dialog').on('show.bs.modal', e => {
+                $(e.target).find('.input-group-activable.active').removeClass('active')
+            })
+        },
+        methods: {
+            configSave(name, value, callback) {
+                const post = {}
+                post[name] = value
+
+                axios.post('/api/v4/openvidu/rooms/' + this.room + '/config', post)
+                    .then(response => {
+                        this.config[name] = value
+                        if (callback) {
+                            callback(response.data)
+                        }
+                        this.$emit('config-update', this.config)
+                        this.$toast.success(response.data.message)
+                    })
+            },
+            lockSave(e) {
+                this.configSave('locked', $(e.target).prop('checked') ? 1 : 0)
+            },
+            passwordClear() {
+                this.configSave('password', '')
+            },
+            passwordSave() {
+                this.configSave('password', $('#password-input input').val(), () => {
+                        $('#password-input').removeClass('active')
+                })
+            },
+            passwordSet() {
+                $('#password-input').addClass('active').find('input')
+                    .off('keydown.pass')
+                    .on('keydown.pass', e => {
+                        if (e.which == 13) {
+                            // On ENTER save the password
+                            this.passwordSave()
+                            e.preventDefault()
+                        } else if (e.which == 27) {
+                            // On ESC escape from the input, but not the dialog
+                            $('#password-input').removeClass('active')
+                            e.stopPropagation()
+                        }
+                    })
+                    .focus()
+            }
+        }
+    }
+</script>
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -83,6 +83,7 @@
 
         Route::get('openvidu/rooms', 'API\V4\OpenViduController@index');
         Route::post('openvidu/rooms/{id}/close', 'API\V4\OpenViduController@closeRoom');
+        Route::post('openvidu/rooms/{id}/config', 'API\V4\OpenViduController@setRoomConfig');
     }
 );
 
@@ -93,7 +94,7 @@
         'prefix' => $prefix . 'api/v4'
     ],
     function () {
-        Route::get('openvidu/rooms/{id}', 'API\V4\OpenViduController@joinRoom');
+        Route::post('openvidu/rooms/{id}', 'API\V4\OpenViduController@joinRoom');
     }
 );
 
diff --git a/src/routes/websocket.php b/src/routes/websocket.php
--- a/src/routes/websocket.php
+++ b/src/routes/websocket.php
@@ -15,27 +15,23 @@
 Websocket::on(
     'connect',
     function ($websocket, Request $request) {
-        \Log::debug("someone connected");
-        $websocket->emit(
-            'message',
-            'welcome'
-        );
+        return;
     }
 );
 
 Websocket::on(
     'open',
     function ($websocket, Request $request) {
-        \Log::debug("socket opened");
+        return;
     }
 );
 
 Websocket::on(
     'disconnect',
     function ($websocket) {
-        \Log::debug("someone disconnected");
+        return;
     }
 );
 
-Websocket::on('message', 'App\Http\Controllers\WebsocketController@message');
-Websocket::on('ping', 'App\Http\Controllers\WebsocketController@ping');
+//Websocket::on('message', 'App\Http\Controllers\WebsocketController@message');
+//Websocket::on('ping', 'App\Http\Controllers\WebsocketController@ping');
diff --git a/src/tests/Browser/Meet/RoomControlsTest.php b/src/tests/Browser/Meet/RoomControlsTest.php
--- a/src/tests/Browser/Meet/RoomControlsTest.php
+++ b/src/tests/Browser/Meet/RoomControlsTest.php
@@ -124,9 +124,10 @@
             $owner->assertToolbar([
                     'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
                     'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
-                    'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED,
+                    'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED,
                     'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED,
                     'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
+                    'security' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
                     'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
                 ])
                 ->whenAvailable('div.meet-video.publisher', function (Browser $browser) {
@@ -153,7 +154,7 @@
             $guest->assertToolbar([
                     'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED,
                     'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED,
-                    'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED,
+                    'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED,
                     'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED,
                     'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
                     'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED,
diff --git a/src/tests/Browser/Meet/RoomSecurityTest.php b/src/tests/Browser/Meet/RoomSecurityTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Browser/Meet/RoomSecurityTest.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Tests\Browser\Meet;
+
+use App\OpenVidu\Room;
+use Tests\Browser;
+use Tests\Browser\Components\Dialog;
+use Tests\Browser\Components\Toast;
+use Tests\Browser\Pages\Meet\Room as RoomPage;
+use Tests\TestCaseDusk;
+
+class RoomSecurityTest extends TestCaseDusk
+{
+    /**
+     * {@inheritDoc}
+     */
+    public function setUp(): void
+    {
+        parent::setUp();
+
+        $this->clearBetaEntitlements();
+        $this->assignBetaEntitlement('john@kolab.org', 'meet');
+
+        $room = Room::where('name', 'john')->first();
+        $room->setSettings(['password' => null, 'locked' => null]);
+    }
+
+    public function tearDown(): void
+    {
+        $this->clearBetaEntitlements();
+        $room = Room::where('name', 'john')->first();
+        $room->setSettings(['password' => null, 'locked' => null]);
+
+        parent::tearDown();
+    }
+
+    /**
+     * Test password protected room
+     *
+     * @group openvidu
+     */
+    public function testRoomPassword(): void
+    {
+        $this->browse(function (Browser $owner, Browser $guest) {
+            // Make sure there's no session yet
+            $room = Room::where('name', 'john')->first();
+            if ($room->session_id) {
+                $room->session_id = null;
+                $room->save();
+            }
+
+            // Join the room as an owner (authenticate)
+            $owner->visit(new RoomPage('john'))
+                ->click('@setup-button')
+                ->submitLogon('john@kolab.org', 'simple123')
+                ->waitFor('@setup-form')
+                ->waitUntilMissing('@setup-status-message.loading')
+                ->assertMissing('@setup-password-input')
+                ->click('@setup-button')
+                ->waitFor('@session')
+                // Enter Security option dialog
+                ->click('@menu button.link-security')
+                ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) {
+                    $browser->assertSeeIn('@title', 'Security options')
+                        ->assertSeeIn('@button-action', 'Close')
+                        ->assertElementsCount('.modal-footer button', 1)
+                        ->assertSeeIn('#password-input .label', 'Password:')
+                        ->assertSeeIn('#password-input-text.text-muted', 'none')
+                        ->assertVisible('#password-input + small')
+                        ->assertSeeIn('#password-set-btn', 'Set password')
+                        ->assertElementsCount('#password-input button', 1)
+                        ->assertMissing('#password-input input')
+                        // Test setting a password
+                        ->click('#password-set-btn')
+                        ->assertMissing('#password-input-text')
+                        ->assertVisible('#password-input input')
+                        ->assertValue('#password-input input', '')
+                        ->assertSeeIn('#password-input #password-save-btn', 'Save')
+                        ->assertElementsCount('#password-input button', 1)
+                        ->type('#password-input input', 'pass')
+                        ->click('#password-input #password-save-btn')
+                        ->assertToast(Toast::TYPE_SUCCESS, 'Room configuration updated successfully.')
+                        ->assertMissing('#password-input input')
+                        ->assertSeeIn('#password-input-text:not(.text-muted)', 'pass')
+                        ->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password')
+                        ->assertElementsCount('#password-input button', 1)
+                        ->click('@button-action');
+
+                    $this->assertSame('pass', $room->fresh()->getSetting('password'));
+                });
+
+            // In another browser act as a guest, expect password required
+            $guest->visit(new RoomPage('john'))
+                ->waitFor('@setup-form')
+                ->waitUntilMissing('@setup-status-message.loading')
+                ->assertSeeIn('@setup-status-message.text-danger', "Please, provide a valid password.")
+                ->assertVisible('@setup-form .input-group:nth-child(4) svg')
+                ->assertAttribute('@setup-form .input-group:nth-child(4) .input-group-text', 'title', 'Password')
+                ->assertAttribute('@setup-password-input', 'placeholder', 'Password')
+                ->assertValue('@setup-password-input', '')
+                ->assertSeeIn('@setup-button', "JOIN")
+                // Try to join w/o password
+                ->click('@setup-button')
+                ->waitFor('#setup-password.is-invalid')
+                // Try to join with a valid password
+                ->type('#setup-password', 'pass')
+                ->click('@setup-button')
+                ->waitFor('@session');
+
+            // Test removing the password
+            $owner->click('@menu button.link-security')
+                ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) {
+                    $browser->assertSeeIn('@title', 'Security options')
+                        ->assertSeeIn('#password-input-text:not(.text-muted)', 'pass')
+                        ->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password')
+                        ->assertElementsCount('#password-input button', 1)
+                        ->click('#password-clear-btn')
+                        ->assertToast(Toast::TYPE_SUCCESS, 'Room configuration updated successfully.')
+                        ->assertMissing('#password-input input')
+                        ->assertSeeIn('#password-input-text.text-muted', 'none')
+                        ->assertSeeIn('#password-set-btn', 'Set password')
+                        ->assertElementsCount('#password-input button', 1)
+                        ->click('@button-action');
+
+                    $this->assertSame(null, $room->fresh()->getSetting('password'));
+                });
+        });
+    }
+}
diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php
--- a/src/tests/Browser/Meet/RoomSetupTest.php
+++ b/src/tests/Browser/Meet/RoomSetupTest.php
@@ -101,12 +101,17 @@
                 ->assertVisible('@setup-form')
                 ->assertSeeIn('@setup-title', 'Set up your session')
                 ->assertVisible('@setup-video')
-                ->assertSeeIn('@setup-form .form-group:nth-child(1) label', 'Microphone')
+                ->assertVisible('@setup-form .input-group:nth-child(1) svg')
+                ->assertAttribute('@setup-form .input-group:nth-child(1) .input-group-text', 'title', 'Microphone')
                 ->assertVisible('@setup-mic-select')
-                ->assertSeeIn('@setup-form .form-group:nth-child(2) label', 'Camera')
+                ->assertVisible('@setup-form .input-group:nth-child(2) svg')
+                ->assertAttribute('@setup-form .input-group:nth-child(2) .input-group-text', 'title', 'Camera')
                 ->assertVisible('@setup-cam-select')
-                ->assertSeeIn('@setup-form .form-group:nth-child(3) label', 'Nickname')
+                ->assertVisible('@setup-form .input-group:nth-child(3) svg')
+                ->assertAttribute('@setup-form .input-group:nth-child(3) .input-group-text', 'title', 'Nickname')
                 ->assertValue('@setup-nickname-input', '')
+                ->assertAttribute('@setup-nickname-input', 'placeholder', 'Your name')
+                ->assertMissing('@setup-password-input')
                 ->assertSeeIn(
                     '@setup-status-message',
                     "The room is closed. Please, wait for the owner to start the session."
@@ -171,12 +176,12 @@
                         ->assertMissing('.status .status-video');
                 })
                 ->within(new Menu(), function ($browser) {
-                    $browser->assertMenuItems(['explore', 'blog', 'support', 'logout']);
+                    $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout']);
                 });
 
             if ($browser->isDesktop()) {
                 $browser->within(new Menu('footer'), function ($browser) {
-                    $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'logout']);
+                    $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']);
                 });
             }
 
diff --git a/src/tests/Browser/Pages/Meet/Room.php b/src/tests/Browser/Pages/Meet/Room.php
--- a/src/tests/Browser/Pages/Meet/Room.php
+++ b/src/tests/Browser/Pages/Meet/Room.php
@@ -63,6 +63,7 @@
             '@setup-mic-select' => '#setup-microphone',
             '@setup-cam-select' => '#setup-camera',
             '@setup-nickname-input' => '#setup-nickname',
+            '@setup-password-input' => '#setup-password',
             '@setup-preview' => '#setup-preview',
             '@setup-volume' => '#setup-preview .volume',
             '@setup-video' => '#setup-preview video',
diff --git a/src/tests/Feature/Controller/OpenViduTest.php b/src/tests/Feature/Controller/OpenViduTest.php
--- a/src/tests/Feature/Controller/OpenViduTest.php
+++ b/src/tests/Feature/Controller/OpenViduTest.php
@@ -14,12 +14,18 @@
     public function setUp(): void
     {
         parent::setUp();
+
         $this->clearBetaEntitlements();
+        $room = Room::where('name', 'john')->first();
+        $room->setSettings(['password' => null, 'locked' => null]);
     }
 
     public function tearDown(): void
     {
         $this->clearBetaEntitlements();
+        $room = Room::where('name', 'john')->first();
+        $room->setSettings(['password' => null, 'locked' => null]);
+
         parent::tearDown();
     }
 
@@ -75,22 +81,22 @@
         $this->assignBetaEntitlement($john, 'meet');
 
         // Unauth access, no session yet
-        $response = $this->get("api/v4/openvidu/rooms/{$room->name}");
+        $response = $this->post("api/v4/openvidu/rooms/{$room->name}");
         $response->assertStatus(423);
 
         // Non-existing room name
-        $response = $this->actingAs($john)->get("api/v4/openvidu/rooms/non-existing");
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing");
         $response->assertStatus(404);
 
         // Non-owner, no session yet
-        $response = $this->actingAs($jack)->get("api/v4/openvidu/rooms/{$room->name}");
+        $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}");
         $response->assertStatus(423);
 
         // Room owner, no session yet
-        $response = $this->actingAs($john)->get("api/v4/openvidu/rooms/{$room->name}");
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}");
         $response->assertStatus(424);
 
-        $response = $this->actingAs($john)->get("api/v4/openvidu/rooms/{$room->name}?init=1");
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]);
         $response->assertStatus(200);
 
         $json = $response->json();
@@ -106,7 +112,7 @@
         $john_token = $json['token'];
 
         // Non-owner, now the session exists
-        $response = $this->actingAs($jack)->get("api/v4/openvidu/rooms/{$room->name}");
+        $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}");
         $response->assertStatus(200);
 
         $json = $response->json();
@@ -116,6 +122,37 @@
         $this->assertTrue(strpos($json['token'], 'wss://') === 0);
         $this->assertTrue($json['token'] != $john_token);
         $this->assertTrue(!array_key_exists('shareToken', $json));
+        $this->assertEmpty($json['config']['password']);
+        $this->assertEmpty($json['config']['requires_password']);
+
+        // Non-owner, password protected room, password not provided
+        $room->setSettings(['password' => 'pass']);
+        $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}");
+        $response->assertStatus(425);
+
+        $json = $response->json();
+
+        $this->assertCount(3, $json);
+        $this->assertSame('error', $json['status']);
+        $this->assertSame('Failed to join the session. Invalid password.', $json['message']);
+        $this->assertEmpty($json['config']['password']);
+        $this->assertTrue($json['config']['requires_password']);
+
+        // Non-owner, password protected room, invalid provided
+        $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'aa']);
+        $response->assertStatus(425);
+
+        // Non-owner, password protected room, valid password provided
+        $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'pass']);
+        $response->assertStatus(200);
+
+        $json = $response->json();
+
+        $this->assertSame($session_id, $json['session']);
+
+        // Make sure the room owner can access the password protected room w/o password
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}");
+        $response->assertStatus(200);
 
         // TODO: Test accessing an existing room of deleted owner
     }
@@ -135,7 +172,7 @@
         $room = Room::where('name', 'john')->first();
 
         // Guest, request with screenShare token
-        $response = $this->get("api/v4/openvidu/rooms/{$room->name}?screenShare=1");
+        $response = $this->post("api/v4/openvidu/rooms/{$room->name}", ['screenShare' => 1]);
         $response->assertStatus(200);
 
         $json = $response->json();
@@ -199,4 +236,82 @@
         $this->assertSame("Failed to close the session.", $json['message']);
         $this->assertCount(2, $json);
     }
+
+    /**
+     * Test configuring the room (session)
+     *
+     * @group openvidu
+     */
+    public function testSetRoomConfig(): void
+    {
+        $john = $this->getTestUser('john@kolab.org');
+        $jack = $this->getTestUser('jack@kolab.org');
+        $room = Room::where('name', 'john')->first();
+
+        // Unauth access not allowed
+        $response = $this->post("api/v4/openvidu/rooms/{$room->name}/config", []);
+        $response->assertStatus(401);
+
+        // Non-existing room name
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/config", []);
+        $response->assertStatus(404);
+
+        // TODO: Test a room with a deleted owner
+
+        // Non-owner
+        $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/config", []);
+        $response->assertStatus(403);
+
+        // Room owner
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", []);
+        $response->assertStatus(200);
+
+        $json = $response->json();
+
+        $this->assertCount(2, $json);
+        $this->assertSame('success', $json['status']);
+        $this->assertSame("Room configuration updated successfully.", $json['message']);
+
+        // Set password and room lock
+        $post = ['password' => 'aaa', 'locked' => 1];
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post);
+        $response->assertStatus(200);
+
+        $json = $response->json();
+
+        $this->assertCount(2, $json);
+        $this->assertSame('success', $json['status']);
+        $this->assertSame("Room configuration updated successfully.", $json['message']);
+        $room->refresh();
+        $this->assertSame('aaa', $room->getSetting('password'));
+        $this->assertSame('true', $room->getSetting('locked'));
+
+        // Unset password and room lock
+        $post = ['password' => '', 'locked' => 0];
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post);
+        $response->assertStatus(200);
+
+        $json = $response->json();
+
+        $this->assertCount(2, $json);
+        $this->assertSame('success', $json['status']);
+        $this->assertSame("Room configuration updated successfully.", $json['message']);
+        $room->refresh();
+        $this->assertSame(null, $room->getSetting('password'));
+        $this->assertSame(null, $room->getSetting('locked'));
+
+        // Test invalid option error
+        $post = ['password' => 'eee', 'unknown' => 0];
+        $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post);
+        $response->assertStatus(422);
+
+        $json = $response->json();
+
+        $this->assertCount(2, $json);
+        $this->assertSame('error', $json['status']);
+        $this->assertSame("Invalid room configuration option.", $json['errors']['unknown']);
+
+        $room->refresh();
+        $this->assertSame(null, $room->getSetting('password'));
+    }
 }