diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml
index 1c205b5f5e..2d5c68c0e8 100644
--- a/interface/resources/qml/LoginDialog.qml
+++ b/interface/resources/qml/LoginDialog.qml
@@ -120,6 +120,6 @@ FocusScope {
     }
 
     Component.onCompleted: {
-        bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
+        bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
     }
 }
diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml
index 144b91063f..ebc677fb00 100644
--- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml
+++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml
@@ -22,11 +22,16 @@ Item {
     width: root.width
     height: root.height
     readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
+    readonly property string termsContainerOculusText: qsTr("By signing up, you agree to High Fidelity's Terms of Service")
+    readonly property int textFieldHeight: 31
     readonly property string fontFamily: "Raleway"
     readonly property int fontSize: 15
     readonly property bool fontBold: true
+    readonly property int textFieldFontSize: 18
+    readonly property var passwordImageRatio: 16 / 23
 
-    readonly property bool withSteam: withSteam
+    property bool withOculus: withOculus
+    property bool withSteam: withSteam
     property string errorString: errorString
 
     readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@@ -61,15 +66,20 @@ Item {
 
         Item {
             id: contentItem
-            anchors.fill: parent
+            width: parent.width
+            height: errorContainer.height + fields.height + buttons.height + additionalTextContainer.height +
+                termsContainer.height
+            anchors.top: parent.top
+            anchors.topMargin: root.bannerHeight + 0.25 * parent.height
+            anchors.left: parent.left
 
             Item {
                 id: errorContainer
-                width: parent.width
+                width: root.bannerWidth
                 height: loginErrorMessageTextMetrics.height
                 anchors {
-                    bottom: buttons.top;
-                    bottomMargin: hifi.dimensions.contentSpacing.y;
+                    bottom: completeProfileBody.withOculus ? fields.top : buttons.top;
+                    bottomMargin: 1.5 * hifi.dimensions.contentSpacing.y;
                     left: buttons.left;
                 }
                 TextMetrics {
@@ -79,8 +89,8 @@ Item {
                 }
                 Text {
                     id: loginErrorMessage;
-                    width: root.bannerWidth
                     color: "red";
+                    width: root.bannerWidth;
                     font.family: completeProfileBody.fontFamily
                     font.pixelSize: 18
                     font.bold: completeProfileBody.fontBold
@@ -88,13 +98,196 @@ Item {
                     horizontalAlignment: Text.AlignHCenter
                     text: completeProfileBody.errorString
                     visible: true
+                    onTextChanged: {
+                        mainContainer.recalculateErrorMessage();
+                    }
+                    Component.onCompleted: {
+                        mainContainer.recalculateErrorMessage();
+                    }
                 }
-                Component.onCompleted: {
-                    if (loginErrorMessageTextMetrics.width > root.bannerWidth && root.isTablet) {
-                        loginErrorMessage.wrapMode = Text.WordWrap;
-                        loginErrorMessage.verticalAlignment = Text.AlignLeft;
-                        loginErrorMessage.horizontalAlignment = Text.AlignLeft;
-                        errorContainer.height = 3 * loginErrorMessageTextMetrics.height;
+            }
+
+            Item {
+                id: fields
+                width: root.bannerWidth
+                height: 3 * completeProfileBody.textFieldHeight + 2 * hifi.dimensions.contentSpacing.y
+                visible: completeProfileBody.withOculus
+                anchors {
+                    left: parent.left
+                    leftMargin: (parent.width - root.bannerWidth) / 2
+                    bottom: buttons.top
+                    bottomMargin: hifi.dimensions.contentSpacing.y
+                }
+
+                HifiControlsUit.TextField {
+                    id: usernameField
+                    width: root.bannerWidth
+                    height: completeProfileBody.textFieldHeight
+                    placeholderText: "Username"
+                    font.pixelSize: completeProfileBody.textFieldFontSize
+                    styleRenderType: Text.QtRendering
+                    anchors {
+                        top: parent.top
+                    }
+                    Keys.onPressed: {
+                        if (!usernameField.visible) {
+                            return;
+                        }
+                        switch (event.key) {
+                            case Qt.Key_Tab:
+                                event.accepted = true;
+                                if (event.modifiers === Qt.ShiftModifier) {
+                                    passwordField.focus = true;
+                                } else {
+                                    emailField.focus = true;
+                                }
+                                break;
+                            case Qt.Key_Backtab:
+                                event.accepted = true;
+                                passwordField.focus = true;
+                                break;
+                            case Qt.Key_Enter:
+                            case Qt.Key_Return:
+                                event.accepted = true;
+                                loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
+                                bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
+                                    "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
+                                break;
+                        }
+                    }
+                    onFocusChanged: {
+                        root.text = "";
+                        if (focus) {
+                            root.isPassword = false;
+                        }
+                    }
+                    Component.onCompleted: {
+                        var userID = "";
+                        if (completeProfileBody.withOculus) {
+                            userID = loginDialog.oculusUserID();
+                        }
+                        usernameField.text = userID;
+                    }
+                }
+                HifiControlsUit.TextField {
+                    id: emailField
+                    width: root.bannerWidth
+                    height: completeProfileBody.textFieldHeight
+                    anchors {
+                        top: usernameField.bottom
+                        topMargin: hifi.dimensions.contentSpacing.y
+                    }
+                    placeholderText: "Email"
+                    font.pixelSize: completeProfileBody.textFieldFontSize
+                    styleRenderType: Text.QtRendering
+                    activeFocusOnPress: true
+                    Keys.onPressed: {
+                        switch (event.key) {
+                            case Qt.Key_Tab:
+                                event.accepted = true;
+                                if (event.modifiers === Qt.ShiftModifier) {
+                                    usernameField.focus = true;
+                                } else {
+                                    passwordField.focus = true;
+                                }
+                                break;
+                            case Qt.Key_Backtab:
+                                event.accepted = true;
+                                usernameField.focus = true;
+                                break;
+                            case Qt.Key_Enter:
+                            case Qt.Key_Return:
+                                event.accepted = true;
+                                loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
+                                bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
+                                    "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
+                                break;
+                        }
+                    }
+                    onFocusChanged: {
+                        root.text = "";
+                        if (focus) {
+                            root.isPassword = false;
+                        }
+                    }
+                }
+                HifiControlsUit.TextField {
+                    id: passwordField
+                    width: root.bannerWidth
+                    height: completeProfileBody.textFieldHeight
+                    placeholderText: "Password (optional)"
+                    font.pixelSize: completeProfileBody.textFieldFontSize
+                    styleRenderType: Text.QtRendering
+                    activeFocusOnPress: true
+                    echoMode: passwordFieldMouseArea.showPassword ? TextInput.Normal : TextInput.Password
+                    anchors {
+                        top: emailField.bottom
+                        topMargin: hifi.dimensions.contentSpacing.y
+                    }
+
+                    onFocusChanged: {
+                        root.text = "";
+                        root.isPassword = focus;
+                    }
+
+                    Item {
+                        id: showPasswordContainer
+                        z: 10
+                        // width + image's rightMargin
+                        width: showPasswordImage.width + 8
+                        height: parent.height
+                        anchors {
+                            right: parent.right
+                        }
+
+                        Image {
+                            id: showPasswordImage
+                            width: passwordField.height * passwordImageRatio
+                            height: passwordField.height * passwordImageRatio
+                            anchors {
+                                right: parent.right
+                                rightMargin: 8
+                                top: parent.top
+                                topMargin: passwordFieldMouseArea.showPassword ? 6 : 8
+                                bottom: parent.bottom
+                                bottomMargin: passwordFieldMouseArea.showPassword ? 5 : 8
+                            }
+                            source: passwordFieldMouseArea.showPassword ?  "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"
+                            MouseArea {
+                                id: passwordFieldMouseArea
+                                anchors.fill: parent
+                                acceptedButtons: Qt.LeftButton
+                                property bool showPassword: false
+                                onClicked: {
+                                    showPassword = !showPassword;
+                                }
+                            }
+                        }
+                    }
+                    Keys.onPressed: {
+                        switch (event.key) {
+                            case Qt.Key_Tab:
+                                event.accepted = true;
+                                if (event.modifiers === Qt.ShiftModifier) {
+                                    emailField.focus = true;
+                                } else if (usernameField.visible) {
+                                    usernameField.focus = true;
+                                } else {
+                                    emailField.focus = true;
+                                }
+                                break;
+                            case Qt.Key_Backtab:
+                                event.accepted = true;
+                                emailField.focus = true;
+                                break;
+                        case Qt.Key_Enter:
+                        case Qt.Key_Return:
+                            event.accepted = true;
+                            loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
+                            bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
+                                "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
+                            break;
+                        }
                     }
                 }
             }
@@ -105,7 +298,7 @@ Item {
                 height: d.minHeightButton
                 anchors {
                     top: parent.top
-                    topMargin: (parent.height - additionalTextContainer.height) / 2 - hifi.dimensions.contentSpacing.y
+                    topMargin: (parent.height - additionalTextContainer.height + fields.height) / 2 - hifi.dimensions.contentSpacing.y
                     left: parent.left
                     leftMargin: (parent.width - root.bannerWidth) / 2
                 }
@@ -144,7 +337,7 @@ Item {
                     width: (parent.width - hifi.dimensions.contentSpacing.x) / 2
                     height: d.minHeightButton
 
-                    text: qsTr("Create your profile")
+                    text: completeProfileBody.withOculus ? qsTr("Sign Up") : qsTr("Create your profile")
                     color: hifi.buttons.blue
 
                     fontFamily: completeProfileBody.fontFamily
@@ -158,55 +351,12 @@ Item {
                             UserActivityLogger.logAction("encourageLoginDialog", data);
                         }
                         loginErrorMessage.visible = false;
-                        loginDialog.createAccountFromSteam();
-                    }
-                }
-            }
-
-            Item {
-                id: additionalTextContainer
-                width: parent.width
-                height: additionalTextMetrics.height
-                anchors {
-                    top: buttons.bottom
-                    horizontalCenter: parent.horizontalCenter
-                    topMargin: hifi.dimensions.contentSpacing.y
-                    left: parent.left
-                }
-
-                TextMetrics {
-                    id: additionalTextMetrics
-                    font: additionalText.font
-                    text: "Already have a High Fidelity profile? Link to an existing profile here."
-                }
-
-                HifiStylesUit.ShortcutText {
-                    id: additionalText
-                    text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
-
-                    font.family: completeProfileBody.fontFamily
-                    font.pixelSize: completeProfileBody.fontSize
-                    font.bold: completeProfileBody.fontBold
-                    wrapMode: Text.NoWrap
-                    lineHeight: 1
-                    lineHeightMode: Text.ProportionalHeight
-                    horizontalAlignment: Text.AlignHCenter
-                    linkColor: hifi.colors.blueAccent
-
-                    onLinkActivated: {
-                        loginDialog.isLogIn = true;
-                        bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", "withSteam": true, "linkSteam": true });
-                    }
-                    Component.onCompleted: {
-                        if (additionalTextMetrics.width > root.bannerWidth && root.isTablet) {
-                            additionalText.width = root.bannerWidth;
-                            additionalText.wrapMode = Text.WordWrap;
-                            additionalText.verticalAlignment = Text.AlignLeft;
-                            additionalText.horizontalAlignment = Text.AlignLeft;
-                            additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height;
-                            additionalTextContainer.anchors.left = buttons.left;
-                        } else {
-                            additionalText.anchors.centerIn = additionalTextContainer;
+                        if (completeProfileBody.withOculus) {
+                            loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
+                            bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
+                                "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
+                        } else if (completeProfileBody.withSteam) {
+                            loginDialog.createAccountFromSteam();
                         }
                     }
                 }
@@ -217,29 +367,33 @@ Item {
                 width: parent.width
                 height: termsTextMetrics.height
                 anchors {
-                    top: additionalTextContainer.bottom
+                    top: buttons.bottom
                     horizontalCenter: parent.horizontalCenter
-                    topMargin: 2 * hifi.dimensions.contentSpacing.y
+                    topMargin: hifi.dimensions.contentSpacing.y
                     left: parent.left
                 }
                 TextMetrics {
                     id: termsTextMetrics
                     font: termsText.font
-                    text: completeProfileBody.termsContainerText
+                    text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText
                     Component.onCompleted: {
                         // with the link.
-                        termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
+                        if (completeProfileBody.withOculus) {
+                            termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
+                        } else {
+                            termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
+                        }
                     }
                 }
 
                 HifiStylesUit.InfoItem {
                     id: termsText
-                    text: completeProfileBody.termsContainerText
+                    text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText
                     font.family: completeProfileBody.fontFamily
                     font.pixelSize: completeProfileBody.fontSize
                     font.bold: completeProfileBody.fontBold
                     wrapMode: Text.WordWrap
-                    color: hifi.colors.lightGray
+                    color: hifi.colors.white
                     linkColor: hifi.colors.blueAccent
                     lineHeight: 1
                     lineHeightMode: Text.ProportionalHeight
@@ -247,7 +401,7 @@ Item {
                     onLinkActivated: loginDialog.openUrl(link);
 
                     Component.onCompleted: {
-                        if (termsTextMetrics.width > root.bannerWidth && root.isTablet) {
+                        if (termsTextMetrics.width > root.bannerWidth) {
                             termsText.width = root.bannerWidth;
                             termsText.wrapMode = Text.WordWrap;
                             additionalText.verticalAlignment = Text.AlignLeft;
@@ -260,14 +414,86 @@ Item {
                     }
                 }
             }
+
+            Item {
+                id: additionalTextContainer
+                width: parent.width
+                height: additionalTextMetrics.height
+                anchors {
+                    top: termsContainer.bottom
+                    horizontalCenter: parent.horizontalCenter
+                    topMargin: 2 * hifi.dimensions.contentSpacing.y
+                    left: parent.left
+                }
+
+                TextMetrics {
+                    id: additionalTextMetrics
+                    font: additionalText.font
+                    text: "Already have a High Fidelity profile? Link to an existing profile here."
+                }
+
+                HifiStylesUit.ShortcutText {
+                    id: additionalText
+                    text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
+                    width: root.bannerWidth;
+                    font.family: completeProfileBody.fontFamily
+                    font.pixelSize: completeProfileBody.fontSize
+                    font.bold: completeProfileBody.fontBold
+                    wrapMode: Text.NoWrap
+                    lineHeight: 1
+                    lineHeightMode: Text.ProportionalHeight
+                    horizontalAlignment: Text.AlignHCenter
+                    linkColor: hifi.colors.blueAccent
+
+                    onLinkActivated: {
+                        bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "",
+                            "withSteam": completeProfileBody.withSteam, "linkSteam": completeProfileBody.withSteam, "withOculus": completeProfileBody.withOculus,
+                            "linkOculus": completeProfileBody.withOculus });
+                    }
+                    Component.onCompleted: {
+                        if (additionalTextMetrics.width > root.bannerWidth) {
+                            additionalText.wrapMode = Text.WordWrap;
+                            additionalText.verticalAlignment = Text.AlignLeft;
+                            additionalText.horizontalAlignment = Text.AlignLeft;
+                            additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height;
+                            additionalTextContainer.anchors.left = buttons.left;
+                        } else {
+                            additionalText.anchors.centerIn = additionalTextContainer;
+                        }
+                    }
+                }
+            }
+        }
+        function recalculateErrorMessage() {
+            if (completeProfileBody.errorString !== "") {
+                loginErrorMessage.visible = true;
+                var errorLength = completeProfileBody.errorString.split(/\r\n|\r|\n/).length;
+                var errorStringEdited = completeProfileBody.errorString.replace(/[\n\r]+/g, "\n");
+                loginErrorMessage.text = errorStringEdited;
+                if (errorLength > 1.0) {
+                    loginErrorMessage.wrapMode = Text.WordWrap;
+                    loginErrorMessage.verticalAlignment = Text.AlignLeft;
+                    loginErrorMessage.horizontalAlignment = Text.AlignLeft;
+                    errorContainer.height = errorLength * loginErrorMessageTextMetrics.height;
+                } else if (loginErrorMessageTextMetrics.width > root.bannerWidth) {
+                    loginErrorMessage.wrapMode = Text.WordWrap;
+                    loginErrorMessage.verticalAlignment = Text.AlignLeft;
+                    loginErrorMessage.horizontalAlignment = Text.AlignLeft;
+                    errorContainer.height = (loginErrorMessageTextMetrics.width / root.bannerWidth) * loginErrorMessageTextMetrics.height;
+                } else {
+                    loginErrorMessage.wrapMode = Text.NoWrap;
+                    loginErrorMessage.verticalAlignment = Text.AlignVCenter;
+                    loginErrorMessage.horizontalAlignment = Text.AlignHCenter;
+                    errorContainer.height = loginErrorMessageTextMetrics.height;
+                }
+            }
         }
     }
 
     Connections {
         target: loginDialog
         onHandleCreateCompleted: {
-            console.log("Create Succeeded")
-
+            console.log("Create Succeeded");
             if (completeProfileBody.withSteam) {
                 if (completeProfileBody.loginDialogPoppedUp) {
                     var data = {
@@ -277,20 +503,24 @@ Item {
                 }
                 loginDialog.loginThroughSteam();
             }
-            bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false });
+            bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false,
+                "withOculus": completeProfileBody.withOculus, "linkOculus": false });
         }
         onHandleCreateFailed: {
             console.log("Create Failed: " + error);
-            if (completeProfileBody.withSteam) {
+            if (completeProfileBody.withSteam || completeProfileBody.withOculus) {
                 if (completeProfileBody.loginDialogPoppedUp) {
+                    action = completeProfileBody.withSteam ? "Steam" : "Oculus";
                     var data = {
-                        "action": "user failed to create a profile with Steam from the complete profile screen"
+                        "action": "user failed to create a profile with " + action + " from the complete profile screen"
                     }
                     UserActivityLogger.logAction("encourageLoginDialog", data);
                 }
             }
-
-            bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam });
+            if (!completeProfileBody.withOculus) {
+                bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
+                    "withOculus": completeProfileBody.withOculus });
+            }
         }
     }
 
@@ -302,5 +532,6 @@ Item {
         }
         d.resize();
         root.text = "";
+        usernameField.forceActiveFocus();
     }
 }
diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
index 5048bf0278..4dd05f594d 100644
--- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml
+++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
@@ -36,9 +36,10 @@ Item {
     property bool keyboardRaised: false
     property bool punctuationMode: false
 
-    property bool withSteam: false
+    property bool withSteam: withSteam
     property bool linkSteam: linkSteam
-    property bool withOculus: false
+    property bool withOculus: withOculus
+    property bool linkOculus: linkOculus
     property string errorString: errorString
     property bool lostFocus: false
 
@@ -83,23 +84,24 @@ Item {
             }
             UserActivityLogger.logAction("encourageLoginDialog", data);
         }
-
-        bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam });
+        bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
+            "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus });
     }
 
     function init() {
         // going to/from sign in/up dialog.
-        loginDialog.isLogIn = true;
         loginErrorMessage.text = linkAccountBody.errorString;
         loginErrorMessage.visible = (linkAccountBody.errorString  !== "");
-        loginButton.text = !linkAccountBody.linkSteam ? "Log In" : "Link Account";
+        if (loginErrorMessageTextMetrics.width > emailField.width) {
+            loginErrorMessage.wrapMode = Text.WordWrap;
+            errorContainer.height = (loginErrorMessageTextMetrics.width / emailField.width) * loginErrorMessageTextMetrics.height;
+        }
+        loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account";
         loginButton.color = hifi.buttons.blue;
         emailField.placeholderText = "Username or Email";
         var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", "");
         emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : "";
-        if (linkAccountBody.linkSteam) {
-            steamInfoText.anchors.top = passwordField.bottom;
-            keepMeLoggedInCheckbox.anchors.top = steamInfoText.bottom;
+        if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
             loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2;
             loginButton.anchors.right = emailField.right;
         } else {
@@ -125,7 +127,7 @@ Item {
             id: loginContainer
             width: emailField.width
             height: errorContainer.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y +
-                keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height + steamInfoTextMetrics.height
+                keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height
             anchors {
                 top: parent.top
                 topMargin: root.bannerHeight + 0.25 * parent.height
@@ -135,7 +137,7 @@ Item {
 
             Item {
                 id: errorContainer
-                width: loginErrorMessageTextMetrics.width
+                width: parent.width
                 height: loginErrorMessageTextMetrics.height
                 anchors {
                     bottom: emailField.top;
@@ -304,7 +306,7 @@ Item {
                 fontSize: linkAccountBody.fontSize
                 fontBold: linkAccountBody.fontBold
                 color: hifi.buttons.noneBorderlessWhite;
-                visible: linkAccountBody.linkSteam
+                visible: linkAccountBody.linkSteam || linkAccountBody.linkOculus
                 anchors {
                     top: keepMeLoggedInCheckbox.bottom
                     topMargin: hifi.dimensions.contentSpacing.y
@@ -315,10 +317,9 @@ Item {
                             "action": "user clicked cancel at link account screen"
                         };
                         UserActivityLogger.logAction("encourageLoginDialog", data);
-                        loginDialog.dismissLoginDialog();
                     }
-
-                    bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "errorString": "" });
+                    bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
+                        "withOculus": linkAccountBody.withOculus, "errorString": "" });
                 }
             }
             HifiControlsUit.Button {
@@ -337,33 +338,6 @@ Item {
                     linkAccountBody.login();
                 }
             }
-            TextMetrics {
-                id: steamInfoTextMetrics
-                font: steamInfoText.font
-                text: steamInfoText.text
-            }
-            Text {
-                id: steamInfoText
-                width: root.bannerWidth
-                visible: linkAccountBody.linkSteam
-                anchors {
-                    top: loginButton.bottom
-                    topMargin: hifi.dimensions.contentSpacing.y
-                    left: emailField.left
-                }
-
-                font.family: linkAccountBody.fontFamily
-                font.pixelSize: linkAccountBody.textFieldFontSize
-                color: "white"
-                text: qsTr("Your Steam account information will not be exposed to others.");
-                verticalAlignment: Text.AlignVCenter
-                horizontalAlignment: Text.AlignHCenter
-                Component.onCompleted: {
-                    if (steamInfoTextMetrics.width > root.bannerWidth) {
-                        steamInfoText.wrapMode = Text.WordWrap;
-                    }
-                }
-            }
             TextMetrics {
                 id: cantAccessTextMetrics
                 font: cantAccessText.font
@@ -372,7 +346,7 @@ Item {
             HifiStylesUit.ShortcutText {
                 id: cantAccessText
                 z: 10
-                visible: !linkAccountBody.linkSteam
+                visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus
                 anchors {
                     top: loginButton.bottom
                     topMargin: hifi.dimensions.contentSpacing.y
@@ -423,10 +397,10 @@ Item {
                 buttonGlyphSize: 24
                 buttonGlyphRightMargin: 10
                 onClicked: {
-                    // if (loginDialog.isOculusStoreRunning()) {
-                    //     linkAccountBody.withOculus = true;
-                    //     loginDialog.loginThroughSteam();
-                    // } else
+                    if (loginDialog.isOculusRunning()) {
+                        linkAccountBody.withOculus = true;
+                        loginDialog.loginThroughOculus();
+                    } else
                     if (loginDialog.isSteamRunning()) {
                         linkAccountBody.withSteam = true;
                         loginDialog.loginThroughSteam();
@@ -446,18 +420,17 @@ Item {
                     }
 
                     bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader,
-                        "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam });
+                        "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus });
                 }
                 Component.onCompleted: {
-                    if (linkAccountBody.linkSteam) {
+                    if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
                         continueButton.visible = false;
                         return;
                     }
-                    // if (loginDialog.isOculusStoreRunning()) {
-                    //     continueButton.text = qsTr("CONTINUE WITH OCULUS");
-                    //     continueButton.buttonGlyph = hifi.glyphs.oculus;
-                    // } else
-                    if (loginDialog.isSteamRunning()) {
+                    if (loginDialog.isOculusRunning()) {
+                        continueButton.text = qsTr("CONTINUE WITH OCULUS");
+                        continueButton.buttonGlyph = hifi.glyphs.oculus;
+                    } else if (loginDialog.isSteamRunning()) {
                         continueButton.text = qsTr("CONTINUE WITH STEAM");
                         continueButton.buttonGlyph = hifi.glyphs.steamSquare;
                     } else {
@@ -470,7 +443,7 @@ Item {
             id: signUpContainer
             width: loginContainer.width
             height: signUpTextMetrics.height
-            visible: !linkAccountBody.linkSteam
+            visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus
             anchors {
                 left: loginContainer.left
                 top: loginContainer.bottom
@@ -519,7 +492,7 @@ Item {
                         UserActivityLogger.logAction("encourageLoginDialog", data);
                     }
                     bodyLoader.setSource("SignUpBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader,
-                        "errorString": "", "linkSteam": linkAccountBody.linkSteam });
+                        "errorString": "" });
                 }
             }
         }
@@ -543,7 +516,7 @@ Item {
             fontFamily: linkAccountBody.fontFamily
             fontSize: linkAccountBody.fontSize
             fontBold: linkAccountBody.fontBold
-            visible: linkAccountBody.loginDialogPoppedUp && !linkAccountBody.linkSteam;
+            visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus;
             onClicked: {
                 if (linkAccountBody.loginDialogPoppedUp) {
                     var data = {
diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml
index 5e4a6c4cb3..583f00583b 100644
--- a/interface/resources/qml/LoginDialog/LoggingInBody.qml
+++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml
@@ -29,6 +29,8 @@ Item {
     property bool withSteam: withSteam
     property bool withOculus: withOculus
     property bool linkSteam: linkSteam
+    property bool linkOculus: linkOculus
+    property bool createOculus: createOculus
 
     readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
 
@@ -75,15 +77,25 @@ Item {
         }
     }
 
+    Timer {
+        id: oculusSuccessTimer
+        interval: 500;
+        running: false;
+        repeat: false;
+        onTriggered: {
+            loginDialog.loginThroughOculus();
+            init();
+        }
+    }
+
     function init() {
         // For the process of logging in.
         loggingInText.wrapMode = Text.NoWrap;
-
-        if (loggingInBody.linkSteam) {
+        if (loggingInBody.createOculus) {
+            loggingInGlyph.text = hifi.glyphs.oculus;
             loggingInGlyph.visible = true;
-            loggingInText.text = "Linking to Steam";
+            loggingInText.text = "Creating account with Oculus";
             loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
-            loginDialog.linkSteam();
         } else if (loggingInBody.withSteam) {
             loggingInGlyph.visible = true;
             loggingInText.text = "Logging in to Steam";
@@ -100,12 +112,18 @@ Item {
         loggingInSpinner.visible = true;
     }
     function loadingSuccess() {
-        loggingInSpinner.visible = false;
         if (loggingInBody.linkSteam) {
             loggingInText.text = "Linking to Steam";
+            loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
             loginDialog.linkSteam();
             return;
+        } else if (loggingInBody.linkOculus) {
+            loggingInText.text = "Linking to Oculus";
+            loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
+            loginDialog.linkOculus();
+            return;
         }
+        loggingInSpinner.visible = false;
         if (loggingInBody.withSteam) {
             // reset the flag.
             loggingInGlyph.visible = false;
@@ -246,6 +264,26 @@ Item {
                     verticalAlignment: Text.AlignVCenter;
                     visible: false;
                 }
+                HifiControlsUit.Button {
+                    id: okButton;
+                    width: d.minWidthButton
+                    height: d.minHeightButton
+                    text: qsTr("OK")
+                    color: hifi.buttons.white
+                    anchors {
+                        top: loggedInGlyph.bottom
+                        topMargin: 3 * hifi.dimensions.contentSpacing.y
+                        left: parent.left
+                        leftMargin: (parent.width - width) / 2;
+                    }
+                    onClicked: {
+                        root.tryDestroy();
+                        if (loginDialog.getLoginDialogPoppedUp()) {
+                            loginDialog.dismissLoginDialog();
+                        }
+                    }
+                    visible: false
+                }
             }
         }
     }
@@ -257,6 +295,34 @@ Item {
 
     Connections {
         target: loginDialog
+        onHandleCreateCompleted: {
+            console.log("Create Succeeded")
+            if (loggingInBody.withOculus) {
+                if (loggingInBody.loginDialogPoppedUp) {
+                    var data = {
+                        "action": "user created Oculus account successfully"
+                    };
+                    UserActivityLogger.logAction("encourageLoginDialog", data);
+                }
+                loggingInBody.createOculus = false;
+                loggingInText.text = "Account created!";
+                loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
+                oculusSuccessTimer.start();
+            }
+        }
+        onHandleCreateFailed: {
+            console.log("Create Failed: " + error);
+            if (loggingInBody.withOculus) {
+                if (loggingInBody.loginDialogPoppedUp) {
+                    var data = {
+                        "action": "user created Oculus account unsuccessfully"
+                    };
+                    UserActivityLogger.logAction("encourageLoginDialog", data);
+                }
+                bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
+                    "withOculus": loggingInBody.withOculus, "errorString": error });
+            }
+        }
         onHandleLinkCompleted: {
             console.log("Link Succeeded");
             if (loggingInBody.linkSteam) {
@@ -267,21 +333,40 @@ Item {
                     };
                     UserActivityLogger.logAction("encourageLoginDialog", data);
                 }
-
-                loggingInBody.loadingSuccess();
+            } else if (loggingInBody.linkOculus) {
+                loggingInBody.linkOculus = false;
+                if (loggingInBody.loginDialogPoppedUp) {
+                    var data = {
+                        "action": "user linked Oculus with their hifi account credentials successfully"
+                    };
+                    UserActivityLogger.logAction("encourageLoginDialog", data);
+                }
             }
+            loggingInBody.loadingSuccess();
         }
         onHandleLinkFailed: {
             console.log("Link Failed: " + error);
-            if (loggingInBody.linkSteam) {
+            loggingInSpinner.visible = false;
+            if (loggingInBody.linkOculus) {
+                loggingInText.text = "Oculus failed to link";
+                if (loggingInBody.loginDialogPoppedUp) {
+                    var data = {
+                        "action": "user linked Oculus unsuccessfully"
+                    };
+                    UserActivityLogger.logAction("encourageLoginDialog", data);
+                }
+                okButton.visible = true;
+            } else if (loggingInBody.linkSteam){
                 if (loggingInBody.loginDialogPoppedUp) {
                     var data = {
                         "action": "user linked Steam unsuccessfully"
                     };
                     UserActivityLogger.logAction("encourageLoginDialog", data);
                 }
+            } else {
+                bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": loggingInBody.linkSteam,
+                    "linkOculus": loggingInBody.linkOculus, "errorString": error });
             }
-            bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": true, "errorString": error });
         }
 
         onHandleLoginCompleted: {
@@ -292,8 +377,19 @@ Item {
         onHandleLoginFailed: {
             console.log("Login Failed")
             loggingInSpinner.visible = false;
+            loggingInGlyph.visible = false;
             var errorString = "";
-            if (loggingInBody.linkSteam && loggingInBody.withSteam) {
+            if (loggingInBody.linkOculus && loggingInBody.withOculus) {
+                errorString = "Username or password is incorrect.";
+                if (loggingInBody.loginDialogPoppedUp) {
+                    var data = {
+                        "action": "user failed to link Oculus with their hifi account credentials"
+                    };
+                    UserActivityLogger.logAction("encourageLoginDialog", data);
+                }
+                bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
+                    "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
+            } else if (loggingInBody.linkSteam && loggingInBody.withSteam) {
                 errorString = "Username or password is incorrect.";
                 if (loggingInBody.loginDialogPoppedUp) {
                     var data = {
@@ -301,9 +397,9 @@ Item {
                     };
                     UserActivityLogger.logAction("encourageLoginDialog", data);
                 }
-                bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "linkSteam": loggingInBody.linkSteam, "errorString": errorString });
+                bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
+                    "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
             } else if (loggingInBody.withSteam) {
-                loggingInGlyph.visible = false;
                 errorString = "Your Steam authentication has failed. Please make sure you are logged into Steam and try again.";
                 if (loggingInBody.loginDialogPoppedUp) {
                     var data = {
@@ -311,19 +407,19 @@ Item {
                     };
                     UserActivityLogger.logAction("encourageLoginDialog", data);
                 }
-                bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "errorString": errorString });
+                bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
+                    "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
             } else if (loggingInBody.withOculus) {
-                loggingInGlyph.visible = false;
-                errorString = "Your Oculus authentication has failed. Please make sure you are logged into Oculus and try again."
+                errorString = "Your Oculus account is not connected to an existing High Fidelity account. Please create a new one."
                 if (loggingInBody.loginDialogPoppedUp) {
                     var data = {
                         "action": "user failed to authenticate with Oculus to log in"
                     };
                     UserActivityLogger.logAction("encourageLoginDialog", data);
                 }
-                bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString });
-            }
-            else {
+                bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
+                    "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
+            } else {
                 errorString = "Username or password is incorrect.";
                 if (loggingInBody.loginDialogPoppedUp) {
                     var data = {
diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml
index 3ba66391e6..64df9089a1 100644
--- a/interface/resources/qml/LoginDialog/SignUpBody.qml
+++ b/interface/resources/qml/LoginDialog/SignUpBody.qml
@@ -23,6 +23,7 @@ Item {
     clip: true
     height: root.height
     width: root.width
+    readonly property string termsContainerText: qsTr("By signing up, you agree to High Fidelity's Terms of Service")
     property int textFieldHeight: 31
     property string fontFamily: "Raleway"
     property int fontSize: 15
@@ -37,7 +38,6 @@ Item {
     onKeyboardRaisedChanged: d.resize();
 
     property string errorString: errorString
-    property bool linkSteam: linkSteam
     property bool lostFocus: false
 
     readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@@ -73,7 +73,6 @@ Item {
 
     function init() {
         // going to/from sign in/up dialog.
-        loginDialog.isLogIn = false;
         emailField.placeholderText = "Email";
         emailField.text = "";
         emailField.anchors.top = usernameField.bottom;
@@ -353,7 +352,7 @@ Item {
                         }
                         UserActivityLogger.logAction("encourageLoginDialog", data);
                     }
-                    bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": signUpBody.linkSteam });
+                    bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
                 }
             }
             HifiControlsUit.Button {
@@ -380,6 +379,54 @@ Item {
                     signUpBody.signup();
                 }
             }
+            Item {
+                id: termsContainer
+                width: parent.width
+                height: termsTextMetrics.height
+                anchors {
+                    top: signUpButton.bottom
+                    horizontalCenter: parent.horizontalCenter
+                    topMargin: 2 * hifi.dimensions.contentSpacing.y
+                    left: parent.left
+                }
+                TextMetrics {
+                    id: termsTextMetrics
+                    font: termsText.font
+                    text: signUpBody.termsContainerText
+                    Component.onCompleted: {
+                        // with the link.
+                        termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
+                    }
+                }
+
+                HifiStylesUit.InfoItem {
+                    id: termsText
+                    text: signUpBody.termsContainerText
+                    font.family: signUpBody.fontFamily
+                    font.pixelSize: signUpBody.fontSize
+                    font.bold: signUpBody.fontBold
+                    wrapMode: Text.WordWrap
+                    color: hifi.colors.white
+                    linkColor: hifi.colors.blueAccent
+                    lineHeight: 1
+                    lineHeightMode: Text.ProportionalHeight
+
+                    onLinkActivated: loginDialog.openUrl(link);
+
+                    Component.onCompleted: {
+                        if (termsTextMetrics.width > root.bannerWidth) {
+                            termsText.width = root.bannerWidth;
+                            termsText.wrapMode = Text.WordWrap;
+                            additionalText.verticalAlignment = Text.AlignLeft;
+                            additionalText.horizontalAlignment = Text.AlignLeft;
+                            termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
+                            termsContainer.anchors.left = buttons.left;
+                        } else {
+                            termsText.anchors.centerIn = termsContainer;
+                        }
+                    }
+                }
+            }
         }
     }
 
@@ -433,14 +480,15 @@ Item {
 
             if (errorString !== "") {
                 loginErrorMessage.visible = true;
+                var errorLength = errorString.split(/\r\n|\r|\n/).length;
                 var errorStringEdited = errorString.replace(/[\n\r]+/g, "\n");
                 loginErrorMessage.text = errorStringEdited;
-                loginErrorMessageTextMetrics.text = errorString;
-                if (loginErrorMessageTextMetrics.width > usernameField.width) {
+                if (errorLength > 1.0) {
+                    loginErrorMessage.width = root.bannerWidth;
                     loginErrorMessage.wrapMode = Text.WordWrap;
                     loginErrorMessage.verticalAlignment = Text.AlignLeft;
                     loginErrorMessage.horizontalAlignment = Text.AlignLeft;
-                    errorContainer.height = (loginErrorMessageTextMetrics.width / usernameField.width) * loginErrorMessageTextMetrics.height;
+                    errorContainer.height = errorLength * loginErrorMessageTextMetrics.height;
                 }
                 errorContainer.anchors.bottom = usernameField.top;
                 errorContainer.anchors.bottomMargin = hifi.dimensions.contentSpacing.y;
diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml
index af46fc0223..2c8e61a29a 100644
--- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml
+++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml
@@ -19,6 +19,7 @@ import TabletScriptingInterface 1.0
 Item {
     id: usernameCollisionBody
     clip: true
+    readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
     width: root.width
     height: root.height
     readonly property string fontFamily: "Raleway"
@@ -26,13 +27,18 @@ Item {
     readonly property int textFieldFontSize: 18
     readonly property bool fontBold: true
 
-    readonly property bool withSteam: withSteam
+    property bool withSteam: withSteam
+    property bool withOculus: withOculus
 
     readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
 
     function create() {
         mainTextContainer.visible = false
-        loginDialog.createAccountFromSteam(textField.text);
+        if (usernameCollisionBody.withOculus) {
+            loginDialog.createAccountFromOculus(textField.text);
+        } else if (usernameCollisionBody.withSteam) {
+            loginDialog.createAccountFromSteam(textField.text);
+        }
     }
 
     property bool keyboardEnabled: false
@@ -90,12 +96,19 @@ Item {
             font.family: usernameCollisionBody.fontFamily
             font.pixelSize: usernameCollisionBody.fontSize
             font.bold: usernameCollisionBody.fontBold
-            text: qsTr("Your Steam username is not available.");
+            text: qsTr("");
             wrapMode: Text.WordWrap
             color: hifi.colors.redAccent
             lineHeight: 1
             lineHeightMode: Text.ProportionalHeight
             horizontalAlignment: Text.AlignHCenter
+            Component.onCompleted: {
+                if (usernameCollisionBody.withOculus) {
+                    text = qsTr("Your Oculus username is not available.");
+                } else if (usernameCollisionBody.withSteam) {
+                    text = qsTr("Your Steam username is not available.");
+                }
+            }
         }
 
 
@@ -164,7 +177,8 @@ Item {
                 fontSize: usernameCollisionBody.fontSize
                 fontBold: usernameCollisionBody.fontBold
                 onClicked: {
-                    bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "" });
+                    bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam,
+                        "withOculus": usernameCollisionBody.withOculus, "errorString": "" });
                 }
             }
             HifiControlsUit.Button {
@@ -187,6 +201,55 @@ Item {
                 }
             }
         }
+        Item {
+            id: termsContainer
+            width: parent.width
+            height: termsTextMetrics.height
+            anchors {
+                top: buttons.bottom
+                horizontalCenter: parent.horizontalCenter
+                topMargin: 2 * hifi.dimensions.contentSpacing.y
+                left: parent.left
+                leftMargin: (parent.width - buttons.width) / 2
+            }
+            TextMetrics {
+                id: termsTextMetrics
+                font: termsText.font
+                text: usernameCollisionBody.termsContainerText
+                Component.onCompleted: {
+                    // with the link.
+                    termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
+                }
+            }
+
+            HifiStylesUit.InfoItem {
+                id: termsText
+                text: usernameCollisionBody.termsContainerText
+                font.family: usernameCollisionBody.fontFamily
+                font.pixelSize: usernameCollisionBody.fontSize
+                font.bold: usernameCollisionBody.fontBold
+                wrapMode: Text.WordWrap
+                color: hifi.colors.white
+                linkColor: hifi.colors.blueAccent
+                lineHeight: 1
+                lineHeightMode: Text.ProportionalHeight
+
+                onLinkActivated: loginDialog.openUrl(link);
+
+                Component.onCompleted: {
+                    if (termsTextMetrics.width > root.bannerWidth) {
+                        termsText.width = root.bannerWidth;
+                        termsText.wrapMode = Text.WordWrap;
+                        additionalText.verticalAlignment = Text.AlignLeft;
+                        additionalText.horizontalAlignment = Text.AlignLeft;
+                        termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
+                        termsContainer.anchors.left = buttons.left;
+                    } else {
+                        termsText.anchors.centerIn = termsContainer;
+                    }
+                }
+            }
+        }
     }
 
     Component.onCompleted: {
@@ -201,18 +264,25 @@ Item {
         target: loginDialog
         onHandleCreateCompleted: {
             console.log("Create Succeeded");
-            if (usernameCollisionBody.withSteam) {
+            if (usernameCollisionBody.withOculus) {
+                if (usernameCollisionBody.loginDialogPoppedUp) {
+                    var data = {
+                        "action": "user created a profile with Oculus successfully in the username collision screen"
+                    }
+                    UserActivityLogger.logAction("encourageLoginDialog", data);
+                }
+                loginDialog.loginThroughOculus();
+            } else if (usernameCollisionBody.withSteam) {
                 if (usernameCollisionBody.loginDialogPoppedUp) {
                     var data = {
                         "action": "user created a profile with Steam successfully in the username collision screen"
                     }
                     UserActivityLogger.logAction("encourageLoginDialog", data);
                 }
-
                 loginDialog.loginThroughSteam();
             }
-
-            bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, "linkSteam": false })
+            bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam,
+                "withOculus": usernameCollisionBody.withOculus, "linkSteam": false, "linkOculus": false })
         }
         onHandleCreateFailed: {
             console.log("Create Failed: " + error)
diff --git a/interface/resources/qml/OverlayLoginDialog.qml b/interface/resources/qml/OverlayLoginDialog.qml
index 8f709af2ab..0ad2c57e5f 100644
--- a/interface/resources/qml/OverlayLoginDialog.qml
+++ b/interface/resources/qml/OverlayLoginDialog.qml
@@ -150,6 +150,6 @@ FocusScope {
 
     Component.onCompleted: {
         keyboardTimer.start();
-        bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
+        bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
     }
 }
diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml
index 3ad17430cd..8d6444bc0e 100644
--- a/interface/resources/qml/dialogs/TabletLoginDialog.qml
+++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml
@@ -14,6 +14,8 @@ import QtQuick 2.5
 import controlsUit 1.0 as HifiControlsUit
 import stylesUit 1.0 as HifiStylesUit
 
+import TabletScriptingInterface 1.0
+
 import "../LoginDialog"
 
 FocusScope {
@@ -25,10 +27,9 @@ FocusScope {
     width: parent.width
     height: parent.height
 
-    signal sendToScript(var message);
-    signal canceled();
+    property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system");
 
-    property bool isHMD: false
+    property bool isHMD: HMD.active
     property bool gotoPreviousApp: false;
 
     property bool keyboardEnabled: false
@@ -52,6 +53,7 @@ FocusScope {
     }
 
     function tryDestroy() {
+        tabletProxy.gotoHomeScreen();
     }
 
     MouseArea {
@@ -76,7 +78,7 @@ FocusScope {
         interval: 200
 
         onTriggered: {
-            if (MenuInterface.isOptionChecked("Use 3D Keyboard")) {
+            if (MenuInterface.isOptionChecked("Use 3D Keyboard") && root.isHMD) {
                 KeyboardScriptingInterface.raised = true;
             }
         }
@@ -169,11 +171,13 @@ FocusScope {
 
     Component.onDestruction: {
         loginKeyboard.raised = false;
-        KeyboardScriptingInterface.raised = false;
+        if (root.isHMD) {
+            KeyboardScriptingInterface.raised = false;
+        }
     }
 
     Component.onCompleted: {
         keyboardTimer.start();
-        bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
+        bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
     }
 }
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 68ac05ef18..30d422949f 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -120,6 +120,7 @@
 #include <plugins/PluginManager.h>
 #include <plugins/PluginUtils.h>
 #include <plugins/SteamClientPlugin.h>
+#include <plugins/OculusPlatformPlugin.h>
 #include <plugins/InputConfiguration.h>
 #include <RecordingScriptingInterface.h>
 #include <render/EngineStats.h>
@@ -800,7 +801,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
     if (auto steamClient = pluginManager->getSteamClientPlugin()) {
         steamClient->init();
     }
-
     PROFILE_SET_THREAD_NAME("Main Thread");
 
 #if defined(Q_OS_WIN)
@@ -2737,6 +2737,7 @@ Application::~Application() {
     if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
         steamClient->shutdown();
     }
+
     DependencyManager::destroy<PluginManager>();
 
     DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache
@@ -4873,6 +4874,10 @@ void Application::idle() {
         steamClient->runCallbacks();
     }
 
+    if (auto oculusPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
+        oculusPlugin->handleOVREvents();
+    }
+
     float secondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_MSEC / MSECS_PER_SECOND;
     _lastTimeUpdated.start();
 
diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp
index 6e9c91785f..4359318833 100644
--- a/interface/src/ui/LoginDialog.cpp
+++ b/interface/src/ui/LoginDialog.cpp
@@ -18,6 +18,7 @@
 
 #include <plugins/PluginManager.h>
 #include <plugins/SteamClientPlugin.h>
+#include <plugins/OculusPlatformPlugin.h>
 #include <shared/GlobalAppProperties.h>
 #include <ui/TabletScriptingInterface.h>
 #include <UserActivityLogger.h>
@@ -109,8 +110,16 @@ bool LoginDialog::isSteamRunning() const {
     return steamClient && steamClient->isRunning();
 }
 
-bool LoginDialog::isOculusStoreRunning() const {
-    return qApp->property(hifi::properties::OCULUS_STORE).toBool();
+bool LoginDialog::isOculusRunning() const {
+    auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin();
+    return (oculusPlatformPlugin && oculusPlatformPlugin->isRunning());
+}
+
+QString LoginDialog::oculusUserID() const {
+    if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
+        return oculusPlatformPlugin->getOculusUserID();
+    }
+    return "";
 }
 
 void LoginDialog::dismissLoginDialog() {
@@ -126,6 +135,79 @@ void LoginDialog::login(const QString& username, const QString& password) const
     DependencyManager::get<AccountManager>()->requestAccessToken(username, password);
 }
 
+void LoginDialog::loginThroughOculus() {
+   qDebug() << "Attempting to login through Oculus";
+    if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
+       oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
+            DependencyManager::get<AccountManager>()->requestAccessTokenWithOculus(nonce, oculusID);
+        });
+    }
+}
+
+void LoginDialog::linkOculus() {
+    qDebug() << "Attempting to link Oculus account";
+    if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
+        oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
+            if (nonce.isEmpty() || oculusID.isEmpty()) {
+                emit handleLoginFailed();
+                return;
+            }
+
+            JSONCallbackParameters callbackParams;
+            callbackParams.callbackReceiver = this;
+            callbackParams.jsonCallbackMethod = "linkCompleted";
+            callbackParams.errorCallbackMethod = "linkFailed";
+            const QString LINK_OCULUS_PATH = "api/v1/user/oculus/link";
+
+            QJsonObject payload;
+            payload["oculus_nonce"] = nonce;
+            payload["oculus_id"] = oculusID;
+
+            auto accountManager = DependencyManager::get<AccountManager>();
+            accountManager->sendRequest(LINK_OCULUS_PATH, AccountManagerAuth::Required,
+                                        QNetworkAccessManager::PostOperation, callbackParams,
+                                        QJsonDocument(payload).toJson());
+        });
+    }
+}
+
+void LoginDialog::createAccountFromOculus(QString email, QString username, QString password) {
+    qDebug() << "Attempting to create account from Oculus info";
+    if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
+        oculusPlatformPlugin->requestNonceAndUserID([this, email, username, password] (QString nonce, QString oculusID) {
+            if (nonce.isEmpty() || oculusID.isEmpty()) {
+                emit handleLoginFailed();
+                return;
+            }
+
+            JSONCallbackParameters callbackParams;
+            callbackParams.callbackReceiver = this;
+            callbackParams.jsonCallbackMethod = "createCompleted";
+            callbackParams.errorCallbackMethod = "createFailed";
+
+            const QString CREATE_ACCOUNT_FROM_OCULUS_PATH = "api/v1/user/oculus/create";
+
+            QJsonObject payload;
+            payload["oculus_nonce"] = nonce;
+            payload["oculus_id"] = oculusID;
+            if (!email.isEmpty()) {
+                payload["email"] = email;
+            }
+            if (!username.isEmpty()) {
+                payload["username"] = username;
+            }
+            if (!password.isEmpty()) {
+                payload["password"] = password;
+            }
+
+            auto accountManager = DependencyManager::get<AccountManager>();
+            accountManager->sendRequest(CREATE_ACCOUNT_FROM_OCULUS_PATH, AccountManagerAuth::None,
+                                        QNetworkAccessManager::PostOperation, callbackParams,
+                                        QJsonDocument(payload).toJson());
+            });
+    }
+}
+
 void LoginDialog::loginThroughSteam() {
     qDebug() << "Attempting to login through Steam";
     if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@@ -157,7 +239,7 @@ void LoginDialog::linkSteam() {
             const QString LINK_STEAM_PATH = "api/v1/user/steam/link";
 
             QJsonObject payload;
-            payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
+            payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket));
 
             auto accountManager = DependencyManager::get<AccountManager>();
             accountManager->sendRequest(LINK_STEAM_PATH, AccountManagerAuth::Required,
@@ -184,9 +266,9 @@ void LoginDialog::createAccountFromSteam(QString username) {
             const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create";
 
             QJsonObject payload;
-            payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
+            payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket));
             if (!username.isEmpty()) {
-                payload.insert("username", QJsonValue::fromVariant(QVariant(username)));
+                payload["username"] = username;
             }
 
             auto accountManager = DependencyManager::get<AccountManager>();
@@ -214,6 +296,45 @@ void LoginDialog::createCompleted(QNetworkReply* reply) {
 }
 
 void LoginDialog::createFailed(QNetworkReply* reply) {
+    if (isOculusRunning()) {
+        auto replyData = reply->readAll();
+        QJsonParseError parseError;
+        auto doc = QJsonDocument::fromJson(replyData, &parseError);
+        if (parseError.error != QJsonParseError::NoError) {
+            emit handleCreateFailed(reply->errorString());
+            return;
+        }
+        auto data = doc["data"];
+        auto error = data["error"];
+        auto oculusError = data["oculus"];
+        auto user = error["username"].toArray();
+        auto uid = error["uid"].toArray();
+        auto email = error["email"].toArray();
+        auto password = error["password"].toArray();
+        QString reply;
+        if (uid[0].isString()) {
+            emit handleCreateFailed("Oculus ID " + uid[0].toString() + ".");
+            return;
+        }
+        if (user[0].isString()) {
+            reply = "Username " + user[0].toString() + ".";
+        }
+        if (email[0].isString()) {
+            reply.append((!reply.isEmpty()) ? "\n" : "");
+            reply.append("Email " + email[0].toString() + ".");
+        }
+        if (password[0].isString()) {
+            reply.append((!reply.isEmpty()) ? "\n" : "");
+            reply.append("Password " + password[0].toString() + ".");
+        }
+        if (!oculusError.isNull() && !oculusError.isUndefined()) {
+            emit handleCreateFailed("Could not verify token with Oculus. Please try again.");
+            return;
+        } else {
+            emit handleCreateFailed(reply);
+            return;
+        }
+    }
     emit handleCreateFailed(reply->errorString());
 }
 
diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h
index 2714d654bf..7c932932cf 100644
--- a/interface/src/ui/LoginDialog.h
+++ b/interface/src/ui/LoginDialog.h
@@ -22,7 +22,6 @@ extern const QUrl OVERLAY_LOGIN_DIALOG;
 
 class LoginDialog : public OffscreenQmlDialog {
     Q_OBJECT
-    Q_PROPERTY(bool isLogIn READ getIsLogIn WRITE setIsLogIn)
     HIFI_QML_DECL
 
 public:
@@ -67,24 +66,23 @@ protected slots:
     Q_INVOKABLE void dismissLoginDialog();
 
     Q_INVOKABLE bool isSteamRunning() const;
-    Q_INVOKABLE bool isOculusStoreRunning() const;
+    Q_INVOKABLE bool isOculusRunning() const;
+
+    Q_INVOKABLE QString oculusUserID() const;
 
     Q_INVOKABLE void login(const QString& username, const QString& password) const;
     Q_INVOKABLE void loginThroughSteam();
     Q_INVOKABLE void linkSteam();
     Q_INVOKABLE void createAccountFromSteam(QString username = QString());
+    Q_INVOKABLE void loginThroughOculus();
+    Q_INVOKABLE void linkOculus();
+    Q_INVOKABLE void createAccountFromOculus(QString email = QString(), QString username = QString(), QString password = QString());
 
     Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
 
     Q_INVOKABLE void openUrl(const QString& url) const;
 
     Q_INVOKABLE bool getLoginDialogPoppedUp() const;
-
-private:
-    bool getIsLogIn() const { return _isLogIn; }
-    void setIsLogIn(const bool isLogIn) { _isLogIn = isLogIn; }
-
-    bool _isLogIn{ false };
 };
 
 #endif // hifi_LoginDialog_h
diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h
index a061a4c923..944d5e89d1 100644
--- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h
@@ -31,6 +31,8 @@ public:
 
     virtual void compositeExtra() override;
 
+    virtual void pluginUpdate() override {};
+
 protected:
     mutable bool _isThrottled = false;
 
diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h
index 11563b3798..e4ff1b8b37 100644
--- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h
@@ -20,6 +20,7 @@ public:
     QImage getScreenshot(float aspectRatio = 0.0f) const override;
     QImage getSecondaryCameraScreenshot() const override;
     void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
+    void pluginUpdate() override {};
 private:
     static const QString NAME;
 };
diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
index a56daaad83..4aeacbe05c 100644
--- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
@@ -46,6 +46,8 @@ public:
 
     virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; };
 
+    void pluginUpdate() override {};
+
 signals:
     void hmdMountedChanged();
     void hmdVisibleChanged(bool visible);
diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h
index 5a7ca24059..a55bde0f4e 100644
--- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h
@@ -28,6 +28,8 @@ public:
     // to the HMD plugins.  
     //virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override;
 
+    virtual void pluginUpdate() override {};
+
 protected:
     virtual bool internalActivate() override;
     virtual void internalDeactivate() override;
diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp
index 989661cb81..f74b337ee7 100644
--- a/libraries/networking/src/AccountManager.cpp
+++ b/libraries/networking/src/AccountManager.cpp
@@ -588,6 +588,29 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) {
     connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
 }
 
+void AccountManager::requestAccessTokenWithOculus(const QString& nonce, const QString &oculusID) {
+    QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
+
+    QNetworkRequest request;
+    request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
+
+    QUrl grantURL = _authURL;
+    grantURL.setPath("/oauth/token");
+
+    QByteArray postData;
+    postData.append("grant_type=password&");
+    postData.append("oculus_nonce=" + nonce + "&");
+    postData.append("oculus_id=" + oculusID + "&");
+    postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
+
+    request.setUrl(grantURL);
+    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+
+    QNetworkReply* requestReply = networkAccessManager.post(request, postData);
+    connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished);
+    connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
+}
+
 void AccountManager::refreshAccessToken() {
 
     // we can't refresh our access token if we don't have a refresh token, so check for that first
diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h
index ca2b826c98..477488031d 100644
--- a/libraries/networking/src/AccountManager.h
+++ b/libraries/networking/src/AccountManager.h
@@ -106,6 +106,7 @@ public:
 public slots:
     void requestAccessToken(const QString& login, const QString& password);
     void requestAccessTokenWithSteam(QByteArray authSessionTicket);
+    void requestAccessTokenWithOculus(const QString& nonce, const QString& oculusID);
     void requestAccessTokenWithAuthCode(const QString& authCode,
                                         const QString& clientId,
                                         const QString& clientSecret,
diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h
index ad49ceafe6..fde43e7a5b 100644
--- a/libraries/plugins/src/plugins/DisplayPlugin.h
+++ b/libraries/plugins/src/plugins/DisplayPlugin.h
@@ -217,6 +217,9 @@ public:
 
     static const QString& MENU_PATH();
 
+    // for updating plugin-related commands. Mimics the input plugin.
+    virtual void pluginUpdate() = 0;
+
 signals:
     void recommendedFramebufferSizeChanged(const QSize& size);
     void resetSensorsRequested();
diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h
index 90746d648e..fc1e12b639 100644
--- a/libraries/plugins/src/plugins/Forward.h
+++ b/libraries/plugins/src/plugins/Forward.h
@@ -21,6 +21,7 @@ class DisplayPlugin;
 class InputPlugin;
 class CodecPlugin;
 class SteamClientPlugin;
+class OculusPlatformPlugin;
 class Plugin;
 class PluginContainer;
 class PluginManager;
@@ -35,4 +36,5 @@ using CodecPluginPointer = std::shared_ptr<CodecPlugin>;
 using CodecPluginList = std::vector<CodecPluginPointer>;
 using CodecPluginProvider = std::function<CodecPluginList()>;
 using SteamClientPluginPointer = std::shared_ptr<SteamClientPlugin>;
+using OculusPlatformPluginPointer = std::shared_ptr<OculusPlatformPlugin>;
 using InputPluginSettingsPersister = std::function<void(const InputPluginList&)>;
diff --git a/libraries/plugins/src/plugins/OculusPlatformPlugin.h b/libraries/plugins/src/plugins/OculusPlatformPlugin.h
new file mode 100644
index 0000000000..93bf534c6e
--- /dev/null
+++ b/libraries/plugins/src/plugins/OculusPlatformPlugin.h
@@ -0,0 +1,28 @@
+//
+//  Created by Wayne Chen on 2018/12/20
+//  Copyright 2018 High Fidelity Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2-0.html
+//
+#pragma once
+
+#include <QtCore/QString>
+
+#include <functional>
+
+using NonceUserIDCallback = std::function<void(QString, QString)>;
+
+class OculusPlatformPlugin {
+public:
+    virtual ~OculusPlatformPlugin() = default;
+
+    virtual QString getName() const = 0;
+    virtual QString getOculusUserID() const = 0;
+
+    virtual bool isRunning() const = 0;
+
+    virtual void requestNonceAndUserID(NonceUserIDCallback callback) = 0;
+
+    virtual void handleOVREvents() = 0;
+};
diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp
index 13fa75f030..0d0209e35f 100644
--- a/libraries/plugins/src/plugins/PluginManager.cpp
+++ b/libraries/plugins/src/plugins/PluginManager.cpp
@@ -188,6 +188,22 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() {
     return steamClientPlugin;
 }
 
+const OculusPlatformPluginPointer PluginManager::getOculusPlatformPlugin() {
+    static OculusPlatformPluginPointer oculusPlatformPlugin;
+    static std::once_flag once;
+    std::call_once(once, [&] {
+        // Now grab the dynamic plugins
+        for (auto loader : getLoadedPlugins()) {
+            OculusPlatformProvider* oculusPlatformProvider = qobject_cast<OculusPlatformProvider*>(loader->instance());
+            if (oculusPlatformProvider) {
+                oculusPlatformPlugin = oculusPlatformProvider->getOculusPlatformPlugin();
+                break;
+            }
+        }
+    });
+    return oculusPlatformPlugin;
+}
+
 const DisplayPluginList& PluginManager::getDisplayPlugins() {
     static std::once_flag once;
     static auto deviceAddedCallback = [](QString deviceName) {
diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h
index e340b2fa21..1a578c7406 100644
--- a/libraries/plugins/src/plugins/PluginManager.h
+++ b/libraries/plugins/src/plugins/PluginManager.h
@@ -27,6 +27,7 @@ public:
     const InputPluginList& getInputPlugins();
     const CodecPluginList& getCodecPlugins();
     const SteamClientPluginPointer getSteamClientPlugin();
+    const OculusPlatformPluginPointer getOculusPlatformPlugin();
 
     DisplayPluginList getPreferredDisplayPlugins();
     void setPreferredDisplayPlugins(const QStringList& displays);
diff --git a/libraries/plugins/src/plugins/RuntimePlugin.h b/libraries/plugins/src/plugins/RuntimePlugin.h
index 9a7d6e0638..756b4ff585 100644
--- a/libraries/plugins/src/plugins/RuntimePlugin.h
+++ b/libraries/plugins/src/plugins/RuntimePlugin.h
@@ -51,5 +51,13 @@ public:
     virtual SteamClientPluginPointer getSteamClientPlugin() = 0;
 };
 
+class OculusPlatformProvider {
+public:
+    virtual OculusPlatformPluginPointer getOculusPlatformPlugin() = 0;
+};
+
 #define SteamClientProvider_iid "com.highfidelity.plugins.steamclient"
 Q_DECLARE_INTERFACE(SteamClientProvider, SteamClientProvider_iid)
+
+#define OculusPlatformProvider_iid "com.highfidelity.plugins.oculusplatform"
+Q_DECLARE_INTERFACE(OculusPlatformProvider, OculusPlatformProvider_iid)
\ No newline at end of file
diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp
index f10aba7920..a67e3127e5 100644
--- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp
+++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp
@@ -12,6 +12,7 @@
 #include <display-plugins/CompositorHelper.h>
 #include <gpu/Frame.h>
 #include <gl/Config.h>
+#include <shared/GlobalAppProperties.h>
 
 #include "OculusHelpers.h"
 
@@ -30,7 +31,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
         return false;
     }
 
-    if (ovr::quitRequested(status) || ovr::displayLost(status) || !ovr::handleOVREvents()) {
+    if (ovr::quitRequested(status) || ovr::displayLost(status)) {
         QMetaObject::invokeMethod(qApp, "quit");
         return false;
     }
diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h
index 547d3ee5fe..1abb7cdad7 100644
--- a/plugins/oculus/src/OculusBaseDisplayPlugin.h
+++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h
@@ -13,6 +13,9 @@
 
 #include <OVR_CAPI_GL.h>
 
+#define OVRPL_DISABLED
+#include <OVR_Platform.h>
+
 class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
     using Parent = HmdDisplayPlugin;
 public:
@@ -30,7 +33,7 @@ public:
 
     QRectF getPlayAreaRect() override;
     QVector<glm::vec3> getSensorPositions() override;
-   
+
 protected:
     void customizeContext() override;
     void uncustomizeContext() override;
diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp
index 548afb97ab..2693b9ee7e 100644
--- a/plugins/oculus/src/OculusHelpers.cpp
+++ b/plugins/oculus/src/OculusHelpers.cpp
@@ -296,34 +296,3 @@ controller::Pose hifi::ovr::toControllerPose(ovrHandType hand,
     pose.valid = true;
     return pose;
 }
-
-bool hifi::ovr::handleOVREvents() {
-#ifdef OCULUS_APP_ID
-    if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
-        // pop messages to see if we got a return for an entitlement check
-        ovrMessageHandle message = ovr_PopMessage();
-
-        while (message) {
-            switch (ovr_Message_GetType(message)) {
-                case ovrMessage_Entitlement_GetIsViewerEntitled: {
-                    if (!ovr_Message_IsError(message)) {
-                        // this viewer is entitled, no need to flag anything
-                        qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally";
-                    } else {
-                        // we failed the entitlement check, quit
-                        qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
-                        return false;
-                    }
-                }
-            }
-
-            // free the message handle to cleanup and not leak
-            ovr_FreeMessage(message);
-
-            // pop the next message to check, if there is one
-            message = ovr_PopMessage();
-        }
-    }
-#endif
-    return true;
-}
diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h
index bdfc4434bb..3587117825 100644
--- a/plugins/oculus/src/OculusHelpers.h
+++ b/plugins/oculus/src/OculusHelpers.h
@@ -30,7 +30,6 @@ struct ovr {
     static ovrSessionStatus getStatus(ovrResult& result);
     static ovrTrackingState getTrackingState(double absTime = 0.0, ovrBool latencyMarker = ovrFalse);
     static QString getError();
-    static bool handleOVREvents();
 
     static inline bool quitRequested() { return quitRequested(getStatus()); }
     static inline bool reorientRequested() { return reorientRequested(getStatus()); }
diff --git a/plugins/oculus/src/OculusPlatformPlugin.cpp b/plugins/oculus/src/OculusPlatformPlugin.cpp
new file mode 100644
index 0000000000..27fb98c8b5
--- /dev/null
+++ b/plugins/oculus/src/OculusPlatformPlugin.cpp
@@ -0,0 +1,109 @@
+//
+//  Created by Wayne Chen on 2019/01/08
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "OculusPlatformPlugin.h"
+
+#include <shared/GlobalAppProperties.h>
+
+#include <QMetaObject>
+
+#include "OculusHelpers.h"
+
+QString OculusAPIPlugin::NAME { "Oculus Rift" };
+
+OculusAPIPlugin::OculusAPIPlugin() {
+    _session = hifi::ovr::acquireRenderSession();
+}
+
+OculusAPIPlugin::~OculusAPIPlugin() {
+    hifi::ovr::releaseRenderSession(_session);
+}
+
+bool OculusAPIPlugin::isRunning() const {
+    return (qApp->property(hifi::properties::OCULUS_STORE).toBool());
+}
+
+void OculusAPIPlugin::requestNonceAndUserID(NonceUserIDCallback callback) {
+#ifdef OCULUS_APP_ID
+    _nonceUserIDCallback = callback;
+    ovr_User_GetUserProof();
+    ovr_User_GetLoggedInUser();
+#endif
+}
+
+void OculusAPIPlugin::handleOVREvents() {
+#ifdef OCULUS_APP_ID
+    if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
+        // pop messages to see if we got a return for an entitlement check
+        ovrMessageHandle message { nullptr };
+
+        // pop the next message to check, if there is one
+        while ((message = ovr_PopMessage())) {
+            switch (ovr_Message_GetType(message)) {
+                case ovrMessage_Entitlement_GetIsViewerEntitled: {
+                    if (!ovr_Message_IsError(message)) {
+                        // this viewer is entitled, no need to flag anything
+                        qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally";
+                    } else {
+                        // we failed the entitlement check, quit
+                        qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
+                        QMetaObject::invokeMethod(qApp, "quit");
+                    }
+                    break;
+                }
+                case ovrMessage_User_Get: {
+                    if (!ovr_Message_IsError(message)) {
+                        qCDebug(oculusLog) << "Oculus Platform user retrieval succeeded";
+                        ovrUserHandle user = ovr_Message_GetUser(message);
+                        _user = ovr_User_GetOculusID(user);
+                        // went all the way through the `requestNonceAndUserID()` pipeline successfully.
+                    } else {
+                        qCDebug(oculusLog) << "Oculus Platform user retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
+                        // emit the signal so we don't hang for it anywhere else.
+                        _user = "";
+                    }
+                    break;
+                }
+                case ovrMessage_User_GetLoggedInUser: {
+                    if (!ovr_Message_IsError(message)) {
+                        ovrUserHandle user = ovr_Message_GetUser(message);
+                        _userID = ovr_User_GetID(user);
+                        ovr_User_Get(_userID);
+                    } else {
+                        qCDebug(oculusLog) << "Oculus Platform user ID retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
+                        // emit the signal so we don't hang for it anywhere else.
+                    }
+                    _userIDChanged = true;
+                    break;
+                }
+                case ovrMessage_User_GetUserProof: {
+                    if (!ovr_Message_IsError(message)) {
+                        ovrUserProofHandle userProof = ovr_Message_GetUserProof(message);
+                        _nonce = ovr_UserProof_GetNonce(userProof);
+                        qCDebug(oculusLog) << "Oculus Platform nonce retrieval succeeded: " << _nonce;
+                    } else {
+                        qCDebug(oculusLog) << "Oculus Platform nonce retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
+                        _nonce = "";
+                        // emit the signal so we don't hang for it anywhere else.
+                    }
+                    _nonceChanged = true;
+                    break;
+                }
+            }
+
+            if (_nonceChanged && _userIDChanged) {
+                _nonceUserIDCallback(_nonce, QString::number(_userID));
+                _nonceChanged = _userIDChanged = false;
+            }
+
+            // free the message handle to cleanup and not leak
+            ovr_FreeMessage(message);
+        }
+    }
+#endif
+}
diff --git a/plugins/oculus/src/OculusPlatformPlugin.h b/plugins/oculus/src/OculusPlatformPlugin.h
new file mode 100644
index 0000000000..3d80540419
--- /dev/null
+++ b/plugins/oculus/src/OculusPlatformPlugin.h
@@ -0,0 +1,39 @@
+//
+//  Created by Wayne Chen on 2019/01/08
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include <plugins/OculusPlatformPlugin.h>
+
+#include <OVR_CAPI_GL.h>
+
+#define OVRPL_DISABLED
+#include <OVR_Platform.h>
+
+class OculusAPIPlugin : public OculusPlatformPlugin {
+public:
+    OculusAPIPlugin();
+    virtual ~OculusAPIPlugin();
+    QString getName() const { return NAME; }
+    QString getOculusUserID() const { return _user; };
+
+    bool isRunning() const;
+
+    virtual void requestNonceAndUserID(NonceUserIDCallback callback);
+
+    virtual void handleOVREvents();
+
+private:
+    static QString NAME;
+    NonceUserIDCallback _nonceUserIDCallback;
+    QString _nonce;
+    bool _nonceChanged{ false };
+    bool _userIDChanged{ false };
+    QString _user;
+    ovrID _userID;
+    ovrSession _session;
+};
diff --git a/plugins/oculus/src/OculusProvider.cpp b/plugins/oculus/src/OculusProvider.cpp
index 47ccc5304e..67871b8610 100644
--- a/plugins/oculus/src/OculusProvider.cpp
+++ b/plugins/oculus/src/OculusProvider.cpp
@@ -18,15 +18,18 @@
 
 #include "OculusDisplayPlugin.h"
 #include "OculusDebugDisplayPlugin.h"
+#include "OculusPlatformPlugin.h"
 #include "OculusControllerManager.h"
 
-class OculusProvider : public QObject, public DisplayProvider, InputProvider
+class OculusProvider : public QObject, public DisplayProvider, InputProvider, OculusPlatformProvider
 {
     Q_OBJECT
     Q_PLUGIN_METADATA(IID DisplayProvider_iid FILE "oculus.json")
     Q_INTERFACES(DisplayProvider)
     Q_PLUGIN_METADATA(IID InputProvider_iid FILE "oculus.json")
     Q_INTERFACES(InputProvider)
+    Q_PLUGIN_METADATA(IID OculusPlatformProvider_iid FILE "oculus.json")
+    Q_INTERFACES(OculusPlatformProvider)
 
 public:
     OculusProvider(QObject* parent = nullptr) : QObject(parent) {}
@@ -62,6 +65,15 @@ public:
         return _inputPlugins;
     }
 
+    virtual OculusPlatformPluginPointer getOculusPlatformPlugin() override {
+        static std::once_flag once;
+        std::call_once(once, [&] {
+            _oculusPlatformPlugin = std::make_shared<OculusAPIPlugin>();
+            
+        });
+        return _oculusPlatformPlugin;
+    }
+
     virtual void destroyInputPlugins() override {
         _inputPlugins.clear();
     }
@@ -73,6 +85,7 @@ public:
 private:
     DisplayPluginList _displayPlugins;
     InputPluginList _inputPlugins;
+    OculusPlatformPluginPointer _oculusPlatformPlugin;
 };
 
 #include "OculusProvider.moc"