question

kevin-4 avatar image
kevin-4 asked

Initial Startup flow best practices

So I used Playfab about a year ago for a test project that never went public. I'm now wanting to implement it for a project I plan to publish on mobile and desktop platforms, but wanted to make sure the startup flow I used a year ago is still the best way to go. I have a number of questions to ask in order to get caught up, sorry in advance! So for the flow...

New User:

1) User enters app. 'AuthMethod' (a key saved locally in the user's encrypted PlayerPrefs) is null, so user never authenticated with our app before. Automatically make a new account for them using the appropriate device authentication API call (LoginWithAndroidDeviceIDRequest, LoginWithIOSDeviceIDRequest, LoginWithCustomIDRequest).

2) Set 'AuthMethod' to 'Device'.

3) Allow user to set their optional Display name if they want to be eligible for leaderboards and stuff. Also, now that they're actually in the app and experiencing it, we can notify them to consider linking a social account (facebook, etc) or a custom account, in case they ever want to log in from another device.

Returning User:

1) User enters app. 'AuthMethod' is not null and is either 'Device', 'Facebook', 'Custom', 'Steam', etc.

2) Authenticate them through the appropriate API call for the saved AuthMethod.

Questions:

1) Is the basic flow above still the best way to go, or are there changes/improvements I've missed in the past year (like does the SDK now save the last authentication method?).

2) A competitor service has a property called 'Authenticated' as part of their SDK. Checking this at the start of the game loading will tell you if the SDK has saved the user's last authentication. If this property is true, you can immediately query for the current user's Player info and load up the game, no need to actively authenticate first since they already are with the service. Does Playfab have anything similar to this, where the SDK maintains the last authenticated user for a period of time until it expires (i.e. after a few days of inactivity from the app) so we don't have to call one of the authentication API calls every session?

3) (this question assumes the answer to number 2 is No): With device authentication, there's no issue re-authenticating with device every time in the background for the user, without any prompt to them. They won't notice it's happening anyway. However for custom logins (username and password) it will annoy the user to prompt them to manually sign in every app session. Is there anyway to generate an access token when they successfully login, and supply that to Playfab for as long as it stays active in lieu of a username and password? This is how Facebook and Steam logins work with Playfab, only that the life of the access tokens in those instances are handled by the external platforms. Does Playfab do anything similar with their own custom logins?

4) Are the InvalidFacebookToken and InvalidSteamToken error codes returned when Playfab determines the access tokens from facebook and steam, respectively, are expired? If that's not the use of those error codes, how do we check this? Do we manually make a call to the respective services each session to check if the saved access token is still valid, or does Playfab handle that for us as part of the authentication method for each service?

5) Related to #4: Should I continue saving access tokens locally for users in encrypted player prefs, or is there something we can set in the SDK to handle this for us?

6) Regarding device auth: say a user plays one of my games but only ever plays it with device authentication. Now say they sell that device to a new user that so happens to also try my game. When they open the app for, what to them is the first time, they will automatically be logged on under the previous device owner's account. Now I know this is an extreme edge case where, unless you have an exceptionally large player base, you're unlikely to encounter. But I thought about this while reviewing my old code and didn't know how to 1) spot this is happening, and then 2) actually handle it.

7) Only question not related to authentication: The competitor service mentioned above has a 'SetDurable' flag on requests, which allows requests to be queued up while the user has no connection, then once a connection is established, the requests will be sent 1 by 1 in order. This essentially allowed for an offline mode with little effort. Upon reviewing my old code using PF, I noticed I made my own offline mode which led to a lot of extra properties and methods in different classes. Does Playfab have a similar flag to SetDurable, or any way to queue up requests when there's no connection? If not, can anyone think of a way to achieve this?

Feels good to be back with Playfab!

sdksAuthentication
10 |1200

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

brendan avatar image
brendan answered

Sure - happy to help.

