Amazon Cognito and API Gateway AWS IAM Authorization

    It is very handy to have something out of the box when you want to add authentication and authorization for your web or mobile apps. Instead of building time consuming solutions or try to authenticate against custom providers where you still need to handle user management, authentication, and sync across devices, here is a cloud solution named AWS Cognito.

    Amazon Cognito lets you easily add user sign-up and sign-in to your mobile and web apps and have the options to authenticate users through social identity providers such as Facebook, Twitter, or Amazon, with SAML identity solutions, or by using your own identity system. Even more, you can assign different permissions to different users.

    Cognito User Pools support for groups and Cognito Federated Identities support for fine-grained Role-Based Access Control (RBAC).  With Groups support in Cognito, developers can easily customize users’ app experience by creating groups which represent different user types and app usage permissions.

    Speaking of permissions, support for fine-grained Role-Based Access Control (RBAC) in Cognito Federated Identities allows developers to assign different IAM roles to different authenticated users.

    Let’s build one consumer of Cognito authentication provider that will authenticate and authorize users to use different API operations. Some of the operations will be available for all authenticated users, and some will be resticted and availbale only for a small group of users.

    Build the API

    First, in order to obtain API ID, we build the API with several resources using AWS API Gateway. In this scenario we have one API named “office_api” with two resources:

    • user: GET
    • admin: GET

    As backed target we have set up Lambda function that only returns “Hello user” for User resource and “Hello admin“ for Admin resource. Do not forget to deploy the API in order to consume it. Deployment stage is named “api”.

    Create Cognito User Pool

    Navigate to AWS Cognito and choose “Manage your Users Pool”. Here we are going to create one user pool where user info will be stored.

    Follow the steps for creating the pool proposed by AWS console. You can choose “Review defaults” and create one default pool. More info about pool creation can be found on http://docs.aws.amazon.com/cognito/latest/developerguide/create-new-user-pool-console-quickstart.html

    After the user pool is created you will get “Pool Id”. You can find this number under pool’s General setting -> Pool Id. Value is in format <region_unique code>.

    From “General settings” menu navigate to “App clients” and register one. Number for “App client id” that is generated after registration will be included in all Web or Mobile apps that are going to use this pool. Of course, you can register different App clients for different applications.

    Create several users by entering their required attributes. More about sign up and sign in users in Cognito can be found under blog Cognito User Pool – Sign in, Register and Sign Up user process.

    Create Federated Identities

    With Cognito Federated Identities, users can sign-in through social identity providers such as Facebook and Twitter, or through custom identity solution. Cognito Federated Identity provides temporary credentials so you can control access to AWS resources from your app.

    Navigate to Cognito and choose “Manage Identity providers”. Create new “Identity pool” by providing Identity pool name and under “Authentication providers” choose Cognito. For User Pool ID enter “Pool Id” value and for App Client Id enter “App client id” value, both generated from previously created User Pool. Now, create the Identity pool. You’ll be asked for granting access to your resources:

    At his step Cognito created authentication roles. Click allow and the Identity pool will be created.

    By default, the authentication role defined as default for the Federated Identity will be applied to authenticated users. But, there is an option to change this approach by configuration available at ”Authenticated role selection” option under Identity pool -> Authentication providers.

    From drop down menu choose “Choose role from token” and for Role resolution choose “DENY”. With this configuration, Cognito will grant user permission from our Custom IAM roles that we are going to create instead using the default ones.

    As it says: “if no roles are specified in the token, the role resolution will be invoked. By default, it will fall back to the default role specified for this Identity Pool. You can also choose to DENY the request.” We don’t want to fall back to the default role so we choose DENY.

    More info about Identity pool creation can be found on http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-integrating-user-pools-with-identity-pools.html

    Create IAM roles

    Create two IAM roles, one for User and one for Admin. Navigate to IAM roles and choose “Web Identity” for “Select type of trusted entity”. For Identity Provider select Amazon Cognito and enter Identity Pool ID which is a unique number for the Federated Identity Pool with value formatted as <region>:<GUID>.

    Than create “Inline policy” per user role.

    Role: CognitoRoleSecureAPIAdmin

    {

    “Version”: “2012-10-17”,

    “Statement”: [

    {

    “Effect”: “Allow”,

    “Action”: [

    “execute-api:Invoke”

    ],

    “Resource”: [

                    “arn:aws:execute-api:us-east-1:<account>:<API ID>/*/GET/user”,

                    “arn:aws:execute-api:us-east-1:<account>:<API ID>/*/GET/admin”

    ]

    }

    ]

    }

     

    Role: CognitoRoleSecureApiUser

    {

    “Version”: “2012-10-17”,

    “Statement”: [

    {

    “Effect”: “Allow”,

    “Action”: [

    “execute-api:Invoke”

    ],

    “Resource”: [

                        “arn:aws:execute-api:us-east-1:<account>:<API ID>/*/GET/user”,

    ]

    }

    ]

    }

    Configure Roles in Cognito User Pool

    Let’s connect IAM roles with user Groups. Navigate to Cognito -> User pool and create two groups. First one name it Admin, select CognitoRoleSecureAPIAdmin for IAM role and set precedence. Because a user can belong to more than one group, each group can have assigned precedence number. This is a non-negative number that specifies the precedence of this group relative to the other groups that a user can belong to in the user pool. Zero is the top precedence value. Groups with lower precedence values take precedence over groups with higher or null precedence values. If a user belongs to two or more groups, it is the group with the lowest precedence value whose IAM role is applied to the “cognito:preferred_role” claim in the user’s ID token. Let’s set Precedence to 0 for Admin group.

    Create second group as User, select CognitoRoleSecureAPIUser for IAM role and set precedence to 1.

    Now add users to the groups. For example, we are going to add:

    • John to group User
    • Rose to group Admin and User

    Call the API

    The user (John or Rose) now can call the API Gateway, but the HTTP request must be signed with Signature Version 4 Algorithm. Signature Version 4 is the process to add authentication information to AWS requests. For security, most requests to AWS must be signed with an access key, which consists of an access key ID and secret access key. How Signature Version 4 works:

    • You create a canonical request.
    • You use the canonical request and some other information to create a string to sign.
    • You use your AWS secret access key to derive a signing key, and then use that signing key and the string to sign to create a signature.
    • You add the resulting signature to the HTTP request in a header or as a query string parameter.

    When AWS receives the request, it performs the same steps that you did to calculate the signature. AWS then compares the calculated signature to the one you sent with the request. If the signatures match, the request is processed. If the signatures don’t match, the request is denied.

    In Node.js for Signature Version 4 signature we can use the following library: https://github.com/mhart/aws4

    In order to make the call user should authenticate itself and get Token ID. For user authentication we are using following libraries:

    https://github.com/aws/amazon-cognito-identity-js

    https://github.com/aws/amazon-cognito-js

    Next, generated token ID is used for getting AWS credentials:

    function getAWSCredentials(idToken) {

    AWS.config.credentials = new AWS.CognitoIdentityCredentials({

    IdentityPoolId: ‘<region>:<GUID>’,

    Logins: {

    ‘cognito-idp.<region>.amazonaws.com/<userPoolId>’: idToken

    }

    });

    }

    After the credentials are generated you can make the call to the API. We are using the following snippet:

    function queryApi(credentials) {

    const options = {

    service: ‘execute-api’,

    region: ‘us-east-1′,

    url: “https://<apiID>.execute-api.<region>.amazonaws.com/api/admin”,

    hostname: <apiID>.execute-api.<region>.amazonaws.com’,

    path: ‘/api/admin’,

    method: ‘GET’,

    headers:  {

    ‘Content-Type’: ‘application/json’,

    ‘Accept’: ‘application/json’

    }

    };

    aws4.sign(options);

    https.request(options, function(res) { res.pipe(process.stdout); }).end()

    }

    Where credentials = AWS.config.credentials.

    First, we’ve tested with the user John that is associated with AppUser group and he is able to access only the /api/user resource. He is getting response “Hello user”. When he tries to access /api/admin he is receiving message Unauthorized.

    After that, we’ve tested with second user Rose that is associated with both groups (AppUser and ApiAdmin) and she is able to invoke both methods /api/user and /api/admin.

    Conclusion

    Here are some key points to remember about fine-grained RBAC. Support for fine-grained Role-Based Access Control (RBAC) in Cognito Federated Identities allows developers to assign different IAM roles to different authenticated users. With fine-grained RBAC, a developer can map federated users to different IAM roles. With RBAC, you can leverage the roles passed via the ID token that was assigned by the user pool. For each authentication provider configured in your identity pool user permissions are controlled via AWS IAM roles that you create.

    Great news is that there is no additional cost for using groups within a user pool. You pay only for Monthly Active Users (MAUs). Cognito Federated Identities feature for controlling user permissions and generating unique identifiers is free with Amazon Cognito.

    Further reading: https://aws.amazon.com/blogs/mobile/building-fine-grained-authorization-using-amazon-cognito-user-pools-groups/