Understanding SDK code

Understanding sign-up code

Let’s walk through the AWS SDK code to understand how it works. First, open the Cognito.cs script in Unity. In this script you will see a line similar to the following:

AmazonCognitoIdentityProviderClient provider = new AmazonCognitoIdentityProviderClient
	( new Amazon.Runtime.AnonymousAWSCredentials(), CredentialsManager.region );

Here, an Amazon Cognito Identity Provider client is created and initialized so that we can begin making calls to the Cognito API to sign-up users. This statement constructs a provider client using AWS credentials and the region endpoint in the constructor.

Next, listeners are added on button clicks for the Login and Sign Up button so that code can be executed when the player tries to login and sign up.

    LoginButton.onClick.AddListener(Login);
    SignupButton.onClick.AddListener(Signup);

Lets dissect Signup_Method_Async() first. This method creates a new cognito user.

We are storing the username, password, and email in variables so we can use the information in a request to Cognito to create the users login credentials.

    string userName = SignupUsernameField.text;
    string passWord = SignupPasswordField.text;
    string email = EmailField.text;

Then, a SignUpRequest is used to sign-up a user to your Cognito user pool. The SignUpRequest needs to know your Cognito App Client ID as well as the username and password that the player typed in when trying to sign-up.

    SignUpRequest signUpRequest = new SignUpRequest()
    {
        ClientId = CredentialsManager.appClientId,
        Username = userName,
        Password = passWord
    };

Next, a list of attributes is created and added to the SignUpRequest. When you created your user pool, email was selected to be a required attribute when a player creates an account. You can also define other attributes that you might want required as well. These can be built-in attributes that AWS offers already, like phone number and address, or even your own custom attributes. The email attribute is added to the SignUpRequest.

    List<AttributeType> attributes = new List<AttributeType>()
    {
        new AttributeType(){Name = "email", Value = email}
    };

    signUpRequest.UserAttributes = attributes;

Finally, the request is submitted to sign the player up!

    try
    {
        SignUpResponse request = await provider.SignUpAsync(signUpRequest);
        Debug.Log("Sign up worked");

        // Send Login Event
        Events.Call_Signup();
    }
    catch (Exception e)
    {
        Debug.Log("Exception: " + e);
        return;
    }

This is a try-catch block where you try to submit the request to sign up your player to your Cognito user pool. If the request is successful, it prints a message to the console saying the sign up worked. If the request fails, you catch the exception and print the error message to the console to help with debugging.

Understanding sign-in code

Lets dissect the Login_User() method. This method signs in the user based on credentials they provide. We save the credentials users provide as variables in the code.

    string userName = LoginUsernameField.text;
    string passWord = LoginPasswordField.text;

A user pool variable is created, which is a representation of your Cognito user pool. Also, a user variable is created, which is a representation of the user that is trying to sign-in.

    CognitoUserPool userPool = new CognitoUserPool(CredentialsManager.userPoolId,
        CredentialsManager.appClientId, provider);

    CognitoUser user = new CognitoUser(userName, CredentialsManager.appClientId,
        userPool, provider);

Then, the user is authenticated by validating the username and password. This is done through the Secure Remote Password (SRP) protocol, which helps to simplify the authentication process of Amazon Cognito user pools. It is a secure password-based authentication and key-exchange protocol that solves the challenge of authenticating clients to servers securely.

    InitiateSrpAuthRequest authRequest = new InitiateSrpAuthRequest()
    {
        Password = passWord
    };

Then, StartWithSrpAuthAsync() is used to authenticate the user and return an AuthFlowResponse object. This has an AuthenticationResult property that contains the user’s session tokens if the authentication is successful.

    try
    {
        AuthFlowResponse authResponse = await
            user.StartWithSrpAuthAsync(authRequest).ConfigureAwait(false);

        GetUserRequest getUserRequest = new GetUserRequest();
        getUserRequest.AccessToken = authResponse.AuthenticationResult.AccessToken;

        Debug.Log("User Access Token: " + getUserRequest.AccessToken);
        jwt = getUserRequest.AccessToken;

        // User is logged in
        loginSuccessful = true;
    }
    catch (Exception e)
    {
        Debug.Log("Exception: " + e);
        return;
    }

Upon successful sign-in, we get the user ID of the player to be used for analytics by calling the Get_User_ID() method.

    if (loginSuccessful == true) {

        string subId = await Get_User_Id();
        CredentialsManager.userid = subId;

        // Send Login Event
        Events.Call_Login();

        // Print UserID
        Debug.Log("Response - User's Sub ID from Cognito: " +
            CredentialsManager.userid);

    }

The Get_User_ID() method gets a players unique identifier from Cognito.

private async Task<string> Get_User_Id()
{
    Debug.Log("Getting user's id...");

    string subId = "";

    Task<GetUserResponse> responseTask =
        provider.GetUserAsync(new GetUserRequest
        {
            AccessToken = jwt
        });

    GetUserResponse responseObject = await responseTask;

    // Set User ID
    foreach (var attribute in responseObject.UserAttributes)
    {
        if (attribute.Name == "sub")
        {
            subId = attribute.Value;
            break;
        }
    }

    return subId;
}

In production, you may or may not want to collect the unique user identifier. Another alternative is to provide anonymity for your players.

Disclaimer: The code in this workshop is meant for tutorial purposes only and is not production-ready code.