In general, the flow we would recommend is what you see in our tutorials: https://api.playfab.com/docs/tutorials/landing-players/best-login. Basically, start the player in the game using a zero-friction sign-in, like a device ID login call, and then incentivize them to add a "recoverable" login type later (like Facebook or Twitch). But no, I would not recommend using the "Authenticated" logic you described below, as it makes too many assumptions about the state of the game. If the game is uninstalled, or the user replaces the device, you'll lose that info, so assuming that the player is completely new because they don't have that data locally isn't necessarily correct.

Bear in mind that while Android devices always give the same device ID (unless they're factory reset), iOS devices do not - they generate a new ID for the device for your games the first time an app from your developer account is installed. So if the player uninstalls and re-installs, you'll get a new ID. Instead, I'd recommend saving the device ID to the player's Keychain on the first install, so that you can always get it later. Then, check the Keychain to see if there's an ID before trying to create a new account. That'll then get you to the same account even across different iOS devices.

Saving information locally on the last used login is a good idea after that though, since the way token-based services work (like Facebook), you do need to use their sign-in method to get an updated token (to make sure integrated functionality, like friends lists, works correctly). And our Session Tickets do last for 24 hours, so no, you don't have to sign in the player every time - but it is a good idea to always check the response from each API call, to see if the ticket has expired and needs to be replaced.

For games where you use username/password to log in, yes, you can use the Custom ID login as a way to create a locally saved GUID that you use to sign in, instead. That way, you don't have to have the player enter the username/password each time - just generate a GUID once they're signed in, and use LinkCustomID to add it to the account as a credential.

For 4, yes, if you use the Facebook or Steam login call and the token isn't valid, that's the error you get back. Those services do not allow us to continue to renew those tokens directly, so you do need to use the service-specific login call. However, if the player is logged into the service in question, you can query for the service token from them without asking the user for anything. And no (5), there's no reason to additionally save the service token from them in player prefs.

For the device sale scenario, unless the player also gives the new owner their service account with their cellphone provider, the device is going to have to be factory reset. At that point, the new owner gets a new ID.

Queueing requests on the local device isn't something we do in our SDK, and I'd caution you to think carefully about what security you want to have on your game before considering doing something similar on your own. First, anything you store on the client device for later transmission is trivially hacked by the local user. Granted, anything coming from the client device has to be considered suspect anyway, so that's not a huge difference from the non-cached scenario. However, it does mean that you're trusting the client to tell you the complete history of everything that happened since the last time you heard from it. It's a lot easier for me to cheat your game if I can write a script that creates a single "here's everything I did in the last 12 hours" package, rather than having to have a process that runs for 12 hours and sends up the calls for each change with appropriate timing (the idea being that you could check the time between level completion reports in Cloud Script, for example).

10 |1200

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

kevin-4 avatar image
kevin-4 answered

Thanks for the info Brendan. There was a lot to unpackage there, so let me see if I understood all the points you made (sorry for making this an answer rather than comment, but I went over the characters for a comment):

1) So EVERY account should have an anonymous device ID attached to it, as this should be the first way you authenticate a user when they first enter your app.

2) Even once you incentivize a user into linking a recoverable login (Facebook, Steam, Username/Email and Password), we should still default back to signing them in with their no-longer-anonymous device ID as 1) they're both linked to the same account now anyway and 2) this will continue to allow sign-in to be frictionless, yet with the benefit that now, if they get a new device, they will have a way to recover the account.

2-a) If our app does not require any services from the 3rd party (like friends list as you mentioned), can we just continue using their device ID to authenticate them indefinitely? So even when the Access Token ultimately expires, we wouldn't care as we don't need them to use their 3rd party credentials unless they need to recover their account.

2-b) If we do infact need some other service from the 3rd party, then we should NOT default back to always using device ID for authentication. In this case, we should now save locally on the client which service they connected their device ID to. Then on every subsequent game session, we query that service for an Access Token if the user is still signed in (do NOT save the access token locally, even encrypted, like I suggested before). If the service says they are no longer logged in, now we can prompt them to re-login using the appropriate SDK or API call.

2-c) Extension of 2-b: Assume the user wiped our app data. Now even though they last used Facebook to login, we don't have that info saved locally anymore so we use their device ID to authenticate them. Now this does indeed log them into the correct account, however some 3rd party services like the friends list might not be working since we couldn't check if their access token was still valid before authentication. How should we handle this type of situation?

