Using Federated Identities to access non-public S3 objects as an unauthenticated user.
Tags: aws, amplify, serverless, authentication
Published: 2019-07-22 18:05:27

I recently worked on a project that had some interesting requirements. The project consisted of a main app that would lean heavily on Amplify (obviously 😃), making use of Auth, AppSync, Storage to name a few. All the services and data needed to be locked down to the users that created it and be accessible by the application administrators. The second part of the project consisted of an unknown number of external applications. These applications would be written by other external developers and would not use the Amplify services generated by the main application. These applications would need to be able to access some assets in the non-public S3 buckets and not be authenticated. 

These apps will not be able to lean as heavily on Amplify as the main application does, so let's cover how we can do this. 

As with everything in AWS, there is more than one way to skin a cat. We still need to access these objects in a secure way and we can achieve this with a combination of identity pools [Federated identities](https://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html) and AWS Signature Version 4. 

### Federated Identities

> Amazon Cognito identity pools provide temporary AWS credentials for users who are guests (unauthenticated) and for users who have been authenticated and received a token.

First, we need to allow unauthenticated identities, log in to the AWS console and choose Cognito then choose to manage Federated Identities. Choose the identity pool that has been created for the external application and click **Edit identity pool** in the top right. Scroll down and click **Unauthenticated identities**. Select the checkbox and save your changes. AWS will now provide you with temporary credentials. 

With this done we can now set up our application to make unauthenticated requests to AWS services with the temporary credentials generated by the identity pool.

We still need a way to create these temporary credentials, to do this we are gonna make use of the `aws-amplify` npm package as it makes it super easy. We will use the `Auth` module. Obviously you could use the `aws-sdk` and use the Cognito package from that. They will both achieve the same thing I just happen to like the amplify offering.

In your index.js file, add the following:

    import Amplify, { Auth } from "aws-amplify";
    
    Amplify.configure({
      Auth: {
        identityPoolId: "eu-west-1:****-***********-***",
        region: "eu-west-1"
      }
    });

This is the id of the identity pool that is generating the unauthenticated identities.

Now you can use the following to gain access to the temporary credentials

    const creds = await Auth.currentCredentials();

### AWS4

But what do we do with these newly available credentials? This is where  AWS Signature Version 4 comes in. This allows us to sign our requests, the signing process adds a token to the query string, authenticating us with our temporary credentials. So in our case we want access to an object in S3. We can sign the request prior to sending it, this allows AWS to know that the request is coming from someone that holds these unauthenticated credentials and not some random chap on the internet. To sign our requests we are making use of the [`aws4`] (https://www.npmjs.com/package/aws4) package.

![Birds](https://media.giphy.com/media/26Ff9P7oFlrqX2epq/giphy.gif)

It works with nearly all amazon services,  including S3, EC2, DynamoDB, Kinesis, Lambda, SQS, SNS, IAM, STS, RDS, CloudWatch, CloudWatch Logs, CodeDeploy, CloudFront, CloudTrail, ElastiCache, EMR, Glacier, CloudSearch, Elastic Load Balancing, Elastic Transcoder, CloudFormation, Elastic Beanstalk, Storage Gateway, Data Pipeline, Direct Connect, Redshift, OpsWorks, SES, SWF, AutoScaling, Mobile Analytics, Cognito Identity, Cognito Sync, Container Service, AppStream, Key Management Service, Config, CloudHSM, Route53 and Route53 Domains.

To sign our request for an S3 object we can make use of the following function. This returns 

    import aws4 from "aws4";
    import { Auth } from "aws-amplify";
    
    export default async (src: string) => {
      const credentials = await Auth.currentCredentials();
      const request = {
        host: "s3.eu-west-2.amazonaws.com",
        path: `/${process.env.REACT_APP_S3_BUCKET}/${src}`,
        service: "s3",
        signQuery: true
      };
      const protocol = "https://";
      const signedRequest = aws4.sign(
        request,
        Auth.essentialCredentials(credentials)
      );
    
      return protocol + signedRequest.host + signedRequest.path;
    };

We can use the return URL to access our S3 object in a secure way. Give it a try!

Granted my situation is pretty irregular but I can definitely see where this would be of some use. I hope this breakdown is useful to others needing to sign requests to AWS services from unauthenticated users.