DEV Community

Binh Bui for AWS Community Builders

Posted on • Updated on

Allow user access to your API without authentication (Anonymous user access)

Allow user access to your API without authentication (Anonymous user access)

Photo by Marek Okon

Amazon Cognito identity pools support both authenticated and unauthenticated users. Unauthenticated users receive access to your AWS resources even if they aren't logged in with any of your identity providers (IdPs). This degree of access is useful to display content to users before they log in. Each unauthenticated user has a unique identity in the identity pool, even though they haven't been individually logged in and authenticated.

This article describes the case where your user chooses to switch from logging in with an unauthenticated identity to using an authenticated identity.
We will demo the full solution for this case. You can get code for this solution from GitHub

The demo has 2 parts:

  1. Infrastructure: Setup the infrastructure for the demo.
  2. /app This is a frontend application. write by Vue3

Infrastructure: Setup the infrastructure for the demo.

 // create a auth service, this will create a user pool and user pool client
    // it also creates 2 roles, one for unauthenticated and one for authenticated
    // the roles will be used to create the authorizer and the integration
    const auth = new AuthService(this, 'AuthService', {});

    // ==== HTTP API ====
    const api = new HttpApi(this, 'HttpApi', {
      description: 'This is a sample HTTP API',
      corsPreflight: {
        allowHeaders: [
          'Content-Type',
          'X-Amz-Date',
          'Authorization',
          'X-Api-Key',
          'X-Amz-Security-Token',
        ],
        allowMethods: [
          CorsHttpMethod.OPTIONS,
          CorsHttpMethod.GET,
          CorsHttpMethod.POST,
          CorsHttpMethod.PUT,
          CorsHttpMethod.PATCH,
          CorsHttpMethod.DELETE,
        ],
        allowCredentials: true,
        allowOrigins: ['http://localhost:8080'],
      },
    });
    // add http iam authorizer
    const authorizer = new apiGatewayAuthorizers.HttpIamAuthorizer();

    // lambda function
    const fn = new NodejsFunction(this, 'SampleFunction', {
      entry: './lambda/index.ts',
      runtime: lambda.Runtime.NODEJS_14_X,
      handler: 'main',
      architecture: lambda.Architecture.ARM_64,
      logRetention: logs.RetentionDays.ONE_WEEK,
      bundling: {
        externalModules: ['aws-sdk'],
        minify: true,
      },
    });

    // add route with lambda integration
    api.addRoutes({
      path: '/api/test',
      methods: [HttpMethod.GET],
      integration: new apiGatewayIntegrations.HttpLambdaIntegration(
        'fn-integration',
        fn,
        {}
      ),
      authorizer, // use IAM authorizer
    });

    // allow unauthenticated access to the API
    // This is very important for the API to work
    auth.unAuthRole.attachInlinePolicy(
      new iam.Policy(this, 'UnAuthPolicy', {
        statements: [
          new iam.PolicyStatement({
            actions: ['execute-api:*'],
            resources: [
              // allow unauthenticated access to the API /api
              // arn:aws:execute-api:region:account-id:api-id/stage/METHOD_HTTP_VERB/Resource-path
              `arn:aws:execute-api:${Stack.of(this).region}:*:*/*/*/api/*`,
            ],
          }),
        ],
      })
    );

    // output api url
    new CfnOutput(this, 'ApiUrl', {
      value: `${api.apiEndpoint}/api/test`,
    });
  }
Enter fullscreen mode Exit fullscreen mode
  1. First, we create a user pool and user pool client. I have written the code in aws-cognito-anonymous-user-access-api

The use pool has 2 roles, one for unauthenticated and one for authenticated. The unauthenticated role will be used to create the authorizer and the integration. And a Cognito identity pool will be created. You must enable the feature allowUnauthenticatedIdentities in the identity pool.

// cognito identity providers
const identityPool = new cognito.CfnIdentityPool(this, 'CognitoIdentityProvider', {
  allowUnauthenticatedIdentities: true,
  cognitoIdentityProviders: [
    {
      clientId: client.userPoolClientId,
      providerName: (userPool as cognito.UserPool).userPoolProviderName,
    },
  ],
})
Enter fullscreen mode Exit fullscreen mode
  1. Next, we create an HTTP API. You can see my article How to create an HTTP API in AWS Lambda to understand how to create an HTTP API.
  2. Finally, we need to modify the unauthenticated role to allow unauthenticated access to the API. This step is very important for the API to work.
