Awesome
Unity AuthManager
Unity Version: 2018.3.6f1
Prerequisites
- Unity 2018 - https://unity3d.com/get-unity/download
- Nodejs - https://nodejs.org/en/download/
- MongoDB - https://docs.mongodb.com/manual/installation/
For running the backend REST API and connecting to MongoDB, see: https://github.com/zklinger2000/express-unity-auth
In my example, I am using a Digital Ocean droplet with MongoDB and running the Express REST API locally. Having a local MongoDB on Linux or OSX is fairly simple to setup, but I would suggest Docker on Windows, if you have a copy of Windows Pro Edition.
Overview
This project demos a token-based Authentication system built using ExpressJS and MongoDB on the server side, and a Unity client on the frontend. It is similar to the process of saving cookies into your web browser, but uses Unity's PlayerPrefs utility to save the token locally instead of saving it into the browser's Application cache.
Getting Started
Simply clone the repo and open the project folder in Unity. This will give you the client or front-end piece of this full-stack project. NOTE: You need the REST API, or backend server portion to see this work. See the repo link above.
Installing
git clone git@github.com:zklinger2000/unity-auth-manager.git
Go into Assets/Scenes/
and load the scene Boot
.
Manager List
The architecture for this Unity project is based on the principle separation of concerns. Each manager's job is to own a specific set of methods and properties dealing with specific aspects of the application.
GameManager
When you load up the Boot
scene, the only object in it is the GamaManager
transform object with a script attached.
The GamaManager
is in charge of loading scenes, instantiating prefabs and updating the state of the application.
It is also where all the other managers get initialized on startup, or enabled later at runtime.
The thing to notice about the GameManager
class is that it inherits from the Manager
class:
public class GameManager : Manager<GameManager>
In the /Scripts/Utils
folder you'll find two classes, Singleton.cs
and Manager.cs
. Both of these classes are
singleton implementations in C#, but the Manager
class has the major distinction of not replacing an object when a
new instantiation is attempted, thereby only ever having one instance of an object, but instead, does not allow any new
instantiations at all. The video courses in the Programming Path on Pluralsight go into depth on how these two classes
are created and used. I highly suggest watching those if you want more information on the specifics.
UIManager
public class UIManager : Manager<UIManager>
The UIManager
has a prefab with all the different canvases and buttons that make up the starting screen and menus. It
also holds all the scripts involved with handling button clicks and updating which menu should be seen for each state
.
Depending on which state
the application is in, each canvas has its Active
property turned on or off, so they are
always sitting above every loaded scene, but not necessarily visible.
AuthManager
public class AuthManager : Manager<AuthManager>
The AuthManager
holds our User
object, saves and loads our User
data between sessions and makes HTTP calls to our
backend for logging in and making authorized requests for secure resources.
HTTP
RestClient for Unity is a very Nodejs friendly package you can get off the Asset store. It is friendly in that it looks very similiar to how one writes requests in ExpressJS. They deserve a lot of thanks for writing that and making it open-sourced.
public void PostNewUser(string username, string password)
{
_currentRequest = new RequestHelper {
Uri = basePath + "/users/create",
Body = new JsonObjectNewUser(username, password)
};
RestClient.Post(_currentRequest)
.Then(res =>
{
JSONObject json = new JSONObject(res.Text);
_user.Username = TrimQuotationMarks(json["username"].ToString());
_user.Token = TrimQuotationMarks(json["token"].ToString());
// Save token and username with PlayerPrefs
userSaveData.Username = _user.Username;
userSaveData.Token = _user.Token;
userSaveData.Save();
GameManager.Instance.GoToMenu("welcome");
})
.Catch(err => EditorUtility.DisplayDialog ("Error", err.Message, "Ok"));
Persistent Login
A fancy way to say you don't have to login every time you open the app, or a web page. The token we receive upon
login gets saved into a file on your local machine using Unity's PlayerPrefs
utility.
[SerializeField] private UserSaveData_SO userSaveData;
public void Save()
{
if (key == "")
{
key = name;
}
string jsonData = JsonUtility.ToJson(this, true);
PlayerPrefs.SetString(key, jsonData);
PlayerPrefs.Save();
}
public void Load()
{
if (key == "")
{
key = name;
}
JsonUtility.FromJsonOverwrite(PlayerPrefs.GetString(key), this);
}
Whenever the app opens, it checks the registry (on Windows) and if it finds a token, it updates the current User
and
can now be considered "logged in" as long as any check for AuthManager.Instance.isAuthorized()
returns true
.
// AuthManager.cs
private void Start()
{
_user = new User();
// Load User data from PlayerPrefs
userSaveData.Load();
if (userSaveData.Token != String.Empty)
{
_user.Username = userSaveData.Username;
_user.Token = userSaveData.Token;
}
}
Authentication Flows
SignUp
username
andpassword
are entered into theInputField
's.- The payload is written as a JSON string.
- An
HTTP POST
request is sent to the/create
route of the backend API with the payload attached to the body. - If that
username
does not already exist, a newUser
is created and saved in MongoDB. - The response from the server sends back a JSON object with the
User
andtoken
. - The
User
object is updated on the client with whatever info we want to send back. - The
username
andtoken
are saved to the local machine.
Login
username
andpassword
are entered into theInputField
's.- The payload is written as a JSON string.
- An
HTTP POST
request is sent to the/login
route of the backend API with the payload attached to the body. - If that
username
andpassword
are verified, we get theUser
from MongoDB. - The response from the server sends back a JSON object with the
User
andtoken
. - The
User
object is updated on the client with whatever info we want to send back. - The
username
andtoken
are saved to the local machine.
Logout
- The
User
object in the Unity client has its data deleted. - The file with the token on the local machine is deleted.
- Any requests for secure data to the backend or
state
change in the Unity client will no longer pass a check toAuthManager.Instance.isAuthorized();
.
Authorized HTTP Requests
- A new request object is created with the JWT (token) inserted as the value of the
Authorization
Header. - Request is sent to the REST API.
- On the backend, the token is pulled out of the Header and decrypted, which gives the timestamp and
User._id
value used to do a lookup in the database. - If the
User
exists, and has authorization for that particular resource (not implemented for this demo), a response with JSON data is sent back to Unity.
Conclusion
While this is a very simple demo, it does create the framework on which a much more robust auth system could be built. Input validation, encrypting the password in Unity before sending it in the request, adding a timestamp check, etc, can all be added onto this to make it more secure.
On the backend, we're using a simple username/password ourselves, but it could be changed to use Google, AWS, Facebook, or any other ID system, without changing the Unity code at all. This token system does not rely on how the User is authenticated, it simply relies on whether or not you have the Bearer token itself and the ID of the document in the database matching the ID in the encrypted token.
From what I've read, while these tokens can be decrypted by a dedicated hacker, they can never be successfully
created without prior access to the database and the encryption key sitting on the server. So, don't put anything
in your token that is hyper critical. The user._id
in your database isn't that important without access to that
encryption key, thus making it the thing that needs to be guarded the most.
Let's say the encryption key changes once a day on the server. Every time that encryption key is changed, no tokens anywhere in the world would be valid and the next time someone tried to launch the app, or request a secured resource, the old token would be invalid, the user is considered 'logged out' and the REST API would refuse any requests made until the user is logged back in and given a new token.
After spending the last few months focused on nothing but animation in Unity, this was a great time for going on a deep dive into the "software" aspects of building a Unity game or VR app. I love the Manager system setup I learned from the Unity courses on Pluralsight. Marrying that to prior work built in React made it for a really fun project.
Authors
Zachary Klinger
License
UNLICENSED
Acknowledgments
All of this was built upon the techniques learned from the Swords and Shovels course on Pluralsight blended with the
authentication process from Stephen Grider's course on Udemy
Advanced React and Redux.
Swords and Shovels is actually three separate Learning Paths on Pluralsight: