Best Practices for Anonymous Authentication

Does this sound like a familiar experience to you? You just downloaded an app that a friend recommended to you, and the first thing you encounter is a sign-up screen for the app. The sign-up screen also only supports email and password authentication–no single sign in options, either! Now you need to come up with a unique new password that doesn’t overlap with any of your other passwords, as you don’t want to compromise any of your accounts. In reality, you probably don’t want to create an account in the first place, since you are not even sure that you want to use this app–you only want to try it out.

Why did the app developer have to create this frustrating experience?

Well, they likely needed some way to link and store data associated with some identifier, and the easiest way to get that identifier is to associate it with a user account.

But instead of giving new users a warm welcome, this onboarding speed bump likely results in most people giving up. The result: fewer users are willing to try out the app, and the developer accidentally hurt the growth potential of their app.

Common ways to solve this problem

A common way to make signing in easier for users is to use a federated login (for example Google Sign-In or Sign in with Apple). Federated authentication providers allow users to sign in once, and then use the same provider to authenticate to any apps and web sites that support this authentication provider. This removes the need for coming up with a new and secure password for each and every app or web site they want to sign in to.

This is great, but still far from perfect: many apps offer multiple ways to sign in, such as using Sign in with Apple. Google Sign-In, and Twitter. This results in a sign-up screen with several options the user has to pick from, and it might be hard for the user to remember which authentication mechanism they used when signing up to the app.

Another way to get around asking users to sign in before using an app is to store all data locally, and then sync the data up to the cloud when the user signs in.

This approach requires developers to not only create a local storage and a remote storage solution, but also to add a syncing mechanism to ensure that each storage location is up to date. This means a lot of extra work for the developer.

This may be the reason the app developer decided to ask users to create an account, as it lowers the complexity of the application and therefore reduces the brittleness of it.

But what if I told you there is another way?

Anonymous authentication - the best of both worlds

Users want to be able to just try out an application. They are not willing to commit to a long-term relationship before they know that they truly want to sign up for an app.

Developers, on the other hand, do not want to spend a lot of time (and money!) on implementing custom sync logic.

Anonymous authentication can help. It is one of the easiest ways to create guest accounts and ramp on users gradually to your application.

Here is how it works: when your application launches for the first time, call getAuth().signInAnonymously(). This will create a new Firebase user for the person currently using the app. The user ID of this new anonymous user is created on the Firebase server and is not connected to any identifying properties of the user or their device - it is a unique, and completely random ID.

Once created, this anonymous user can then be used just like any other Firebase user account. In particular, you can use its UID as an identifier for any data you want to store, say, in Cloud Firestore.

Later, when the user likes the app and decides they want to continue using it, you can offer them to upgrade the anonymous account to a permanent account by asking them to authenticate using one of the other authentication mechanisms Firebase supports - such as Sign in with Apple, Google Sign-In, or even Email/Password authentication. Since the user is now committed to the app, they will be much more happy to complete the steps to register for a permanent user account.

Once they have authenticated, you can link those new credentials to the existing anonymous account, upgrading it to a permanent account. The UID will remain the same, which means that all data the user has already created with your app is still accessible to them.

Although it’s useful to onboard users with anonymous authentication, it’s important to convert users to a permanent sign in method

Although it’s useful to onboard users with anonymous authentication, it’s important to convert users to a permanent sign in method so their work can be definitively retained or accessed on their other devices. This is due to the fact that anonymous accounts are not connected to any identifiable information (such as the user’s email address, their phone number or any other such identifier). As a result, anonymous accounts don’t let users have the same account on multiple devices, and anonymous accounts are unrecoverable if the user ever gets signed out.

Let’s look at some best practices for using anonymous authentication.

Convert your users seamlessly

A good strategy for upgrading anonymous users to regular users is to identify features in your app that benefit from full accounts. For example, if your app allows users to share data with their friends and family, it is a good idea to ask anonymous users to upgrade their account to a full account when they try sharing data with their friends. Check the anonymous authentication documentation for your platform to see how to implement this in web apps, Android, iOS, and Flutter. The following code snippet shows how you can upgrade an anonymous account to a permanent account by linking an email/password credential with the anonymous user account.

