Firebase authentication vs AWS Cognito

Amazon Web-ServicesFirebaseGoogle AnalyticsFirebase AuthenticationAmazon Cognito

Amazon Web-Services Problem Overview


We are building a mobile and web app on AWS using API Gateway and Lambda and are currently evaluating if we should use AWS Cognito or Firebase Auth.

AWS Cognito integrates nicely into API Gateway and Lamdba e.g. only authenticated users can execute certain API calls. Can the same behaviour be reached if we use Firebase Authentication instead? Any good or bad experience with this?

Amazon Web-Services Solutions


Solution 1 - Amazon Web-Services

We are doing the same.

We started with Cognito but moved to Firebase because we were not satisfied with the way AWS Android SDK implements the authentication flow with Google and Facebook: the code is quite old, it makes use of deprecated methods and generally requires rewriting. On the other hand, Firebase authentication is obviously working seamlessly.

When you don't use Cognito, you need to implement your custom authenticator in AWS API Gateway which is quite easy and is described in https://aws.amazon.com/blogs/mobile/integrating-amazon-cognito-user-pools-with-api-gateway/. Firebase instructions for token validation are in https://firebase.google.com/docs/auth/admin/verify-id-tokens

The following is an excerpt of my authenticator's code:

'use strict';

// Firebase initialization
// console.log('Loading function');
const admin = require("firebase-admin");
admin.initializeApp({
  credential: admin.credential.cert("xxx.json"),
  databaseURL: "https://xxx.firebaseio.com"
});
// Standard AWS AuthPolicy - don't touch !!
...
// END Standard AWS AuthPolicy - don't touch !!

exports.handler = (event, context, callback) => {
    // console.log('Client token:', event.authorizationToken);
    // console.log('Method ARN:', event.methodArn);

    // validate the incoming token
    // and produce the principal user identifier associated with the token

    // this is accomplished by Firebase Admin
    admin.auth().verifyIdToken(event.authorizationToken)
        .then(function(decodedToken) {
            let principalId = decodedToken.uid;
            // console.log(JSON.stringify(decodedToken));

            // if the token is valid, a policy must be generated which will allow or deny access to the client

            // if access is denied, the client will recieve a 403 Access Denied response
            // if access is allowed, API Gateway will proceed with the backend integration configured on the method that was called

            // build apiOptions for the AuthPolicy
            const apiOptions = {};
            const tmp = event.methodArn.split(':');
            const apiGatewayArnTmp = tmp[5].split('/');
            const awsAccountId = tmp[4];
            apiOptions.region = tmp[3];
            apiOptions.restApiId = apiGatewayArnTmp[0];
            apiOptions.stage = apiGatewayArnTmp[1];
            
            const method = apiGatewayArnTmp[2];
            let resource = '/'; // root resource
            if (apiGatewayArnTmp[3]) {
                resource += apiGatewayArnTmp[3];
            }
            

            // this function must generate a policy that is associated with the recognized principal user identifier.
            // depending on your use case, you might store policies in a DB, or generate them on the fly

            // keep in mind, the policy is cached for 5 minutes by default (TTL is configurable in the authorizer)
            // and will apply to subsequent calls to any method/resource in the RestApi
            // made with the same token

            // the policy below grants access to all resources in the RestApi
            const policy = new AuthPolicy(principalId, awsAccountId, apiOptions);
            policy.allowAllMethods();
            // policy.denyAllMethods();
            // policy.allowMethod(AuthPolicy.HttpVerb.GET, "/users/username");

            // finally, build the policy and exit the function
            callback(null, policy.build());
        })
        .catch(function(error) {
            // Firebase throws an error when the token is not valid
            // you can send a 401 Unauthorized response to the client by failing like so:
            console.error(error);
            callback("Unauthorized");
        });
};

We are not in production, yet, but tests on the authenticator show that it behaves correctly with Google, Facebook and password authentication and it is also very quick (60 - 200 ms). The only drawback I can see is that you will be charged for the authenticator lambda function, while the Cognito integrated authenticator is free.


Update after almost 1yr

I moved away from API Gateway custom authenticator, mainly because I've not been able to automate its deployment with cloudformation scripts. My solution is now to have authentication directly within the API caching tokens for some time, like the Authenticator does, so to avoid excessive validations.

Solution 2 - Amazon Web-Services

TL;DR; Firebase > Cognito

We started with Cognito first, but we eventually realized it has an atrocious smell to it when it comes using Federated Identities (e.g., Google Sign-in, Facebook Login, etc.). For Cognito User Pools (i.e., allowing a user to sign up/in with a username and password), you can use the built-in API Gateway Cognito User Pool Authorizer and it works beautifully. You don't need to write your own custom authorizer or anything.

However, if you want to support Federated Identities, you need to change the authentication on your API gateway to IAM Auth, and then have EVERY client sigv4 sign the requests, which turned out to be a thorn in our side and cost significant development time. Option 2 was to have API Gateway generate you code for your API calls for every client... which in my opinion is a testament to the cumbersome integration with Cognito.

