Customize authorization rules
Use the @auth
directive to configure authorization rules for public, sign-in user, per user, and per user group data access. Authorization rules operate on the deny-by-default principle. Meaning that if an authorization rule is not specifically configured, it is denied.
1type Todo @model @auth(rules: [{ allow: owner }]) {2 content: String3}
In the example above, each signed-in user, or also known as "owner", of a Todo can create, read, update, and delete their own Todos.
Amplify also allows you to restrict the allowed operations, combine multiple authorization rules, and apply fine-grained field-level authorization.
1type Todo2 @model3 @auth(rules: [{ allow: public, operations: [read] }, { allow: owner }]) {4 content: String5}
In the example above, everyone (public
) can read every Todo but owner (authenticated users) can create, read, update, and delete their own Todos.
Global authorization rule (only for getting started)
To help you get started, there's a global authorization rule defined when you create a new GraphQL schema. For production environments, remove the global authorization rule and apply rules on each model instead.
1input AMPLIFY {2 globalAuthRule: AuthRule = { allow: public }3}
The global authorization rule (in this case { allow: public }
- allows anyone to create, read, update, and delete) is applied to every data model in the GraphQL schema.
Note: Amplify will always use the most specific authorization rule that's present. For example, a field-level authorization rule will be used in favor of a model-level authorization rule; similarly, a model-level authorization rule will be used in favor of a global authorization rule.
Authorization strategies
Use the guide below to select the correct authorization strategy for your use case:
Recommended use case | Strategy | Provider |
---|---|---|
Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access. | public | apiKey |
Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using AWS IAM controls. | public | iam |
Per user data access. Access is restricted to the "owner" of a record. Leverages amplify add auth Cognito user pool by default. | owner | userPools / oidc |
Any signed-in data access. Unlike owner-based access, any signed-in user has access. | private | userPools / oidc / iam |
Per user group data access. A specific or dynamically configured group of users have access | groups | userPools / oidc |
Define your own custom authorization rule within a Lambda function | custom | function |
Public data access
To grant everyone access, use the public
authorization strategy. Behind the scenes, the API will be protected with an API Key.
1type Todo @model @auth(rules: [{ allow: public }]) {2 content: String3}
You can also override the authorization provider. In the example below, iam
is specified as the provider which allows you to use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API Key.
When you run amplify add auth
, the Amplify CLI generates scoped down IAM policies for the "Unauthenticated role" in Cognito identity pool automatically.
1# public authorization with provider override2type Post @model @auth(rules: [{ allow: public, provider: iam }]) {3 id: ID!4 title: String!5}
Per-user / owner-based data access
To restrict a record's access to a specific user, use the owner
authorization strategy. When owner
authorization is configured, only the record's owner
is allowed the specified operations.
1# The "owner" of a Todo is allowed to create, read, update, and delete their own todos2type Todo @model @auth(rules: [{ allow: owner }]) {3 content: String4}5
6# The "owner" of a Todo record is only allowed to create, read, and update it.7# The "owner" of a Todo record is denied to delete it.8type Todo9 @model10 @auth(rules: [{ allow: owner, operations: [create, read, update] }]) {11 content: String12}
Behind the scenes, Amplify will automatically add a owner: String
field to each record which contains the record owner's identity information upon record creation.
By default, the Cognito user pool's user information is populated into the owner
field. The value saved includes sub
and username
in the format <sub>::<username>
. The API will authorize against the full value of <sub>::<username>
or sub
/ username
separately and return username
. You can alternatively configure OpenID Connect as an authorization provider.
You can override the owner
field to your own preferred field, by specifying a custom ownerField
in the authorization rule.
1type Todo @model @auth(rules: [{ allow: owner, ownerField: "author" }]) {2 content: String #^^^^^^^^^^^^^^^^^^^^3 author: String # record owner information now stored in "author" field4}
Multi-user data access
If you want to grant a set of users access to a record, you can override the ownerField
to a list of owners. Use this if you want a dynamic set of users to have access to a record.
1type Todo @model @auth(rules: [{ allow: owner, ownerField: "authors" }]) {2 content: String3 authors: [String]4}
In the example above, upon record creation, the authors
list is populated with the creator of the record. The creator can then update the authors
field with additional users. Any user listed in the authors
field can access the record.
Signed-in user data access
To restrict a record's access to every signed-in user, use the private
authorization strategy.
If you want to restrict a record's access to a specific user, see Per-user / owner-based data access.
private
authorization applies the authorization rule to every signed-in user access.
1type Todo @model @auth(rules: [{ allow: private }]) {2 content: String3}
In the example above, anyone with a valid JWT token from Cognito user pool are allowed to access all Todos.
You can also override the authorization provider. In the example below, iam
is specified as the provider which allows you to use an "Authenticated Role" from the Cognito identity pool for public access instead of an API Key.
When you run amplify add auth
, the Amplify CLI generates scoped down IAM policies for the "Authenticated role" in Cognito identity pool automatically.
1type Todo @model @auth(rules: [{ allow: private, provider: iam }]) {2 content: String3}
In addition, you can also use OpenID Connect with private
authorization. See OpenID Connect as an authorization provider.
Note: If you have a connected child model that allows private
level access, any user authorized to fetch it from the parent model will be able to read the connected child model. For example,
1type Todo @model @auth(rules: [{ allow: owner }]) {2 id: ID!3 name: String!4 task: [Task] @hasMany5}6
7type Task8 @model9 @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) {10 id: ID!11 description: String!12}
In the above relationship, the owner of a Todo
record can query all the tasks connected to it, since the Task
model allows private
read access.
User group-based data access
To restrict access based on user groups, use the group
authorization strategy.
Static group authorization: When you want to restrict access to a specific set of user groups, provide the group names in the groups
parameter.
1type Salary @model @auth(rules: [{ allow: groups, groups: ["Admin"] }]) {2 id: ID!3 wage: Int4 currency: String5}
In the example above, only users that are part of the "Admin" user group are granted access to the Salary model.
Dynamic group authorization: When you want to restrict access to a set of user groups.
1# Dynamic group authorization with multiple groups2type Post @model @auth(rules: [{ allow: groups, groupsField: "groups" }]) {3 id: ID!4 title: String5 groups: [String]6}7
8# Dynamic group authorization with a single group9type Post @model @auth(rules: [{ allow: groups, groupsField: "group" }]) {10 id: ID!11 title: String12 group: String13}
With dynamic group authorization, each record contains an attribute specifying what Cognito groups should be able to access it. Use the groupsField
argument to specify which attribute in the underlying data store holds this group information. To specify that a single group should have access, use a field of type String
. To specify that multiple groups should have access, use a field of type [String]
.
By default, group
authorization leverages Amazon Cognito user pool groups but you can also use OpenID Connect with group
authorization. See OpenID Connect as an authorization provider.
Known limitations for real-time subscriptions when using dynamic group authorization:
- If you authorize based on a single group per record, then subscriptions are only supported if the user is part of 5 or fewer user groups
- If you authorize via an array of groups (
groups: [String]
example above),
- subscriptions are only supported if the user is part of 20 or fewer groups
- you can only authorize 20 or fewer user groups per record
Custom authorization rule
You can define your own custom authorization rule with a Lambda function.
1type Salary @model @auth(rules: [{ allow: custom }]) {2 id: ID!3 wage: Int4 currency: String5}
The Lambda function of choice will receive an authorization token from the client and execute the desired authorization logic. The AppSync GraphQL API will receive a payload from Lambda after invocation to allow or deny the API call accordingly.
Configure the GraphQL API with the Lambda authorization mode, run the following command in your Terminal:
1amplify update api
1? Select a setting to edit:2> Authorization modes3
4> Lambda5
6? Choose a Lambda source:7> Create a new Lambda function
You can use the default Amplify provided template as a starting point for your custom authorization rule. The authorization Lambda function receives:
1{2 "authorizationToken": "ExampleAuthToken123123123", # Authorization token specified by client3 "requestContext": {4 "apiId": "aaaaaa123123123example123", # AppSync API ID5 "accountId": "111122223333", # AWS Account ID6 "requestId": "f4081827-1111-4444-5555-5cf4695f339f",7 "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", # GraphQL query8 "operationName": "MyQuery", # GraphQL operation name9 "variables": {} # any additional variables supplied to the operation10 }11}
Your Lambda authorization function needs to return the following JSON:
1{2 // required3 "isAuthorized": true, // if "false" then an UnauthorizedException is raised, access is denied4 "resolverContext": { "banana": "very yellow" }, // JSON object visible as $ctx.identity.resolverContext in VTL resolver templates5
6 // optional7 "deniedFields": ["TypeName.FieldName"], // Forces the fields to "null" when returned to the client8 "ttlOverride": 10 // The number of seconds that the response should be cached for. Overrides default specified in "amplify update api"9}
Review the Amplify Library documentation to set the custom authorization token for GraphQL API and DataStore.
Configure multiple authorization rules
When combining multiple authorization rules, they are "logically OR"-ed.
1type Post2 @model3 @auth(4 rules: [5 { allow: public, operations: [read], provider: iam }6 { allow: owner }7 ]8 ) {9 title: String10 content: String11}
1import { createPost } from './graphql/mutations';2import { listPosts } from './graphql/queries';3
4// Creating a post is restricted to Cognito User Pools5const newPostResult = await client.graphql({6 query: queries.createPost,7 variables: { input: { title: 'Hello World' } },8 authMode: 'userPool'9});10
11// Listing posts is available to all users (verified by IAM)12const listPostsResult = await client.graphql({13 query: queries.listPosts,14 authMode: 'iam'15});
In the example above:
- any user (signed in or not, verified by IAM) is allowed to read all posts
- owners are allowed to create, read, update, and delete their own posts.
If you are using DataStore and have multiple authorization rules, you can let DataStore automatically determine the best authorization mode client-side. Review how to Configure Multiple Authorization Types on DataStore for more details.
Field-level authorization rules
When an authorization rule is added to a field, it'll strictly define the authorization rules applied on the field. Field-level authorization rules do not inherit model-level authorization rules. Meaning, only the specified field-level authorization rule is applied.
1type Employee2 @model3 @auth(rules: [{ allow: private, operations: [read] }, { allow: owner }]) {4 name: String5 email: String6 ssn: String @auth(rules: [{ allow: owner }])7}
In the example above:
- Owners are allowed to create, read, update, and delete Employee records they own
- Any signed in user has read access
- Any signed in user can read data with the exception of the
ssn
field. This field only has owner auth applied, the field-level auth rule means that model-level auth rules are not applied
In the example above:
- any signed in user is allowed to read the list of employees'
name
andemail
fields - only the employee/owner themselves have CRUD access to their
ssn
field
Advanced
Review and print access control matrix
Verify your API's access control matrix, by running the following command:
1amplify status api -acm Blog
1iam:public2 ┌─────────┬────────┬──────┬────────┬────────┐3 │ (index) │ create │ read │ update │ delete │4 ├─────────┼────────┼──────┼────────┼────────┤5 │ title │ false │ true │ false │ false │6 │ content │ false │ true │ false │ false │7 └─────────┴────────┴──────┴────────┴────────┘8userPools:owner:owner9 ┌─────────┬────────┬──────┬────────┬────────┐10 │ (index) │ create │ read │ update │ delete │11 ├─────────┼────────┼──────┼────────┼────────┤12 │ title │ true │ true │ true │ true │13 │ content │ true │ true │ true │ true │14 └─────────┴────────┴──────┴────────┴────────┘
Use IAM authorization within the AppSync console
IAM-based @auth
rules are scoped down to only work with Amplify-generated IAM roles. To access the GraphQL API with IAM authorization within your AppSync console, you need to explicitly allow list the IAM user's name.
Add the allow-listed IAM users by adding them to amplify/backend/api/<your-api-name>/custom-roles.json
. (Create the custom-roles.json
file if it doesn't exist). Append the adminRoleNames
array with the IAM role or user names:
1{2 "adminRoleNames": ["<YOUR_IAM_USER_OR_ROLE_NAME>"]3}
These "Admin Roles" have special access privileges that are scoped based on their IAM policy instead of any particular @auth
rule.
Using OIDC authorization provider
private
, owner
, and group
authorization can be configured with an OpenID Connect (OIDC) authorization mode. Add provider: oidc
to the authorization rule.
Upon the next amplify push
, Amplify CLI prompts you for the OpenID Connect provider domain, Client ID, Issued at TTL, and Auth Time TTL.
1type Todo2 @model3 @auth(4 rules: [5 { allow: owner, provider: oidc, identityClaim: "user_id" }6 { allow: private, provider: oidc }7 { allow: group, provider: oidc, groupClaim: "user_groups" }8 ]9 ) {10 content: String11}
The example above highlights the supported authorization strategies with oidc
authorization provider. For owner
and group
authorization, you also need to specify a custom identity and group claim.
Configure custom identity and group claims
@auth
supports using custom claims if you do not wish to use the default Amazon Cognito-provided "cognito:groups" or the double-colon-delimited claims, "sub::username", from your JWT token. This can be helpful if you are using tokens from a 3rd party OIDC system or if you wish to populate a claim with a list of groups from an external system, such as when using a Pre Token Generation Lambda Trigger which reads from a database. To use custom claims specify identityClaim
or groupClaim
as appropriate like in the example below:
1type Post2 @model3 @auth(4 rules: [5 { allow: owner, identityClaim: "user_id" }6 { allow: groups, groups: ["Moderator"], groupClaim: "user_groups" }7 ]8 ) {9 id: ID!10 owner: String11 postname: String12 content: String13}
In this example the record owner will check against a user_id
claim. Similarly, if the user_groups
claim contains a "Moderator" string then access will be granted.
Grant Lambda function access to GraphQL API
Lambda functions' IAM execution role do not immediately grant access to Amplify's GraphQL API because the API operates on a "deny-by-default"-basis. Access need to be explicitly granted. Depending on how your function is deployed, the workflow slightly differ
If you grant a Lambda function in your Amplify project access to the GraphQL API via amplify update function
, then the Lambda function's IAM execution role is allow-listed to honor the permissions granted on the Query
, Mutation
, and Subscription
types.
Therefore, these functions have special access privileges that are scoped based on their IAM policy instead of any particular @auth
rule.
Refer to the sample code to learn how to sign the request to call the GraphQL API using IAM authorization.
How it works
Definition of the @auth
directive:
1# When applied to a type, augments the application with2# owner and group-based authorization rules.3directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION4input AuthRule {5 allow: AuthStrategy!6 provider: AuthProvider7 ownerField: String # defaults to "owner" when using owner auth8 identityClaim: String # defaults to "sub::username" when using owner auth9 groupClaim: String # defaults to "cognito:groups" when using Group auth10 groups: [String] # Required when using Static Group auth11 groupsField: String # defaults to "groups" when using Dynamic Group auth12 operations: [ModelOperation] # Required for finer control13}14
15enum AuthStrategy {16 owner17 groups18 private19 public20 custom21}22enum AuthProvider {23 apiKey24 iam25 oidc26 userPools27 function28}29enum ModelOperation {30 create31 update32 delete33 read # Short-hand to allow "get", "list", "sync", "listen", and "search"34 get # Retrieves an individual item35 list # Retrieves a list of items36 sync # Enables ability to sync offline/online changes (including via DataStore)37 listen # Subscribes to real-time changes38 search # Enables ability to search using @searchable directive39}
Authorization rules consists of:
- authorization strategy (
allow
): who the authorization rule applies to - authorization provider (
provider
): which mechanism is used to apply the authorization rule (API Key, IAM, Amazon Cognito user pool, OIDC) - authorized operations (
operations
): which operations are allowed for the given strategy and provider. If not specified,create
,read
,update
, anddelete
operations are allowed.read
operation:read
operation can be replaced withget
,list
,sync
,listen
, andsearch
for a more granular query access
API Keys are best used for public APIs (or parts of your schema which you wish to be public) or prototyping, and you must specify the expiration time before deploying. IAM authorization uses Signature Version 4 to make request with policies attached to Roles. OIDC tokens provided by Amazon Cognito user pool or 3rd party OpenID Connect providers can also be used for authorization, and enabling this provides a simple access control requiring users to authenticate to be granted top level access to API actions.