diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication.meta
new file mode 100644
index 000000000..cd8906d0e
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 60adf4f9040d189499c6dbb852840cf4
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationDataProviderProfile.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationDataProviderProfile.cs
new file mode 100644
index 000000000..6e820ab5b
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationDataProviderProfile.cs
@@ -0,0 +1,63 @@
+// Copyright (c) XRTK. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using UnityEngine;
+using XRTK.Attributes;
+using XRTK.Definitions.Utilities;
+using XRTK.Interfaces.Authentication;
+
+namespace XRTK.Definitions.Authentication
+{
+ ///
+ /// The configuration profile for .
+ ///
+ [CreateAssetMenu(menuName = "Mixed Reality Toolkit/Authentication System/Generic Authentication Data Provider", fileName = "AuthenticationDataProviderProfile", order = (int)CreateProfileMenuItemIndices.AuthenticationSystem)]
+ public class AuthenticationDataProviderProfile : BaseMixedRealityProfile
+ {
+ [SerializeField]
+ [Tooltip("The client ID is the unique application (client) ID assigned to your app by registering your application with the identity provider.")]
+ private string clientId = "";
+
+ ///
+ /// The client ID is the unique application (client) ID assigned to your app by registering your application with the identity provider.
+ ///
+ public string ClientId => clientId;
+
+ [SerializeField]
+ [Tooltip("The identity provider url to request OAuth tokens from.")]
+ private string identityProviderUrl = "";
+
+ ///
+ /// The identity provider url to request OAuth tokens from.
+ ///
+ public string IdentityProviderUrl => identityProviderUrl;
+
+ [SerializeField]
+ [Tooltip("Scopes or Permissions to request access to.")]
+ private string[] scopes = new string[0];
+
+ ///
+ /// The Scopes or Permissions to request access to.
+ ///
+ public string[] Scopes => scopes;
+
+ [SerializeField]
+ [Tooltip("The redirect url is the endpoint the identity provider will send the security tokens back to.")]
+ private string redirectUrl = "";
+
+ ///
+ /// The redirect url is the endpoint the identity provider will send the security tokens back to.
+ ///
+ public string RedirectUrl => redirectUrl;
+
+ [SerializeField]
+ [Prefab(typeof(IAuthenticationHandler))]
+ [Tooltip("The login prefab to display when the user is logging in.")]
+ private GameObject loginPrefab = null;
+
+ ///
+ /// The login prefab to display when the user is logging in.
+ ///
+ public GameObject LoginPrefab => loginPrefab;
+ }
+}
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationDataProviderProfile.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationDataProviderProfile.cs.meta
new file mode 100644
index 000000000..984d7dfcb
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationDataProviderProfile.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c042ff0dbb5def24f99759820314b067
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationSystemProfile.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationSystemProfile.cs
new file mode 100644
index 000000000..a453258ec
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationSystemProfile.cs
@@ -0,0 +1,19 @@
+// Copyright (c) XRTK. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using UnityEngine;
+using XRTK.Interfaces.Authentication;
+
+namespace XRTK.Definitions.Authentication
+{
+ public class AuthenticationSystemProfile : BaseMixedRealityServiceProfile
+ {
+ [SerializeField]
+ private bool cacheUserTokens = true;
+
+ ///
+ /// Remember previously obtained user tokens.
+ ///
+ public bool CacheUserTokens => cacheUserTokens;
+ }
+}
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationSystemProfile.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationSystemProfile.cs.meta
new file mode 100644
index 000000000..bd9598f8e
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Authentication/AuthenticationSystemProfile.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cd7a1e631fa20d9459694f09e2f93fb0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Utilities/ProfileMenuItemIndices.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Utilities/ProfileMenuItemIndices.cs
index ea0378ffc..98e969b65 100644
--- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Utilities/ProfileMenuItemIndices.cs
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/Utilities/ProfileMenuItemIndices.cs
@@ -24,6 +24,7 @@ public enum CreateProfileMenuItemIndices
Networking,
NetworkingDataProviders,
Diagnostics,
+ AuthenticationSystem,
RegisteredServiceProviders,
Settings
}
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication.meta
new file mode 100644
index 000000000..b47281849
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 8da3efad4e4f335459023717118039f2
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticatedAccount.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticatedAccount.cs
new file mode 100644
index 000000000..c0290682a
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticatedAccount.cs
@@ -0,0 +1,26 @@
+// Copyright (c) XRTK. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+namespace XRTK.Interfaces.Authentication
+{
+ ///
+ /// Represents an authenticated user account
+ ///
+ public interface IAuthenticatedAccount
+ {
+ ///
+ /// The username of the authenticated account
+ ///
+ string Username { get; }
+
+ ///
+ /// The access token required for secure access to an online service
+ ///
+ string AccessToken { get; }
+
+ ///
+ /// Gets the that this account identity is associated with.
+ ///
+ IMixedRealityAuthenticationDataProvider Provider { get; }
+ }
+}
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticatedAccount.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticatedAccount.cs.meta
new file mode 100644
index 000000000..b618db907
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticatedAccount.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ea1cb58906310af4c879624cc34e78f4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticationHandler.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticationHandler.cs
new file mode 100644
index 000000000..9048a9e88
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticationHandler.cs
@@ -0,0 +1,14 @@
+// Copyright (c) XRTK. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+namespace XRTK.Interfaces.Authentication
+{
+ public interface IAuthenticationHandler : UnityEngine.EventSystems.IEventSystemHandler
+ {
+ ///
+ /// Display code flow message.
+ ///
+ ///
+ void DisplayCodeFlowMessage(string message);
+ }
+}
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticationHandler.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticationHandler.cs.meta
new file mode 100644
index 000000000..430120d3f
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IAuthenticationHandler.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4baea7e2107271548a960fdf4e5e976b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationDataProvider.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationDataProvider.cs
new file mode 100644
index 000000000..ffeee54f5
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationDataProvider.cs
@@ -0,0 +1,55 @@
+// Copyright (c) XRTK. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using XRTK.Definitions.Authentication;
+
+namespace XRTK.Interfaces.Authentication
+{
+ ///
+ /// Interface contract for specific identity provider implementations for use in the .
+ ///
+ public interface IMixedRealityAuthenticationDataProvider : IMixedRealityDataProvider
+ {
+ ///
+ /// Event called when a user has successfully logged in.
+ ///
+ event Action OnLoggedIn;
+
+ ///
+ /// Event called when a user has logged out.
+ ///
+ event Action OnLoggedOut;
+
+ ///
+ /// The . Null if no user is logged in.
+ ///
+ IAuthenticatedAccount AuthenticatedAccount { get; }
+
+ ///
+ /// Is there currently a valid user logged in with a valid token?
+ ///
+ bool IsUserLoggedIn { get; }
+
+ ///
+ /// Start Login task.
+ ///
+ ///
+ /// This may prompt the user to authenticate with the
+ ///
+ /// Completed .
+ Task LoginAsync();
+
+ ///
+ /// Log the user out.
+ ///
+ ///
+ void Logout(bool reAuthenticate = true);
+
+ ///
+ /// Removes all account tokens from the cache.
+ ///
+ void ClearTokenCache();
+ }
+}
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationDataProvider.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationDataProvider.cs.meta
new file mode 100644
index 000000000..f552789fa
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationDataProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3c0fd10ffac6fb04bab0551b44ca1c1e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationSystem.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationSystem.cs
new file mode 100644
index 000000000..aefb86251
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationSystem.cs
@@ -0,0 +1,65 @@
+// Copyright (c) XRTK. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using XRTK.Definitions.Authentication;
+
+namespace XRTK.Interfaces.Authentication
+{
+ ///
+ /// Provider agnostic Interface contract for user authentication with public client applications.
+ ///
+ public interface IMixedRealityAuthenticationSystem : IMixedRealitySystem
+ {
+ ///
+ /// Signals when the user has been logged in
+ ///
+ event Action OnLoggedIn;
+
+ ///
+ /// Signals when the user has been logged out.
+ ///
+ event Action OnLoggedOut;
+
+ ///
+ /// Should the login tokens be cached?
+ ///
+ bool CacheUserTokens { get; set; }
+
+ ///
+ /// Gets the currently logged in s.
+ ///
+ IReadOnlyCollection ActiveAccounts { get; }
+
+ ///
+ /// All of the currently active s.
+ ///
+ IReadOnlyCollection ActiveAuthenticationProviders { get; }
+
+ ///
+ /// Register a with the system.
+ ///
+ /// The to register.
+ /// True, if the was successfully registered, otherwise false.
+ bool RegisterAuthenticationDataProvider(IMixedRealityAuthenticationDataProvider provider);
+
+ ///
+ /// Unregister a with the system.
+ ///
+ /// The to unregister.
+ /// True, if the was successfully unregistered, otherwise false.
+ bool UnregisterAuthenticationDataProvider(IMixedRealityAuthenticationDataProvider provider);
+
+ ///
+ /// Logs out of all active sessions in all active s.
+ ///
+ void LogOutAllSessions();
+
+ ///
+ /// Clears out all of the token caches for all active s.
+ ///
+ void ClearAllTokenCaches();
+ }
+}
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationSystem.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationSystem.cs.meta
new file mode 100644
index 000000000..a44cf4c32
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/Authentication/IMixedRealityAuthenticationSystem.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cf9563889fed24c49862ab8550ca9414
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication.meta
new file mode 100644
index 000000000..1bf0c5eab
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 8ba5a13650181cc4ab96ae8c9543e682
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication/AuthenticationSystem.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication/AuthenticationSystem.cs
new file mode 100644
index 000000000..308d51a3b
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication/AuthenticationSystem.cs
@@ -0,0 +1,134 @@
+// Copyright (c) XRTK. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using XRTK.Definitions.Authentication;
+using XRTK.Interfaces.Authentication;
+
+namespace XRTK.Services.Authentication
+{
+ ///
+ /// Concrete implementation of the
+ ///
+ [System.Runtime.InteropServices.Guid("B9AA44EA-23C9-4B4A-8DE6-D598E6F638FB")]
+ public class AuthenticationSystem : BaseSystem, IMixedRealityAuthenticationSystem
+ {
+ ///
+ public AuthenticationSystem(AuthenticationSystemProfile profile) : base(profile)
+ {
+ CacheUserTokens = profile.CacheUserTokens;
+ }
+
+ #region IMixedRealityAuthenticationSystem Implementation
+
+ ///
+ public event Action OnLoggedIn;
+
+ ///
+ public event Action OnLoggedOut;
+
+ ///
+ public bool CacheUserTokens { get; set; }
+
+ private readonly HashSet activeAccounts = new HashSet();
+
+ ///
+ public IReadOnlyCollection ActiveAccounts => activeAccounts;
+
+ private readonly HashSet activeDataProviders = new HashSet();
+
+ ///
+ public IReadOnlyCollection ActiveAuthenticationProviders => activeDataProviders;
+
+ ///
+ public bool RegisterAuthenticationDataProvider(IMixedRealityAuthenticationDataProvider provider)
+ {
+ if (activeDataProviders.Contains(provider))
+ {
+ return false;
+ }
+
+ activeDataProviders.Add(provider);
+ LoginEvents(provider, true);
+ return true;
+ }
+
+ ///
+ public bool UnregisterAuthenticationDataProvider(IMixedRealityAuthenticationDataProvider provider)
+ {
+ if (!activeDataProviders.Contains(provider))
+ {
+ return false;
+ }
+
+ LoginEvents(provider, false);
+ activeDataProviders.Remove(provider);
+ return true;
+ }
+
+ ///
+ public void LogOutAllSessions()
+ {
+ foreach (var provider in activeDataProviders)
+ {
+ provider.Logout(false);
+ }
+ }
+
+ ///
+ public void ClearAllTokenCaches()
+ {
+ foreach (var provider in activeDataProviders)
+ {
+ provider.ClearTokenCache();
+ }
+ }
+
+ #endregion IMixedRealityAuthenticationSystem Implementation
+
+ private void LoginEvents(IMixedRealityAuthenticationDataProvider provider, bool isRegistered)
+ {
+ if (activeDataProviders.Contains(provider))
+ {
+ if (isRegistered)
+ {
+ provider.OnLoggedIn += OnProviderLoggedIn;
+ provider.OnLoggedOut += OnProviderLogout;
+ }
+ else
+ {
+ provider.OnLoggedIn -= OnProviderLoggedIn;
+ provider.OnLoggedOut -= OnProviderLogout;
+ }
+ }
+
+ void OnProviderLoggedIn(IAuthenticatedAccount account)
+ {
+ if (!activeAccounts.Contains(account))
+ {
+ activeAccounts.Add(account);
+ OnLoggedIn?.Invoke(provider, account);
+ }
+ else
+ {
+ Debug.LogError($"{Name}:{nameof(OnLoggedOut)}: Account already logged in!");
+ }
+ }
+
+ void OnProviderLogout(IAuthenticatedAccount account)
+ {
+ if (activeAccounts.Contains(account))
+ {
+ activeAccounts.Remove(account);
+ OnLoggedOut?.Invoke(provider, account);
+ }
+ else
+ {
+ Debug.LogError($"{Name}:{nameof(OnLoggedOut)}: Account already logged out!");
+ }
+ }
+ }
+ }
+}
diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication/AuthenticationSystem.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication/AuthenticationSystem.cs.meta
new file mode 100644
index 000000000..afda790ec
--- /dev/null
+++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/Authentication/AuthenticationSystem.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 17991b69050bcce4a8c0a8e5eb8041ab
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant: