question

Darius Vu avatar image
Darius Vu asked

[Big Issue] How to reset the password when an user forgot it in Unity app?

Dear all,

I must create new post for this topic because this issue is very important for game/ app development. Many users can "forget the passwords" and they want to reset it by email. If this issue cannot be solved then all functions of Playfab that I am developing on my app are not meaningful for me. :( I have to use other server for my app.

Firstly, I follow this tutorial to make the reset password function in my app.

https://docs.microsoft.com/en-us/gaming/playfab/features/engagement/emails/using-email-templates-to-send-an-account-recovery-email

I summarize the process as below:

- User click on "Reset password" button on the app.

- The confirmation email is sent to user's contact email that they signed up.

- User clicks the "confirmationURL" in the email.

- The app can detect the user clicks or not the "confirmationURL" to verify.

- User types the new password and request to server to reset their password.

But the first problem is that Playfab API only supports the reset function by using AdminAPI or ServerAPI and without Client API.

And after the discussion in here:

https://community.playfab.com/questions/44337/the-question-about-the-confirmation-email-for-forg.html

Sarah Zhang helped me to store the Token in Player Internal Data by the auth_token_validated event and recommend me to use the AdminAPI or ServerAPI to call Reset Password function.

And after the discussion in here:

https://community.playfab.com/questions/44653/how-to-build-a-custom-server-to-the-playfab-admin.html?childToView=44819#comment-44819

Citrus Yan helped me to create the Azure function to read the Token from Player Internal Data and call the reset password function by using Playfab Admin API.

However, this solution only works when users login already, it means that they can "change password", not "forgot password" function as I wish. Because the problem is that we cannot execute the cloud script function without login. The function requires users to login first.

The solution is that users can type the Token value and call Azure function directly, but it is hard to get the Token for user's typing because they can open the "URL confirmation" in their email by using email app or web browser...

So could you have any suggestion for me to solve this issue?

I have some ideas, but I don't know that it can do or not.

- Could we get the Token from Player Internal Data in Azure function without the login from users?

- Could we execute the Azure function by using Cloud script? If yes, then we can call the Azure function to reset the password from Cloud script.

- Could we get the Playfab ID from the contact email using Admin API or Server API from Azure function?

Again, thank you so much to teams who support me.

Best regards.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Darius Vu avatar image
Darius Vu answered

I have the solution to fix this issue as below:

In Azure function, I find the Playfab ID from the contact email. Then I get the Token from Player Internal Data using Playfab ID and run the reset password function.

//get the Playfab ID from the contact email
var userAccountInfoRequest = new LookupUserAccountInfoRequest
{
	Email = contactEmail,          
};
var userAccountInfoDataResponse = await adminAPI.GetUserAccountInfoAsync(userAccountInfoRequest);
string playfabID = userAccountInfoDataResponse.Result.UserInfo.PlayFabId;

I tested it by setting manually the password and email. And it is working well.

However, do you know how to execute the Azure function directly from Unity client (don't use automation in Playfab Manager) and pass an email and new password to Azure function as inputs?

7 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Sarah Zhang avatar image Sarah Zhang commented ·

PlayFab has provided a default password resetting web page now. When you request the client API SendAccountRecoveryEmail, just don’t pass the EmailTemplateId. PlayFab would send an email that contains a default resetting password page to the player. The email content would be something like this.

To reset the password for your account in '[YourTitleName]',
click the following link:
https://player.playfab.com/account/resettitlepassword?token=46xxxxxxxx5f61
If you didn't initiate this account recovery request, you don't need to take
any further action and can safely disregard this email.

The above link would navigate to a webpage that made by PlayFab. And players can reset the password on the webpage directly. This feature can simply meet your requirement.

0 Likes 0 ·
Sarah Zhang avatar image Sarah Zhang Sarah Zhang commented ·

Besides, Unity clients can directly send a post request to execute the Azure Functions. You can send a POST request using UnityWebRequest.
Unity code

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class confirmURL : MonoBehaviour
{
    public void OnClickButton()
    {
        StartCoroutine(Post());
    }
 
    IEnumerator Post()
    {
        WWWForm form = new WWWForm();

//add fields here
       
        form.AddField("name", "someone");

//Use your Azure Function URL here
        UnityWebRequest webRequest = UnityWebRequest.Post("http://xxxxxxxxxxxxx/api/HttpTriggerCSharp", form);
        yield return webRequest.SendWebRequest();
        
        if (webRequest.isHttpError || webRequest.isNetworkError)
            Debug.Log(webRequest.error);
        else
        {
            Debug.Log(webRequest.downloadHandler.text);
        }
    }
}
0 Likes 0 ·
Sarah Zhang avatar image Sarah Zhang Sarah Zhang commented ·

Azure Function

        [FunctionName("HttpTriggerCSharp")]
        public static async Task<dynamic> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

           // do the verifiction and resetting password here

            return new OkObjectResult("Please pass a name on the query string or in the request body");
        }
    }

0 Likes 0 ·
Show more comments
Darius Vu avatar image Darius Vu Sarah Zhang commented ·

Oh, this is great help for me. I will try both ways and select the better way for me.

I am so grateful for your support!!!

0 Likes 0 ·
Sarah Zhang avatar image Sarah Zhang Darius Vu commented ·

I will try to test it.

0 Likes 0 ·
Show more comments
Darius Vu avatar image
Darius Vu answered
@Sarah Zhang

Yes, thank you so much. This is my Azure Function:

public static class ForgotPassword
{
	[FunctionName("ForgotPassword")]
	public static async Task<dynamic> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
	{
		log.LogInformation($"{nameof(ForgotPassword)} C# HTTP trigger function processed a request.");
		
		FunctionContext<dynamic> context = JsonConvert.DeserializeObject<FunctionContext<dynamic>>(await req.ReadAsStringAsync());
		dynamic args = context.FunctionArgument; 
		var contactEmail = args["contactEmail"];
		var newPassword = args["newPassword"]; // retrieve the new password sent by the client, specified in ExecuteFunction -> FunctionParameter -> newPassword
		log.LogInformation($"The new password = {newPassword}");
		log.LogInformation($"The contact Email = {contactEmail}");
		//set up title id and secret key for admin api to use 
		var adminApiSettings = new PlayFab.PlayFabApiSettings()
		{
			TitleId = "XXXXX",
			DeveloperSecretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
		};
		log.LogInformation($"TitleId = {adminApiSettings.TitleId}");
		log.LogInformation($"DeveloperSecretKey = {adminApiSettings.DeveloperSecretKey}");
		var adminAPI = new PlayFab.PlayFabAdminInstanceAPI(adminApiSettings);
		//get the Playfab ID from the contact email
		var userAccountInfoRequest = new LookupUserAccountInfoRequest
		{
			Email = contactEmail,		
		};
		var userAccountInfoDataResponse = await adminAPI.GetUserAccountInfoAsync(userAccountInfoRequest);
		string playfabID = userAccountInfoDataResponse.Result.UserInfo.PlayFabId;
		log.LogInformation($"PlayFabId = {playfabID}");
		//log.LogInformation($"PlayFabId = {context.CallerEntityProfile.Entity.Id}");
		//log.LogInformation($"PlayFabId = {context.CallerEntityProfile.Lineage.MasterPlayerAccountId}");
		//get player's token stored in internal data
		var getUserInternalDataResponse = await adminAPI.GetUserInternalDataAsync(new PlayFab.AdminModels.GetUserDataRequest(){
			PlayFabId = playfabID,
			//PlayFabId = context.CallerEntityProfile.Lineage.MasterPlayerAccountId,
			Keys = new List<string> () {"Token"}
		});
 
		var tokenId = getUserInternalDataResponse.Result.Data["Token"].Value;
		log.LogInformation($"tokenId = {tokenId}");
		//reset the password
		return await adminAPI.ResetPasswordAsync(new PlayFab.AdminModels.ResetPasswordRequest() {
			Password = newPassword,
			Token = tokenId
		});
	}
}
1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Sarah Zhang avatar image Sarah Zhang commented ·

Could you please try to use the following method?

  [FunctionName("ForgotPassword")]
        public static async Task<dynamic> ForgotPassword([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
        {
            var contactEmailStr = "";
            var passwordStr = "";
            Microsoft.Extensions.Primitives.StringValues contactEmail;
            Microsoft.Extensions.Primitives.StringValues password;
            if (req.Form.TryGetValue("contactEmail", out contactEmail))
            {
                contactEmailStr = contactEmail.ToString();
            }
            if (req.Form.TryGetValue("password", out password))
            {
                passwordStr = password.ToString();
            }

            log.LogInformation(contactEmailStr + passwordStr);
            return new OkObjectResult(contactEmailStr + passwordStr);

        }
0 Likes 0 ·
developer-6 avatar image
developer-6 answered

Hi,

I used the above code and I'm getting the playfab ID flawlessly but when i'm trying to get Token from playfab ID it is saying

"KeyNotFoundException: The given key was not present in the dictionary."

at line >> result.Data["Token"].Value;

the count of list Data is 0.

Please find the code below and help me.

there is no compile time error in the code.it is running fine.

 void GetPlayfabID(string contactEmail)
    {
        var adminApiSettings = new PlayFabApiSettings()
        {
            TitleId = "xxxxx",
            DeveloperSecretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        };


        adminAPI = new PlayFab.PlayFabAdminInstanceAPI(adminApiSettings);


        var userAccountInfoRequest = new LookupUserAccountInfoRequest
        {
            Email = contactEmail,
        };
        adminAPI.GetUserAccountInfo(userAccountInfoRequest, OnSuccess, OnFaliure);


    }
    string playfabID = "";
    private void OnSuccess(LookupUserAccountInfoResult result)
    {
        playfabID = result.UserInfo.PlayFabId;
       
        GetPlayerToken(playfabID);
    }


    private void OnFaliure(PlayFabError result)
    {
        
//error code..

    }


    void GetPlayerToken(string playfabID)
    {
        var userdatarequest = new GetUserDataRequest
        {
            Keys = new List<string>() { "Token" },
            PlayFabId = playfabID
        };
        adminAPI.GetUserInternalData(userdatarequest, OnSuccessToken, OnFaliureToken);


    }
    internal string playerToken = "";
    private void OnSuccessToken(GetUserDataResult result)
    {
        Debug.Log(" keys Count  : " + result.Data.Count); //getting 0 here


       playerToken = result.Data["Token"].Value;
     
        


        //Open password reset Panel..
    


    }


    private void OnFaliureToken(PlayFabError result)
    {
 //error code..

    }
,
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.