Account Services
Accounts
The accounts system is a fully featured user account management system with roles based access control and support for third-party and multi-factor authentication schemes for a variety of popular sites and providers.
Concepts
Users
Users can create accounts with the system by creating a User
object with this system. Users contain a minimal
amount of personally identifiable information (PII). For instance, passwords are stored separately from the primary
user object. This is both for simplicity of the system as well as additional protection of sensitive data.
Roles
The roles
property of the User document contains the list of Role names that the user is a member or owner of.
This makes it more practical to quickly discover the user’s permissions within the larger security system. This
list is automatically updated by the system upon any change to a Role document itself.
External IDs
In order to support single-sign on with external authentication providers it is important to link a user’s
third-party account id with their AcceleratXR id. These IDs are stored in the externalIds` property as an array
of ``<type>:<uid>
mappings that indicate which third-party provider is represented along with the universally
unique identifier for that provider.
Existing external ID links may be re-associated with another account if a user performs a single-sign on attempt in collaboration with an existing AcceleratXR authentication token.
Verifying Accounts
When a new user is created, the account is considered to be unverified
until that user responds to a verification
e-mail or text message sent to the contact provided at the time of creation. The verification e-mail contains a short
lifespan JWT authentication token that must be provided to the /users/:id/verify
endpoint in order to prove the
user received the e-mail at the given address.
If a user changes their e-mail address at any future point, they will be required to verify the address again, even if a previous e-mail is specified again.
Admin Account
On system startup, a single user account is automatically created called the admin
user. This user is given
superuser power within the system to perform any action. The login credentials of this account are logged to the stdout
of the service exactly once. It is important to save this information for future reference. Since this is an account
with root level access it is highly recommended that this account not be used in daily practice, even when
superuser privileges are desired.
Attention
To prevent risk of unauthorized access to the system the default admin
account should never be used from an
external device. Create another user and add them as a member to one of the default Trusted Roles roles
instead.
Secrets
A User Secret
is how passwords, API keys, multi-factor authentication and backup codes are defined and stored
within the system. They provide a method for user’s to authenticate themselves using a trusted known secret.
Passwords
Users that wish to authenticate via a password can create exactly one secret of type password
. Password secrets are hashed
using the Argon2 algorithm in memory before being transmitted to the database to
ensure maximal protection of the password data.
In the event that a password secret is lost or stolen the /users/:id/recover
endpoint can be used to reset the password
secret and provide a new one.
API Keys
Application keys are used to provide programmatic access to the sytem on behalf of a user. The API key is a unique string, typically a hash, that is created for purposes of identifying users for programmatic functions. The API key can be provided by the user upon creation, or randomly generated by the service. Once an API key secret is created the value is returned in the response and can never be returned again.
Similar to password secrets, API key secrets are hashed with the Argon2 algorithm in-memory before being transmitted and stored to the database.
Multi-Factor Authentication
Users desiring an extra level of protection may enable multi-factor authentication. Multi-factor authentication is enabled by creating
a secret of type mfa
. Once created an additional request must be sent to /users/:userId/secrets/:id/enroll
containing a valid
TOTP code to confirm that the end user has successfully registered their MFA device. Once successfully confirmed, all future
authentication requests using a password secret will require MFA validation.
MFA Backup Codes
Additionally, upon successful enrollment of an MFA secret, a set of backup codes are automatically generated and returned to the
user for safe keeping. These codes may be used at any time as a one-time authentication password secret. Backup codes are not
subject to further multi-factor authentication validation. If all backup codes have been used then the user must recover their
account using the /users/:id/recover
endpoint.
Device
A device secret is used to provide frictionless single-sign on authentication by trusted devices. This is typically a deterministic hash made by the trusted device using a universally unique identifier. Device secrets are not subject to multi-factor authentication requirements. Similar to password secrets, device secrets are hashed with the Argon2 algorithm in-memory before being transmitted and stored to the database.
Roles
Roles are a method of organizing a collection of users for the purposes of enabling group based permissions within the system. Within the larger security system, user id’s and roles are used to identify permissions via Access Control Lists. The Access Control List is a construct for defining the permissable operations for a given user and/or role on a given system resource.
Users can be a member and/or owner of any number of roles with no restrictions on membership. Once added to a role, the
system will add the user’s id to the appropriate property of the Role document as well as the User document’s roles
property. This allows easy retrievable of any user’s roles without requiring additional searches.
Members
Users can be assigned as members to a given Role object. A member inherits all permissions of the role to perform actions permissible to that group within the system. Members however only have permission to view Role data and have no permission to modify or delete the role itself.
Owners
A Role owners is a user with full privileges to add, modify and delete a Role, including any and all members, metadata and additional owners. While it is possible to define a user as only an owner of a Role, it is functionally equivalent to being both a member and an owner as the system does not make any distinction between the two. An owner is always a member of the role regardless of whether they are explicitly listed as a member or not.
Trusted Roles
On startup the system will automatically create a set of roles which are considered to be for those users with superuser permissions. These roles are called Trusted Roles and are explicitly declared in the cluster’s configuration setting upon deployment.
Organizations
Attention
Requires license to AcceleratXR Enterprise.
A user Organization is an additional level of abstraction allowing for the grouping of multiple users together into virtual teams.
Members
Each organization has a list of member users. These users have access to any and all resources defined for the organization. While members have permission to view organization data such as other members and owners, they do not have permission to modify or delete an Organization or any of its members.
Owners
An Organization owner is a user in which has full control over the organization itself and all users that are members as well as other owners.
Roles
User roles can be associated with a given organization by prefixing the name of the role with the uid
of the
organization. This makes it easily possible to define a variety of roles, all specific to different organizations.
By default, a set of roles are automatically created when each organization is created corresponding to the Trusted Roles of the system. This effectively grants any owner of the organization superuser permission to perform any action on behalf of the organization.
In this section you’ll find information regarding all of the fundamental concepts that make up the AcceleratXR user management, authentication and permissions system.
Standard Authentication
Basic
The simplest way to authenticate with the AcceleratXR backend is using Basic authentication via the /auth/password
REST API endpoint or via one of the Login()
functions in CoreSDK
. This endpoint supports authentication using
an account’s stored password, api key or device.
The below example shows how to authenticate using this method using a user’s unique name and password.
CoreSDK->LoginPassword(_XPLATSTR("username"), _XPLATSTR("password")).then([](pplx::task<void> task)
{
try
{
// Force the exception to be re-thrown if an error occurred.
task.get();
}
catch (const axr::sdk::Exception& e)
{
// Handle error here
}
});
try
{
await CoreSDK.LoginPassword("username", "password");
}
catch (Exception error)
{
// Handle error here
}
try
{
await CoreSDK.loginPassword("username", "password");
}
catch (error: any)
{
// Handle error here
}
try
{
AXRCoreSDK SDK = AXRCoreSDK.GetInstance();
await SDK.Instance.LoginPassword("username", "password");
}
catch (Exception error)
{
Debug.LogError("Failed device login. Error=" + error.Message);
}
const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
check(OnlineSub != nullptr);
const IOnlineIdentityPtr IdentityInterface = OnlineSub->GetIdentityInterface();
check(IdentityInterface.IsValid());
FDelegateHandle LoginDelegateHandler;
auto LoginDelegate = FOnLoginCompleteDelegate::CreateLambda([=](int32 InLocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
if (Error.Len() > 0)
{
// Handle error here
}
IdentityInterface->ClearOnLoginCompleteDelegate_Handle(InLocalUserNum, LoginDelegateHandler);
});
LoginDelegateHandler = IdentityInterface->AddOnLoginCompleteDelegate_Handle(0, LoginDelegate);
FOnlineAccountCredentials creds;
creds.Type = ELoginMethods::ToString(ELoginMethods::Basic);
creds.Id = TEXT("username");
creds.Token = TEXT("password");
IdentityInterface->Login(0, creds);
GET /auth/password HTTP/1.1
Authorization: Basic BASE64("username:password")
A successful authentication request will return a valid access token and cookie, or return without an error when using the SDK. Access tokens are typically valid for one hour before they must be refreshed.
Multi-factor Challenge
For users that have enabled multi-factor authentication on their account they may be prompted to enter a time-based
one-time password (TOTP) code to retrieve the final access token. The system notifies the user of this requirement
by returning a CHALLENGE
token after the initial request succeeds. The user then must follow up the initial
request with a call to the /auth/totp
endpoint or by using the CoreSDK.LoginTotp()
function in the SDK.
When calling the the TOTP endpoint the challenge token must be provided in addition to the TOTP code as generated by the user’s registered authenticator app or device.
CoreSDK->SetTotpChallengeCallback(CoreSDK->CreateTask([]()
{
utility::string_t code;
// TODO Prompt user to enter TOTP code
return code;
}));
CoreSDK->LoginPassword(_XPLATSTR("username"), _XPLATSTR("password")).then([](pplx::task<void> task)
{
try
{
// Force the exception to be re-thrown if an error occurred.
task.get();
}
catch (const axr::sdk::Exception& e)
{
// Handle error here
}
});
try
{
await CoreSDK.LoginPassword("username", "password");
}
catch (Exception error)
{
// Handle error here
}
try
{
CoreSDK.onAuthChallenge = async () => {
let code: string = "";
// TODO Prompt user to enter TOTP code
return code;
};
await CoreSDK.loginPassword("username", "password");
}
catch (error: any)
{
// Handle error here
}
try
{
AXRCoreSDK SDK = AXRCoreSDK.GetInstance();
await SDK.Instance.LoginPassword("username", "password");
}
catch (Exception error)
{
Debug.LogError("Failed device login. Error=" + error.Message);
}
const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
check(OnlineSub != nullptr);
const IOnlineIdentityPtr IdentityInterface = OnlineSub->GetIdentityInterface();
check(IdentityInterface.IsValid());
FDelegateHandle LoginDelegateHandler;
auto LoginDelegate = FOnLoginCompleteDelegate::CreateLambda([=](int32 InLocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
if (Error.Len() > 0)
{
// Handle error here
}
IdentityInterface->ClearOnLoginCompleteDelegate_Handle(InLocalUserNum, LoginDelegateHandler);
});
LoginDelegateHandler = IdentityInterface->AddOnLoginCompleteDelegate_Handle(0, LoginDelegate);
FOnlineAccountCredentials creds;
creds.Type = ELoginMethods::ToString(ELoginMethods::Basic);
creds.Id = TEXT("username");
creds.Token = TEXT("password");
IdentityInterface->Login(0, creds);
POST /auth/totp HTTP/1.1
Authorization: jwt <CHALLENGE_TOKEN>
Content-Type: application/json
Content-Length: ...
{
"totp": <totp>,
"userUid": <uid>
}
E-mail
This method is used to easily allow users to authenticate with AcceleratXR via their registered e-mail address. It does not require a password be stored on the account. For that it is considered a password-less authentication method. This method also has the benefit of bypassing any configured multi-factor authentication settings with the account, since it effectively uses a time-based one-time password to function internally.
A user submits an authentication request to the /auth/email/<email>
endpoint. The system then sends a message
to the e-mail provided (assuming it’s registered) with a time-based one-time password code embedded in the body
of the message.
The following example shows the initial request to receive the totp code via e-mail.
CoreSDK->LoginEmail(_XPLATSTR("email")).then([](pplx::task<void> task)
{
try
{
// Force the exception to be re-thrown if an error occurred.
task.get();
}
catch (const axr::sdk::Exception& e)
{
// Handle error here
}
});
try
{
await CoreSDK.LoginEmail("email");
}
catch (Exception error)
{
// Handle error here
}
try
{
await CoreSDK.loginEmail("email");
}
catch (error: any)
{
// Handle error here
}
try
{
AXRCoreSDK SDK = AXRCoreSDK.GetInstance();
await SDK.Instance.LoginEmail("email");
}
catch (Exception error)
{
Debug.LogError("Failed e-mail login. Error=" + error.Message);
}
const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
check(OnlineSub != nullptr);
const IOnlineIdentityPtr IdentityInterface = OnlineSub->GetIdentityInterface();
check(IdentityInterface.IsValid());
FDelegateHandle LoginDelegateHandler;
auto LoginDelegate = FOnLoginCompleteDelegate::CreateLambda([=](int32 InLocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
if (Error.Len() > 0)
{
// Handle error here
}
IdentityInterface->ClearOnLoginCompleteDelegate_Handle(InLocalUserNum, LoginDelegateHandler);
});
LoginDelegateHandler = IdentityInterface->AddOnLoginCompleteDelegate_Handle(0, LoginDelegate);
FOnlineAccountCredentials creds;
creds.Type = ELoginMethods::ToString(ELoginMethods::Email);
creds.Id = TEXT("username");
IdentityInterface->Login(0, creds);
GET /auth/email/<email> HTTP/1.1
Once the code is received the user then submits the provided code to the backend to retrieve the final access token.
CoreSDK->LoginEmail(_XPLATSTR("email"), _XPLATSTR("code")).then([](pplx::task<void> task)
{
try
{
// Force the exception to be re-thrown if an error occurred.
task.get();
}
catch (const axr::sdk::Exception& e)
{
// Handle error here
}
});
try
{
await CoreSDK.LoginEmail("email", "code");
}
catch (Exception error)
{
// Handle error here
}
try
{
await CoreSDK.loginEmail("email", "code");
}
catch (error: any)
{
// Handle error here
}
try
{
AXRCoreSDK SDK = AXRCoreSDK.GetInstance();
await SDK.Instance.LoginEmail("email", "code");
}
catch (Exception error)
{
Debug.LogError("Failed e-mail login. Error=" + error.Message);
}
const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
check(OnlineSub != nullptr);
const IOnlineIdentityPtr IdentityInterface = OnlineSub->GetIdentityInterface();
check(IdentityInterface.IsValid());
FDelegateHandle LoginDelegateHandler;
auto LoginDelegate = FOnLoginCompleteDelegate::CreateLambda([=](int32 InLocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
if (Error.Len() > 0)
{
// Handle error here
}
IdentityInterface->ClearOnLoginCompleteDelegate_Handle(InLocalUserNum, LoginDelegateHandler);
});
LoginDelegateHandler = IdentityInterface->AddOnLoginCompleteDelegate_Handle(0, LoginDelegate);
FOnlineAccountCredentials creds;
creds.Type = ELoginMethods::ToString(ELoginMethods::Email);
creds.Id = TEXT("username");
creds.Token = TEXT("code");
IdentityInterface->Login(0, creds);
POST /auth/email/<email> HTTP/1.1
Content-Type: application/json
Content-Length: ...
{
"code": <totp>
}
Phone
This method is used to easily allow users to authenticate with AcceleratXR via their registered phone number. It does not require a password be stored on the account. For that it is considered a password-less authentication method. This method also has the benefit of bypassing any configured multi-factor authentication settings with the account, since it effectively uses a time-based one-time password to function internally.
A user submits an authentication request to the /auth/phone/<phone>
endpoint. The system then sends a text message
to the phone number (assuming it’s registered) with a time-based one-time password code embedded in the body
of the message.
The following example shows the initial request to receive the totp code via phone.
CoreSDK->LoginPhone(_XPLATSTR("phone")).then([](pplx::task<void> task)
{
try
{
// Force the exception to be re-thrown if an error occurred.
task.get();
}
catch (const axr::sdk::Exception& e)
{
// Handle error here
}
});
try
{
await CoreSDK.LoginPhone("phone");
}
catch (Exception error)
{
// Handle error here
}
try
{
await CoreSDK.loginPhone("phone");
}
catch (error: any)
{
// Handle error here
}
try
{
AXRCoreSDK SDK = AXRCoreSDK.GetInstance();
await SDK.Instance.LoginPhone("phone");
}
catch (Exception error)
{
Debug.LogError("Failed phone login. Error=" + error.Message);
}
const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
check(OnlineSub != nullptr);
const IOnlineIdentityPtr IdentityInterface = OnlineSub->GetIdentityInterface();
check(IdentityInterface.IsValid());
FDelegateHandle LoginDelegateHandler;
auto LoginDelegate = FOnLoginCompleteDelegate::CreateLambda([=](int32 InLocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
if (Error.Len() > 0)
{
// Handle error here
}
IdentityInterface->ClearOnLoginCompleteDelegate_Handle(InLocalUserNum, LoginDelegateHandler);
});
LoginDelegateHandler = IdentityInterface->AddOnLoginCompleteDelegate_Handle(0, LoginDelegate);
FOnlineAccountCredentials creds;
creds.Type = ELoginMethods::ToString(ELoginMethods::Phone);
creds.Id = TEXT("username");
IdentityInterface->Login(0, creds);
GET /auth/phone/<phone> HTTP/1.1
Once the code is received the user then submits the provided code to the backend to retrieve the final access token.
CoreSDK->LoginPhone(_XPLATSTR("phone"), _XPLATSTR("code")).then([](pplx::task<void> task)
{
try
{
// Force the exception to be re-thrown if an error occurred.
task.get();
}
catch (const axr::sdk::Exception& e)
{
// Handle error here
}
});
try
{
await CoreSDK.LoginPhone("phone", "code");
}
catch (Exception error)
{
// Handle error here
}
try
{
await CoreSDK.loginPhone("phone", "code");
}
catch (error: any)
{
// Handle error here
}
try
{
AXRCoreSDK SDK = AXRCoreSDK.GetInstance();
await SDK.Instance.LoginPhone("phone", "code");
}
catch (Exception error)
{
Debug.LogError("Failed phone login. Error=" + error.Message);
}
const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
check(OnlineSub != nullptr);
const IOnlineIdentityPtr IdentityInterface = OnlineSub->GetIdentityInterface();
check(IdentityInterface.IsValid());
FDelegateHandle LoginDelegateHandler;
auto LoginDelegate = FOnLoginCompleteDelegate::CreateLambda([=](int32 InLocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
if (Error.Len() > 0)
{
// Handle error here
}
IdentityInterface->ClearOnLoginCompleteDelegate_Handle(InLocalUserNum, LoginDelegateHandler);
});
LoginDelegateHandler = IdentityInterface->AddOnLoginCompleteDelegate_Handle(0, LoginDelegate);
FOnlineAccountCredentials creds;
creds.Type = ELoginMethods::ToString(ELoginMethods::Phone);
creds.Id = TEXT("username");
creds.Token = TEXT("code");
IdentityInterface->Login(0, creds);
POST /auth/phone/<phone> HTTP/1.1
Content-Type: application/json
Content-Length: ...
{
"code": <totp>
}
This section provides detail on how to work with the standard built-in authentication methods provided by the AcceleratXR platform out of the box.
SSO Providers
Discord
Coming Soon!
Facebook
Coming Soon!
Google
Coming Soon!
Steam
Coming Soon!
Twitter
Coming Soon!
This section provides detailed explanations of how to work with the supported third-party single sign-on (SSO) providers supported by the AcceleratXR platform.