Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase authentication vs AWS Cognito [closed]

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?

like image 491
Tomas Avatar asked Nov 17 '16 08:11

Tomas


People also ask

Should you use firebase for authentication?

Firebase Authentication aims to make building secure authentication systems easy, while improving the sign-in and onboarding experience for end users. It provides an end-to-end identity solution, supporting email and password accounts, phone auth, and Google, Twitter, Facebook, and GitHub login, and more.

Can AWS Cognito be used for authorization?

You can use Amazon Cognito to control permissions for different user groups in your app. This ensures that users have appropriate access to backend resources, determined by the group they belong to. Amazon Cognito makes it easier for you to manage user identities, authentication, and permissions.

How long does firebase auth session last?

By default, a session ends (times out) after 30 minutes of user inactivity. There is no limit to how long a session can last.

What is the difference between Cognito user pool and identity pool?

Short description. User pools are for authentication (identity verification). With a user pool, your app users can sign in through the user pool or federate through a third-party identity provider (IdP). Identity pools are for authorization (access control).


2 Answers

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.

like image 196
pmosconi Avatar answered Oct 12 '22 15:10

pmosconi


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.
like image 40
azizj Avatar answered Oct 12 '22 15:10

azizj