AppSync HTTP resolver to update Cognito User Attributes
Imagine a simple task where you want to have an intermediate step in your AppSync pipeline resolver to update a Cognito user attribute.
The simple way to achieve this would be to build a lambda function that will use AWS SDK to call Cognito API action and put it as a Function inside the pipeline. However is it the most efficient way?
Let’s have a look.
AWS SDK calls Cognito HTTP API under the hood and requests are signed with v4 AWS Signature.
AppSync HTTP resolver are flexible and allow us to attach an IAM role to call AWS APIs directly and automatically signing the requests for us.
Let’s create a role with a policy allowing AdminUpdateUserAttributes action on our User Pool:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "cognito-idp:AdminUpdateUserAttributes",
"Resource": "arn:aws:cognito-idp:us-east-1:12345678910:userpool/us-east-1_xxx"
}
]
}
And add AppSync to list of trusted entities on that role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Now with the role in place we can create a JSON config of our future datasource, let’s call it http.json:
{
"endpoint": "https://cognito-idp.us-east-1.amazonaws.com/",
"authorizationConfig": {
"authorizationType": "AWS_IAM",
"awsIamConfig": {
"signingRegion": "us-east-1",
"signingServiceName": "cognito-idp"
}
}
}
And provision a new datasource with a new role attached:
# aws appsync create-data-source --api-id XXXXXXXXXXXXXXXXXXXX --name CognitoDataSource --type HTTP --http-config file://http.json --service-role-arn arn:aws:iam::12345678910:role/appsync-update-cognito-user-attributes-role
Now with a new datasource in place we can build a resolver. Let’s attach it to a simple query which will return a boolean true/false depending on 200 response from the API:
type Query {
updateUser: Boolean
}
The Request Mapping template to call Cognito API to update user attribute:
#set($body = {})#set($attribute={})
$util.qr($attribute.put("Name", "email_verified"))
$util.qr($attribute.put("Value", "true"))#set($UserAttributes = [])
$util.qr($UserAttributes.add($attribute))
$util.qr($body.put("UserAttributes", $UserAttributes))#set($body.Username = $ctx.identity.sub)
#set($body.UserPoolId = "us-east-1_xxx"){
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/",
"params": {
"headers": {
"content-type": "application/x-amz-json-1.1",
"x-amz-target":"AWSCognitoIdentityProviderService.AdminUpdateUserAttributes"
},
"body": $util.toJson($body)
}
}
Response Template:
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
## If the response is not 200 then return an false. Else return true
#if($ctx.result.statusCode == 200)
true
#else
false
#end
Simple query:
query updateUser {
updateUser
}
And the result:
{
"data": {
"updateUser": true
}
}
Voila! We spared ourselves from writing a Lambda function by using native AppSync functionality.
You may have noticed x-amz-target
header in the HTTP request — this one is quite important, it tells API which service and method we want to invoke on the API.
To get the service name I had to search which class provides AdminUpdateUserAttributes method here: https://aws-amplify.github.io/aws-sdk-ios/docs/reference/
The service name will be under Declared in
section.
Also you can see what amplify library is using to call the same API to give yourself a hint: https://github.com/aws-amplify/amplify-js/blob/master/packages/amazon-cognito-identity-js/src/Client.js
Happy coding!