2-d) The above points don't effect the Playfab recoverable logins like username/email and password, right? Once we link that to their anonymous device ID, we should always go back to using the device ID for sign in. There is no Access Token that Playfab gives for using their sign in methods, correct?

3) So say a user is signing in from a new device. We should automatically authenticate using their device ID, which would make a new account on the backend. Then once they're in the app, they should have an option of linking this new device ID to their previous account using one of their recoverable logins. Will this now make this new account orphaned, as the device ID will be removed from it and added to their previous account? Isn't this wasteful and could interfere with internal statistics like user growth? I thought a better option would be giving them an option at startup like "Do you have an account?" - if yes, allow them to sign in, then once in we link the new device ID to their account. This prevents creating a new account unnecessarily.

3-a) If my above assumptions are correct, that means every account could theoretically have an unlimited number of device IDs and 3rd party logins attached to it, right?

4) You suggested phones being sold always have to be factory reset. If the new owner uses the save service provider as the last, like AT&T, I don't think this is the case though is it? If the phone doesn't need to be unlocked for use with another provider, the original owner can just regularly delete their phone data before selling, which would keep the same device ID and lead to the new phone owner accessing the same in-game account.

Also, on Android at least, it seems that InstanceID will be reset for each app-uninstall or factory reset, however ANDROID_ID was only listed as "can change upon factory reset". The wording for the latter makes it seem like there's a possibility it won't actually reset when there's a factory reset, which goes back to the original issue. InstanceID will reset in that instance, but doesn't seem like a better choice as I wouldn't want an ID that changes every app re-install.

3 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.

kevin-4 avatar image kevin-4 commented ·

Also you mention that your session tickets last for 24 hours. Should we be saving this locally then to test on subsequent game sessions if it's still valid (and we can thus skip authentication)?

0 Likes 0 ·
kevin-4 avatar image kevin-4 commented ·

And you mentioned that you don't like the idea of queuing requests like that while user is offline. I know the general idea is to never trust the client, but if you want to implement an offline mode is there anyway around something like that?

The requests will still have to be verified as plausible by our cloud code when we begin to sync anyway, right? So it's not as if everything that happens only is auto-synced to the user's account, it still has to meet all security criteria we impose on regular online requests as well. I was just wondering if there's an easy way to queue these requests as I'm definitely implementing an offline mode, one way or another.

0 Likes 0 ·
kevin-4 avatar image kevin-4 commented ·