import { EmailAuthProvider, getAuth, linkWithCredential } from "firebase/auth";
// … Perform email password sign in flow but do not log in.
// This grabs the newly created credential
const credential = EmailAuthProvider.credential(email, password);
// Gets the current auth credential (the anonymous user in this case)
const auth = getAuth(); 
// Establishes that the anonymous user is also this user.
linkWithCredential(auth.currentUser, credential) 
  .then((usercred) => {
    const user = usercred.user;
    console.log("Anonymous account successfully upgraded", user);
  }).catch((error) => {
    console.log("Error upgrading anonymous account", error);
  });

Note that upgrading an anonymous account to a permanent user requires a slightly different flow than a regular sign-up flow:

  • At the end of a regular sign-up flow, you need to call a Auth.signInWith method.
  • However, when linking an account, you must not call the Auth.signInWith method. Instead, you call linkWithCredential to link the new credentials with the existing anonymous account.

Once this linking call succeeds, you can then use the new account to access the anonymous account’s Firebase data. FirebaseUI supports this behavior out of the box for web, iOS, and Android.

Don’t keep user data forever

Having potentially lots of anonymous users could quickly burden your Firestore database with extra data that may never be accessed again if users decide your app isn’t for them after trying it out. There are two steps to ensure that stale user data is not kept forever.

  • Delete the user’s data when they decide to delete their account.
  • Automatically delete anonymous users after thirty days since anonymous user creation.

To clear user data when an anonymous user is removed, first deploy either the Delete User Data Firebase Extension or implement your own Cloud Function that handles removing the Firestore data in response to a user delete event.

To automatically remove stale anonymous accounts, upgrade your project to Firebase Authentication with Identity Platform. This enables the automatic clean-up option for anonymous users. When an anonymous user is more than thirty days old, it will be removed from the authentication system. This will then trigger the Delete User Data Extension (if installed) to remove all of the user’s data, so you won’t have to pay for any storage this data takes up in Cloud Firestore or Cloud Storage.

Ensure your Security Rules, Cloud Functions, and client handle anonymous authentication

In many apps, you want to make sure that only users with a full user account have access to features such as upvoting, making certain callable functions, or publishing some data to a public feed. To encourage anonymous users to upgrade to a full account, block these parts of your application for anonymous users by first handling client side logic. This is accomplished by showing a warning to any user that is anonymous by informing them that they will need to convert to a full user account for certain access.

On the backend side, use Security Rules to prevent anonymous users from accessing data they shouldn’t have access to. To do that, check which identity provider the user signed in with:

allow write: if request.auth != null && request.auth.token.firebase.sign_in_provider != 'anonymous'

This ensures that users who are authenticated with a regular identity provider do get write access to your protected users collection, whereas anonymous users will not be able to access this data.

To distinguish between anonymous and regular users in your Cloud Functions, examine the CallableContext in any callable function that your app contains. The CallableContext contains information about the request, including authentication information. Then, look at the auth object contained within to determine the sign in provider. Here is a TypeScript function that does this:

const isAnonymous = function(context: https.CallableContext): Boolean {
  if (context.auth == null) {
    logger.log(`no auth information found - user is neither logged in nor anonymous`);
    return false;
  } else {
    logger.log(‘anonymous user found’);
    if (context.auth.token.firebase.sign\_in\_provider == 'anonymous')
      return true;
  }
  return false
}

Don’t use anonymous users as a form of app-identity attestation

For many apps, it is important to make sure that data and functions can only be accessed from a legitimate app. Creating an anonymous user account for users who visit your site for the first time can be a tempting way to ensure that requests for app content are coming from within your genuine application. However, this is not as secure as it might seem, because anonymous user tokens can be issued through the Firebase REST API. A malicious actor can generate an anonymous user token, and then use it to access resources constrained by an anonymous user token.

Instead, implement AppCheck for attestation. App Check is available for iOS, Android, and the web and ensures that only requests from your genuine applications are able to access your secure resources.

Where to go from here?

Next time you build an app that requires user authentication, give anonymous authentication a try! Not only will this make your app easier to build, but it will also result in a simpler architecture and a better user experience. Instead of losing a lot of potential users that drop off because of the sign-up speed bump, more users will try your app, and it will be easier for you to convince them of your app and convert them to dedicated and loyal users.

Want to learn even more about the UX of Onboarding and Authentication? Check out the episode of Better Safe than Sorry with Roman Nurik and Peter Friese below.