auth.unAuthRole.attachInlinePolicy(
  new iam.Policy(this, 'UnAuthPolicy', {
    statements: [
      new iam.PolicyStatement({
        actions: ['execute-api:*'],
        resources: [
          // allow unauthenticated access to the API /api
          // arn:aws:execute-api:region:account-id:api-id/stage/METHOD_HTTP_VERB/Resource-path
          `arn:aws:execute-api:${Stack.of(this).region}:*:*/*/*/api/*`,
        ],
      }),
    ],
  })
)
Enter fullscreen mode Exit fullscreen mode

Frontend application.

We use AWS-amplify to create a frontend application. AWS Amplify is a JavaScript library for frontend and mobile developers building cloud-enabled applications. You can see more here

Look into the file App.vue you can see my snippet code to make API call with the unauthenticated users (use AWS IAM authorizer).

// get current credentials. With aws-amplify, you can get current credentials from `Auth.currentUserCredentials`
const cre = await Auth.currentCredentials();
const signedRequest = sigV4Client
.newClient({
    accessKey: cre.accessKeyId,
    secretKey: cre.secretAccessKey,
    sessionToken: cre.sessionToken,
    region: awsExports.aws_cognito_region,
    endpoint: config.baseURL,
})
.signRequest({
    method: config.method,
    path: config.url,
    body: config.data,
});

Enter fullscreen mode Exit fullscreen mode

sigV4Client is a helper class to sign requests. You can see more here sigV4Client. You need to sign the request before you send it to the API.

Deploy to AWS

Deploy backend to AWS.

yarn deploy
Enter fullscreen mode Exit fullscreen mode

After deploy, you can something export to file cdk-outputs.json.
Edit file app/src/aws-exports.js and replace the value of the key apiUrl with the value of the key ApiUrl in the file cdk-outputs.json...

Testing

cd app
yarn install
yarn dev
Enter fullscreen mode Exit fullscreen mode

The application will be running on localhost:8080. Open the browser and go to http://localhost:8080 and see the result.

{
  "accessKey": "ASIA36FRBAMP6DINCSON",
  "accountId": "xxx",
  "callerId": "AROA36FRBAMPTRORUFVL7:CognitoIdentityCredentials",
  "cognitoIdentity": {
    "amr": ["unauthenticated"],
    "identityId": "ap-southeast-1:af38fa97-b29f-470b-8224-f58cc561e333",
    "identityPoolId": "ap-southeast-1:3cd835c2-19a8-40f5-8f14-2e30924a5fd3"
  },
  "principalOrgId": "aws:PrincipalOrgID",
  "userArn": "arn:aws:sts::xxxx:assumed-role/CdkStarterStackStack-AuthServiceIdentityPoolUnAuth-1GDNK5A20FXPC/CognitoIdentityCredentials",
  "userId": "AROA36FRBAMPTRORUFVL7:CognitoIdentityCredentials"
}
Enter fullscreen mode Exit fullscreen mode

Wow, you can see the result of the API call without authentication.

Clean up

Don't forget to clean up.

npx cdk destroy
Enter fullscreen mode Exit fullscreen mode

Thanks for reading. I hope you find this article helpful. If you have any questions, please free leave a comment. I will try to answer your questions.

Top comments (2)

Collapse
 
binhbv profile image
Binh Bui

This approach uses the AWS Security Token Service (AWS STS) to create and provide trusted users with temporary security credentials that can control access to your AWS resources.
Temporary security credentials are short-term and will expire in a few minutes to several hours(you can change the time).
So to keep using anonymous access, you should generate new temporary security credentials every time you need to access AWS resources(API endpoint)

This approach is the most secure way to allow the end-user to call the API endpoint if you don't want to require the user must log in before access to the API endpoint.

You can read more about Temporary credentials here

Collapse
 
ahmedgaafer profile image
ahmedgaafer

how can I prevent attacks on this approach and keep using anonymus user calls?

or is it a down side of this approach that it is vurnable to attacks?