The graphic at the bottom of the link you provided (https://api.playfab.com/docs/tutorials/landing-players/best-login) actually seems to go against what the info in the page stated. The graphic is more similar to my 'AuthMethod' technique: it checks to see if the last authentication method was saved locally and uses it to authenticate, and if it wasn't, defaults to device authentication. Also at the end it says unlink any Android or iOS device after you link a 3rd party. That goes against their suggestion to continue using the device ID for authentication, even once you link a 3rd party login (as that 3rd party login is essentially just for account recovery now).

0 Likes 0 ·
brendan avatar image
brendan answered

Sure, to address your points:

1. No, I'm definitely not saying that you should use the Device ID login all the time. For example, if you want to use the Facebook friends system in your game, we have to have a current token for that service to be able to request information about your friends. Using the Device ID is a great way to do a zero-friction sign-in, and if you don't need any additional functionality from any other service linked to the account, you can use it all the time. But you need to decide which login call to use based upon your game's features.

2. It sounds like you're saying that you should always try to have a recoverable account linked to the player account. If so, then yes, we would agree with that.

2a. Correct - if you don't care about any third-party services, a Device ID login would work fine.

2b. Also correct - if you're dependent upon a third-party for additional functionality, it's important to make sure you have a current login with that service.

2c. I don't follow. Any third-party login would continue to work fine across any changes - device, operating system, etc. If I log into your game with Facebook on my iOS device, I can log into your game on my PC, Android device, or any other web-API capable device, and log into the same account.

2d. There's a mix of concepts here. Device ID is local to the client device, and is not a "recoverable" login, because it's information that only the local device has. Facebook, Twitch, Steam, etc. logins are all "recoverable" because the user can sign into them on any device. It sounds like you're asking about a PlayFab "token" for signing the player into their account - that would be the session ticket, which is valid for 24 hours, currently. But we cannot guarantee the validity of that login to third-party services for that period, as they have their own requirements (and, in many cases, do not provide us with a way to refresh the tokens we store for players).

3. Yes, the "do you have an account" question is specifically what we recommend. Simply put, it's not possible for us to "merge" an existing account into a new account, as practically everything about it will be game-specific. So your FTUE should include a "do you already have an account" option. That ensures you can always get the player back to their original account.

3a. You can have many Device IDs associated with a single player account, yes. That's so that a player can have all their devices associated with it and be able to continue play across all devices.

4. I'm not aware of any instance (apart from a stolen device) where the original user's ID is compromised. But if you're concerned about that possibility, you could always generate a unique GUID and store it locally. After all, the Device ID isn't something you'd be sharing across devices, so it's not something you would expect to persist outside the context of a single device install.

For your other comments:

Yes, if you want to have an offline mode, you need to have a way to check all the user's offline play. Given that they could have been offline for many hours, that's really something you should do with a custom game server. Log the player in, read their state from the service, then process all their inputs from the entire period they were offline and process it. That gives you the ability to have deep, long-running logic that analyzes everything about the player activity and decide if it's valid.

Bear in mind that a backend service for your game is here to be the long-term backend store for your information on the player state. In general, think of it the way you would stateless logic. For high-frequency, realtime updates, or for processing a large amount of offline activity, you're going to need to use a long-running process in a dedicated server.

For our tutorial, I'm not clear on what you mean. What we describe is using the appropriate login method throughout your game's lifecycle. If you need Facebook functionality, for instance, you should always use Facebook login. If you need nothing other than a generic login, a Device ID is fine, as long as you have a way to make sure the player is informed and aware of the login options, so that they can set up a recoverable login type and use it on other devices.

5 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.

kevin-4 avatar image kevin-4 commented ·

Alright I'm almost through with the entire authentication flow using PlayFab. I have 1 last question based on the following situation however:

A user already has a recoverable login, and now they're logging in from a seemingly new device. After a successful login, I automatically call the appropriate LinkDevice API call (Android, Custom, iOS). If no error message returns, then they're good to go - future game sessions will be authenticated friction free with their device instead of their recoverable credentials. If I get back LinkedDeviceAlreadyClaimed however, I tell the user that this device is already connected to a different account. If they link it to this current account, the other account may be lost if it has no other recoverable login.

At this point the user can say fine, and I re-send the LinkDevice API call with 'ForceLink' set. However if they say no, then instead of setting them up with device auth for future frictionless logins, I have to keep making them use their recoverable credentials. With a login like Facebook, this isn't too bad as access tokens can last for many days. With PlayFab login though, is there an access token equivalent, or must I request their

0 Likes 0 ·
kevin-4 avatar image kevin-4 commented ·

credentials every single game session? I could save their email and password in an encrypted file that's only stored locally on their device, but would rather avoid that route.

0 Likes 0 ·
brendan avatar image brendan kevin-4 commented ·

The Session Ticket is what you get from any login with PlayFab - it's not a renewable token. So after it expires (currently 24 hours), it's no longer valid and the player has to be signed in again, using any of the Login... API methods. But if the user already has the current device linked to an account, the implication is that they're already playing (or have played) the game. So showing them both accounts and saying "pick one", then re-linking the device ID or recoverable login (depending on which they choose) to the account they're keeping is a common technique.

0 Likes 0 ·
kevin-4 avatar image kevin-4 brendan commented ·

So if the user returns to the app and we see we have a saved session ticket:

1) How do we check if the Session Ticket is still valid?

2) If the Ticket is still valid, what API call can we make to retrieve the user's data, only using the Ticket?

Currently I always do a LoginWith... call when the game first starts, and set InfoRequestParameters for everything I want returned about the user if authentication is successful. I'm not seeing any method where we can pull that info about a user directly, only using a Session Ticket.

0 Likes 0 ·
Show more comments

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.