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:
- Infrastructure: Setup the infrastructure for the demo.
- /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`,
});
}
- 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,
},
],
})
- 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.
- 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/*`,
],
}),
],
})
)
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,
});
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
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
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"
}
Wow, you can see the result of the API call without authentication.
Clean up
Don't forget to clean up.
npx cdk destroy
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)
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
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?