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?


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.


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.

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


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


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.