We got Firebase working through the custom authorizer for API Gateway. Was a breeze on all clients (iOS, Android and Web). The API Gateway endpoints were linked up to Lambda functions, which were able to communicate with DynamoDB, S3, and other web services on behalf of the user calling the endpoint. The lambda functions knew who the calling user was because the custom authorizer returned the email address in the JWT.

Here's a pretty basic Firebase custom authorizer that returns the user email in the JWT as the principalId:

'use strict';
console.log('Loading function');

var admin = require('firebase-admin');
var serviceAccount = require('./my-secret-json.json');

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: 'https://my-app.firebaseio.com'
});

exports.handler = (event, context, callback) => {
    var token = event.authorizationToken;

    if (token == null) {
        callback('Invalid token');
    }
    else {
        admin.auth().verifyIdToken(token)
            .then(function (decodedToken) {
                var email = decodedToken.email;
                var policy = generatePolicy(email);
                callback(null, policy);
            }).catch(function (error) {
                console.log(error);
                callback('Unauthorized'); 
            });
    }
};

var generatePolicy = function (email) {
    return {
        principalId: email,
        policyDocument: {
            Version: '2012-10-17',
            Statement: [
                {
                    Action: 'execute-api:Invoke',
                    Effect: email ? 'allow' : 'deny',
                    Resource: '*'
                }
            ]
        }
    };
}

You can then use $context.authorizer.principalId in your API Gateway mapping template to retrieve the email and pass it to lambda X.


I initially thought latency would be an issue, but that really doesn't seem to be the case. Any and all latency I've encountered is due to latency of the lambda that is being called due to cold start. I've noticed authorization lambdas live much longer than other lambdas.


This lambda gets called for every backend request. There are couple of things though:

  1. caching is enabled for 1hr for each JWT, so that greatly simplifies the calls.
  2. The lambda is being called constantly, so there shouldn't be a cold start, and
  3. The first MILLION lambda requests/month are free, and then it's $0.20 for every million requests/month after that. So unless you're having your API called BILLION times per month, you aren't going to incur outrageous cost.

Solution 3 - Amazon Web-Services

Aws documentation is pretty confused. The callbacks system for the different authentication steps is better documented in Firebase. The result is a cleaner code and a better control on the authentication flow. In addition, the Firebase user interface is more user-friendly. If you are planning to use content providers and sync adapters, I would suggest to use Firebase because you will have simple methods for data synchronization between the local and the remote (Firebase) db

Solution 4 - Amazon Web-Services

For me the deal breaker is ability to export users with all the details, if you decide to move to some other authentication service provider.

While this is possible in Firebase, it is not available in AWS Cognitio! You can enter any time of year but you can never leave :). https://forums.aws.amazon.com/thread.jspa?threadID=296932

Solution 5 - Amazon Web-Services

aws cognito gives more ways to authenticate users than firebase. Especially, if you are building a game, it gives facility to login through google and ios game centres. It provides syncing leaderboards and achievements of game centre provides. Automatic state synchronisation feature is there in Cognito. But definitely, it is very confusing. It takes too much time for implementation. On the other hand, firebase authentication is pretty fast to implement.

Solution 6 - Amazon Web-Services

Just in case that you are using Unity, currently the Unity SDK does not support Cognito User Pool. (That is, AWS hosted list of users) I am currently hesitating because of this. See my post here that they confirmed it is true, currently (26/06/2017) the feature is still not available which might shows a lack of attention to Unity users from them.

However if I use Firebase for logins I would need some more integrations for those credentials to use AWS services. (I would like to use S3 and DynamoDB but only logged in user can use it.) This also made me realize I should move everything to Firebase to save my time and frustrations as soon as possible. (The real time DB is pricier than S3/DynamoDB, but Unity has its own replacement of AWS MobileAnalytics)

AWS S3 recently got nicer UI, which I think is close to Google's level. But other than that I think Firebase's UI is much more joyful to use.

Also, Firebase authentication is free while Cognito is free up to 50k monthly active user. (The next 50k will cost 0.0055 which means if you have 100k MAU it will be 50000 * 0.0055 = 275 USD https://aws.amazon.com/cognito/pricing/)

One more thing, the AWS .NET documentation is a nightmare to read/search in my opinion.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTomasView Question on Stackoverflow
Solution 1 - Amazon Web-ServicespmosconiView Answer on Stackoverflow
Solution 2 - Amazon Web-ServicesazizjView Answer on Stackoverflow
Solution 3 - Amazon Web-ServicesNicola GallazziView Answer on Stackoverflow
Solution 4 - Amazon Web-ServicesZeniView Answer on Stackoverflow
Solution 5 - Amazon Web-Servicessn.anuragView Answer on Stackoverflow
Solution 6 - Amazon Web-Services5argonView Answer on Stackoverflow