AWS Amplify is a framework that allows frontend and mobile developers to add serverless cloud backends to their apps. My last article, “Easy Serverless Authentication with AWS Amplify,” explained how to set up a serverless authentication system with AWS Amplify. Here, we’ll review six sample authorization schemas that can be used to manage permissions for a GraphQL API that’s been set up using Amplify.
We’ve chosen these particular schemas because they cover different access schemes. Ordered from simplest to most complex, we’ll explain such features as field/connection-based permissions, individual user and individual group permissions, public or private permissions, and IAM permissions for background services.
API Access
Let’s start at the beginning. All API access will be protected by default if you add authentication and a non-public GraphQL API to your Amplify project.
Users can access your API, but only if they are signed in. This default setup prevents public access for unauthenticated users.
1. Private To-Do List
Our first example is the prototypical to-do list. This type of app was chosen due to its simple access semantics. Once a user has signed up for the AWS Amplify service, they are able to create tasks that only they can view, update, or delete.
The GraphQL schema looks like this:
type Task @model @auth(rules: [{allow: owner}]) {
id: ID!
description: String!
}
Above is the simplest example of an Amplify GraphQL schema using authorization. The above code is a short version but can also be written like so:
type Task
@model
@auth(
rules: [
{
allow: owner,
operations: [create, update, delete, read]
}
]
) {
id: ID!
description: String!
}
As you can see, the auth
directive accepts a rules array. This array can define different groups and users and the operations they allow, in this case “create
, update
, delete
, read
”.
Are you an AWS expert? We’re hiring new tech bloggers.
2. Simple Blog
The next example is a simple, private blog. Blogs are generally a bit more complex than a to-do list, since you of course want other people to read it. Logged-in users can create, update, and delete their own articles, and everyone—even users who are not registered for the API—can read them but can’t do anything else with other users’ articles.
Here’s what the GraphQL schema looks like:
type Article
@model
@auth(
rules: [
{ allow: public, operations: [read]},
{ allow: owner }
]
) {
id: ID!
publishedAt: AWSDateTime!
title: String!
content: String!
}
Owner authorization is set so that the owner has edit and delete access, and a read
operation is set to “public” for public read access.
If private
were used in place of owner
, everyone who was logged in to the API would be able edit every article, not just the user who created it.
3. Advanced Blog
This type of blog goes a step further by attempting to enable collaboration. Registered users can create articles, and they can assign other users as editors to grant them permission to update their posts. When they finish drafting their articles, they publish them for other logged-in users to read.
Because the auth directive parameter accepts a rules array, it can be modeled with multiple “owner” and “group” rules.
The GraphQL schema looks like this:
type Article
@model
@auth(
rules: [
{ allow: owner },
{
allow: owner
ownerField: "editors"
operations: [update, read]
},
{
allow: groups
groupsField: "readers"
operations: [read]
}
]
) {
id: ID!
title: String!
content: String!
publishedAt: AWSDateTime
editors: [String]!
readers: [String]!
}
The first rule defines that the creator of the article is authorized to read, edit, and delete it.
The second rule defines that all users in the editors
field of the article have permission to view and update it.
The third rule defines that a group in the readers
field of the article is allowed to view it.
This enables you to add any group to the readers field once the article is published and allow all users in that group to read it.
The idea is that you know your editors beforehand and can explicitly add each of them, but you don’t know all of the service’s registered users. So you could add a “reader” group, for example, where every new user is automatically added when signing up for the blog.
4. Chat
Next, let’s look at a chat service. Here, the system has channels and messages with a 1:n relation.
Logged in users can join channels that are created by admins. They’re able to write messages. They can also read everyone’s messages, but only in the channels they have joined.
Following is what the GraphQL schema looks like:
type Channel
@model
@auth(
rules: [
{
allow: groups
groups: ["Admin"]
operations: [create, update, delete, read]
},
{ allow: private, operations: [read] }
]
) {
id: ID!
name: String!
users: [String] @auth(rules: [{ allow: private }])
messages: [Message]
@connection(
name: "channelMessages"
keyField: "channelId"
sortField: "createdAt"
)
@auth(
rules: [
{
allow: owner
ownerField: "users"
operations: [create, read]
}
]
)
}
type Message
@model(queries: null)
@key(
name: "channelMessages"
fields: ["channelId", "text"]
) {
id: ID!
text: String!
createdAt: AWSDateTime!
channelId: ID!
channel: Channel
@connection(
name: "channelMessages"
keyField: "channelId"
)
}
This is a little more complex than the previous three examples, so let’s go through this step by step.
You have two types of resources—Channel
and Message
, which are linked with the connection
directive.
The model
directive on the Message
type deactivates all top-level queries by setting the queries
parameter to null
. This means messages can only be queried via the message
connection in the Channel
type.
The message
connection on the Channel
type is protected so that only users
in the users field can query this connection. Since the update
and delete
operations are missing, nobody can edit or delete posted messages.
The users
field of the Channel
type has special field-level permission that allows all registered users to add or remove themselves to/from this field. This enables users to join and leave channels.
5. Forum
In an online forum, you want everyone to be able to read your posts, but as a forum owner, you also want to ensure moderators can update or delete problematic posts. The next example is a forum where logged-in users can create topics and comment on all other users’ topics. Moderators have the ability to edit and delete all topics and comments. Topics and comments can be read by all users.
Here’s what the GraphQL schema looks like:
type Topic
@model
@auth(
rules: [
{ allow: owner },
{
allow: groups,
groups: ["Moderator"],
operations: [read, update, delete]
},
{ allow: private, operations: [read] }
]
) {
id: ID!
title: String!
comments: [Comment]
@connection(
keyName: "topicComments"
fields: ["id"]
)
}
type Comment
@model
@key(
name: "topicComments",
fields: ["topicId", "content"]
)
@auth(
rules: [
{ allow: owner },
{
allow: groups
groups: ["Moderator"]
operations: [read, update, delete]
},
{ allow: private, operations: [read] }
]
) {
id: ID!
topicId: ID!
content: String!
topic: Topic @connection(fields: ["topicId"])
}
As in the chat example, you have two types of resources. This time, they are called Topic
and Comment
, which are linked via a connection
.
There are three auth rules
—one for the creator, one for the moderators, and one for everyone else.
The Moderator
group is static, which means that in this case, the group is fixed to both types. You can add and remove users from this group, but you can’t add other groups to the type on the fly.
The main difference between this and the chat example is that in this case, every registered user can read everything. It’s even possible to query comments independent of their topics to allow users to search for them.
Read next: Kicking Complexity to the Curb with AWS SAM
6. Image Sharing
In this example, registered users can upload images that are only visible to their customers. While the title or number of uploaded images should be visible to all, only paying customers can actually view the images. Another AWS service, like AWS Lambda or EC2, can read the images and create thumbnails of them.
The GraphQL schema looks like this:
type Image @model {
id: ID!
title: String!
customers: [String]
imageUrl: String!
@auth(
rules: [
{
allow: groups
groupsField: "customers"
operations: [read]
},
{
allow: private
provider: iam
operations: [read]
}
]
)
thumbnailUrl: String
@auth(
rules: [
{ allow: groups, groupsField: "customers" },
{
allow: private
provider: iam
operations: [update]
}
]
)
}
In this example, the group is dynamic. This means that a field in the Image
type holds a list of groups that have access rights.
There’s also a private IAM rule allowing an IAM user/role to read the imageUrl
and update the thumbnailUrl
. This IAM user/role could be a Lambda Function that is triggered when an Image is created. It then generates a thumbnail for that image
and updates the thumbnailUrl
.
Creating Authorization Schemes with GraphQL
Amplify’s auth
directive makes creating authorization schemes for your API easy, while still offering flexibility. It allows public, private, user, group, and IAM-based authorization so that your users and other services can access and modify data.
Still, since everything will eventually be deployed to Cognito, AppSync, and Lambda with CloudFormation templates, you can always opt out of the Amplify stack and do your own thing.
Are you a content specialist in need of expert-based content? Contact us if you need help creating tech blogs like this one.