

How to use Cognito with AppSync
source link: https://advancedweb.hu/how-to-use-cognito-with-appsync/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Access control in AppSync
AWS AppSync provides a managed GraphQL API that you can use to implement backend functionality for users. AppSync integrates with Cognito User Pools, which makes it easy to add sign-in (and sign-up) functionality to an API.
After signing in, you might want to implement different access levels for the users. Maybe some users can use queries and mutations others can not, or different users can access different objects.
In this article we’ll look into how to implement different authorization modes, and what tools Cognito and AppSync gives to define who can do what in a GraphQL API.
Authorization providers
To use any authorization mode, you need to configure that for the API. Since this article is about Cognito User Pools, we’ll add only that with a default of DENY.
How you configure the API auth providers changes a lot. Check out this article for a longer explanation.
Data model
The data model we’ll use is rather simple but it contains all the common access control schemes. There are users who can sign in and they can be admin
or user
. Normal users can query only themselves, but admins can get all the users in the database.
Then there are documents. There is an access level marking them PUBLIC
or SECRET
. Users have permission lists that define whether they can access SECRET documents or not. These lists are stored in the database for each user.
user1group = userpermissions = ["basic"]user2group = userpermissions = ["basic", "secret_documents"]admingroup = adminpermissions= ["basic"]document1level = PUBLICtext = "..."document2level = SECRETtext = "..."Read access graph
In GraphQL, this data model translates to this schema:
type User {
sub: ID!
permissions: [String!]!
}
enum Level {
SECRET
PUBLIC
}
type Document {
level: Level!
text: String!
}
type Query {
user(sub: ID!): User
me: User
documents: [Document!]!
allUsers: [User!]!
}
And we’ll implement these access control points:
- Only admins can call
allUsers
- Non-admins can only get themselves in the
user
query -
documents
returnSECRET
documents only if the caller has thesecret_documents
permission
Schema-based access control
AppSync provides a set of directives that you can use to define access control right in the GraphQL schema. This is convenient as it will be self-documenting and also you get it for free: just add the directive, and everything else is managed by AppSync.
There are some downsides to this approach though. The granularity you can use is the Cognito group, so you can define what different types of users can do, but you can not use, for example, a database value here. This is RBAC (Role-Based Access Control, don’t confuse it with IAM Roles).
The other downside is that what the directives do is kind of a magic and also the default (without a directive) changes in non-intuitive ways depending on the API configuration. It’s easy to end up breaking authorization if you are not careful.
You can add authorization directives to types and fields. You can define who can run a specific query or mutation, and also who can traverse the graph using a specific field.
In our data model, we can use this type of access control for the allUsers
query as we want to only allow it for admin users and nobody else. So, defining this is just adding some directives:
type Query {
allUsers: [User!]!
@aws_cognito_user_pools(cognito_groups: ["admin"])
@aws_auth(cognito_groups: ["admin"])
}
Why use @aws_cognito_user_pools
and @aws_auth
together? The former is effective if there are more than one authorization providers, and the latter if when there is only one. Using both is a safer way to define authorization.
Resolvers-based access control
But auth directives only go this far. If a query can be run by both admins and regular users but with different allowed parameters, directives are not enough on their own. This is implemented in the resolvers.
Decision based on user id and groups
AppSync provides information about the current user in the $context.identity
value that resolvers can use to check access.
In our data model, the user
query takes a sub
argument. It should allow only admins to query any user, and normal users can only query themselves.
To implement this, the $ctx.identity.sub
is the Cognito ID of the user, and the $ctx.identity.groups
is the list of groups the user belongs to.
#if (!$ctx.identity.groups.contains("admin") && $ctx.identity.sub != $ctx.args.sub)
$util.unauthorized()
#else
{
"version" : "2018-05-29",
"operation" : "GetItem",
"key" : {
"sub": {"S": $util.toJson($ctx.args.sub)}
},
"consistentRead" : true
}
#end
The $util.unauthorized()
throws an error. The above template checks the permissions (#if ...
), and either throws an Unauthorized
error or sends a request to DynamoDB.
Decision based on database value
The $ctx.identity
allows more fine-grained control, but sometimes it’s not enough. It is especially the case when access is determined based on a database value.
In our data model, the user objects in the database have a permissions
list. If it is needed for an access decision then we need to read it first.
This is where pipeline resolvers are needed as AppSync needs to do multiple things to provide the result.
The first step is to read the user object from the database and store it in the stash. The stash is a special place that is available for all resolvers in the pipeline, making it an ideal place to store objects that might be needed in later steps.
The request mapping:
{
"version" : "2018-05-29",
"operation" : "GetItem",
"key" : {
"sub": {"S": $util.toJson($ctx.identity.sub)}
},
"consistentRead" : true
}
This uses the $ctx.identity.sub
as that is the unique ID of the user returned by Cognito. In DynamoDB, that is the partition key for the user objects.
The response mapping:
#if ($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
$util.qr($ctx.stash.put("user", $ctx.result))
{}
The first step is error handling, as AppSync should terminate the processing if it can not read the user. Then it puts the $ctx.result
to the $ctx.stash.user
, so it will be available in the later resolvers, and they can use it to throw Unauthorized errors or not.
Filtering results
A special case of access control is to filter result elements. In our data model, all users can query the documents but only with a special permission will the result include SECRET
ones.
To implement this, we’ll use the user object in the stash stored in the previous step. Then the resolver queries the documents from the database:
{
"version" : "2018-05-29",
"operation" : "Scan"
}
This returns all the items (we don’t implement pagination here to make the example simple) and the response template needs to filter the results:
#if ($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
#set($results = [])
#foreach($res in $ctx.result.items)
#if($res.level == "PUBLIC" || $ctx.stash.user.permissions.contains("secret_documents"))
$util.qr($results.add($res))
#end
#end
$util.toJson($results)
With this resolver, the decision to include a given element in the result is based on a value stored in the database for the current user.
Conclusion
AppSync provides several ways to define access control for a GraphQL API. You can implement RBAC using Cognito groups using directives in the schema, and you can use more fine-grained logic in the resolvers.
Recommend
-
36
Alexander ZeitlerCreating an AWS Cognito user pool with OAuth flows using AWS CDKPublished on Sunday, October 4, 2020I tried to setup an
-
11
Authenticating AWS Cognito with LaravelMarch 16, 2020AWS Cognito is AWS’s authentication managed service that integrates natively with API Gateway & Application Load Balancer. Users can sign up directly with Cognito, Sign...
-
15
The Need You have this great Amplify App using AppSync GraphQL. You eventually find that you need to be able to access that data in your AppSync GraphQL database from tools other than your Amplify App. Its easy if you just ha...
-
10
Code is available on GitHub Cognito tokens When a client logs in to a Cognito user pool they get 3 tokens: a refresh_token, an
-
8
Code is available on GitHub Lambda with AppSync A resolver defines how a GraphQL field gets its value, such as what database query should AppSync run...
-
5
How to use DynamoDB with AppSync How to store, retrieve, and query items in a DynamoDB table with an AppSync resolver
-
4
Code is available on GitHub Cognito with AppSync When you have an AppSync API that you want users to access, you need to add authentication....
-
18
Code is available on GitHub Authorization in AppSync AppSync supports several ways for authorization, such as Cognito, AWS IAM, API key, and a
-
5
Code is available on GitHub SQL-based resolvers in AppSync RDS is the relational database managed...
-
10
Code is available on GitHub Arguments-based subscription filtering Originally, AppSync...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK