development

Custom authentication with Amazon Cognito and Firebase

Developer, Photographer, and well... French. Converting caffeine into great products @Novoda. Minimalism is my thing.

As developers, we are always trying to move our technology knowledge forward and at Novoda, we are especially interested in BFF (backend for frontend) solutions to have some abilities on the "backend in the cloud" front. In this blog post we investigate one of the main points of concern: authentication services, and more generally, how to adjust our authentication stack to fit a mobile first approach.

When it comes to hosting your backend, the two main alternatives are Amazon Web Service (AWS) and Google Cloud Platform (GCP). Both provide out-of-the-box support for authentication, respectively via Cognito and via Firebase Authentication. They both can handle username / password based authentication, social logins (Facebook, Twitter, Google...) as well as custom authentication. Here we focus on the latter as this is the scenario you would encounter when transitioning to BFF.

Diving into the samples

We started our journey by forking two samples from Amazon, client and authentication server. They have an extensive post on how this works over here.

The server sample

The Amazon sample needs to be deployed into a Tomcat environment. We converted it into a Spring Boot application deployable into any Java8 environment. In that process, we also introduced Gradle to build the project.
The original sample provides three endpoints, registration of new users, user login and cognito token provision. We added a fourth one to also provide a firebase token.
There is also a JSP web interface to register new users.

The result can be found here.
The flow can be schematized like this:

Sample flow

  1. We use the web interface of the authentication server to register users in the database (left, red).
  2. We can then log in from the Android client to the authentication server that will in turn check our credentials against the database (center, blue).
  3. Once logged in we can ask the authentication server to fetch tokens from Cognito (left, purple) and Firebase (right, orange).
  4. The client can use them to access resources restricted to authenticated users from those services (respectively an Amazon Lambda and the Firebase realtime database).

The client sample

We removed all code not related to developer authentication from the original sample. Moreover, we revisited the package structure, streamlined the code, reworked the UI, and added access to a restricted AWS lambda. Finally, we added some logic to fetch a firebase token from our server and use it to access a restricted firebase database.

This is our revamped sample Android client:

screenshot of the sample application

The Login button allows you to log in to your authentication server by asking for the username / password you gave when registering a user through the web interface. The two following buttons are going to ask the authentication server to fetch a token from the corresponding service (if not already cached) and try to access a restricted resource (a lambda or the firebase database). Wipe data will remove everything that might have been cached and log you out of both services. Let’s now dive a bit more into each service and see how the authentication flow work.

Amazon Cognito

Cognito is an Amazon service that lets you add user sign-up and sign-in to your mobile applications. Using Amazon Cognito Federated Identities you can also authenticate users through social identity providers or use your own back-end authentication system. With a federated identity, you can obtain temporary, limited-privilege AWS credentials to synchronize data with Amazon Cognito Sync or to securely access other AWS services such as DynamoDB, S3, API Gateway and Lambda.

  1. We log into our authentication server
  2. We can now ask for Cognito credentials that allow us to access AWS restricted resource (in our case a lambda). The device initiates a request for the credentials (login) to our authentication server
  3. Our authentication server call GetOpenIdTokenForDeveloperIdentity which will register (or retrieve if already existing) a Cognito IdentityId and an OpenID Connect token based on an UID (a string that uniquely identify the user or device).
  4. Whenever the device want to access an AWS resource, the Amazon SDK will retrieve the credentials we need directly from Cognito.

Now that we are authenticated we can access our lambda named CognitoAuthTest as the authenticated role allows access to this resource as seen below:

{
    "Version": "2012-10-17",
    "Statement": [
         [...]
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": [
               "arn:aws:lambda:REGION:ACCOUNT_ID:function:CognitoAuthTest"
            ]
        }
    ]
}

Using the Amazon SDK we can then call this lambda on the client:

LambdaInvokerFactory factory = new LambdaInvokerFactory(context, region, credentialsProvider);  
LambdaInterface lambdaAccess = factory.build(LambdaInterface.class);  
String result = lambdaAccess.readHiddenText();  

With LambdaInterface being

interface LambdaInterface {  
     @LambdaFunction(functionName = "CognitoAuthTest")
     String readHiddenText();
}

And check the response to make sure we are actually logged into Cognito.

Firebase

Firebase Authentication supports authentication using passwords, popular federated identity providers like Google, Facebook and can also be easily integrated with a custom backend. As for the authentication flow it is a bit simpler than Cognito, we request a Firebase token at our authentication server which, with just one API call, retrieves it from Firebase using a UID that uniquely identifies the user or device you are authenticating:

FirebaseAuth.getInstance().createCustomToken(uid)  
    .addOnSuccessListener(customToken -> {
        // Send token back to client
    });

And return it to the client. Client-side we then need to sign in using this custom token:

FirebaseAuth.getInstance().signInWithCustomToken(token)  
    .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
        @Override
        public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
                // success
            } else {
                // error
            }
        }
    });

One call on the authentication server, one call on the client and everything else is handled internally.
To verify we are indeed logged in we try to access a database reference which by default is accessible only by authenticated users.

final FirebaseDatabase database = FirebaseDatabase.getInstance();  
final DatabaseReference myRef = database.getReference("test_reading");  
myRef.addValueEventListener(new ValueEventListener() {  
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        myRef.removeEventListener(this);
        String value = dataSnapshot.getValue().toString();
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        DatabaseException databaseException = databaseError.toException();
        Log.e("FirebaseResourceAccess", "accessing resource failed!", databaseException);
    }
});

So what should I choose?

When considering pro and cons we can talk about the services offered and ease of implementation.
With Cognito you get access to all the Amazon stack and especially Lambda which are only beta on Google side. Pretty much every other Amazon service has a Google equivalent.
With Firebase you get access to all their stack which target particularly mobile platforms and give you a realtime database, cloud messaging, analytics… You need to keep in mind though that most of these services would only work on devices that have Google Play Services installed which exclude Amazon devices for instance (more information here).
Cognito is pretty easy to integrate server side but is harder on the client side. You need to inherit from AWSAbstractCognitoDeveloperIdentityProvider and provide a way to retrieve the Cognito token from your server. On the other hand, Firebase Authentication is really trivial to implement, one call on the server, one on the client. Everything else is handled by the sdk. We didn’t test Firebase UI but it looks like a quite nice solution if you don’t want to have to deal directly with the really complex authentication flows of most social services.
Something to also take in consideration is that we didn't take pricing into account in our comparison and Amazon mobile SDKs are open while Firebase is a gigantic black box.

Wait, do I even need to choose?

All in all and as always this depends on the stack you use or the one you want to use. Amazon being more popular in this area you’ll probably choose Cognito to integrate with Lambda and API Gateway for the API side of things and with Firebase for analytics, remote configs, cloud messaging and the real time database.

Enjoyed this article? There's more...

We send out a small, valuable newsletter with the best stories, app design & development resources every month.

No spam, no giving your data away, unsubscribe anytime.

About Novoda

We plan, design, and develop the world’s most desirable software products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.

Let’s get in contact

Stay in the loop!

Hear about our events, blog posts and inspiration every month

Subscribe to our newsletter