Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated Oct 25, 2024

Customize authorization rules

You are currently viewing the new GraphQL transformer v2 docs Looking for legacy docs?

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.

type Todo @model @auth(rules: [{ allow: owner }]) {
content: String
}

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.

type Todo
@model
@auth(rules: [{ allow: public, operations: [read] }, { allow: owner }]) {
content: String
}

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.

input AMPLIFY {
globalAuthRule: AuthRule = { allow: public }
}

In the CDK construct, we call this the "sandbox mode" that you need to explicitly enable via an input parameter.

new AmplifyGraphqlApi(this, "MyNewApi", {
...,
translationBehavior: {
sandboxModeEnabled: true
}
});

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.

Currently, only { allow: public } is supported as a global authorization rule.

Authorization strategies

Use the guide below to select the correct authorization strategy for your use case:

Recommended use caseStrategyProvider
Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access.publicapiKey
Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using AWS IAM controls.publiciam (or identityPool when using CDK construct)
Per user data access. Access is restricted to the "owner" of a record. Leverages amplify add auth Cognito user pool by default.owneruserPools / oidc
Any signed-in data access. Unlike owner-based access, any signed-in user has access.privateuserPools / oidc / iam
Per user group data access. A specific or dynamically configured group of users have accessgroupsuserPools / oidc
Define your own custom authorization rule within a Lambda functioncustomfunction

Public data access

To grant everyone access, use the public authorization strategy. Behind the scenes, the API will be protected with an API Key.

type Todo @model @auth(rules: [{ allow: public }]) {
content: String
}

You can also override the authorization provider. In the example below, you can 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.

# public authorization with provider override
type Post @model @auth(rules: [{ allow: public, provider: iam }]) {
id: ID!
title: String!
}

Designate an Amazon Cognito identity pool's role for unauthenticated identities by setting the identityPoolConfig property:

// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well
import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha';
const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', {
allowUnauthenticatedIdentities: true,
authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({
userPool: <your_user_pool>,
userPoolClient: <your_user_pool_client>,
})] },
});
new AmplifyGraphqlApi(this, "MyNewApi", {
definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")),
authorizationModes: {
defaultAuthorizationMode: 'API_KEY',
apiKeyConfig: {
expires: cdk.Duration.days(30)
},
identityPoolConfig: {
identityPoolId: identityPool.identityPoolId,
authenticatedUserRole: identityPool.authenticatedRole,
unauthenticatedUserRole: identityPool.unauthenticatedRole,
}
},
})
# public authorization with provider override
type Post @model @auth(rules: [{ allow: public, provider: identityPool }]) {
id: ID!
title: String!
}

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.

# The "owner" of a Todo is allowed to create, read, update, and delete their own todos
type Todo @model @auth(rules: [{ allow: owner }]) {
content: String
}
# The "owner" of a Todo record is only allowed to create, read, and update it.
# The "owner" of a Todo record is denied to delete it.
type Todo
@model
@auth(rules: [{ allow: owner, operations: [create, read, update] }]) {
content: String
}

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.

Do not set ownerField to your @primaryKey field or id field if no primary key is specified. If you want to query by the ownerField, use an @index on that ownerField to create a secondary index.

type Todo @model @auth(rules: [{ allow: owner, ownerField: "author" }]) {
content: String #^^^^^^^^^^^^^^^^^^^^
author: String # record owner information now stored in "author" field
}

By default, owners can reassign the owner of their existing record to another user.

To prevent an owner from reassigning their record to another user, protect the owner field (by default owner: String) with a field-level authorization rule. For example, in a social media app, you would want to prevent Alice from being able to reassign Alice's Post to Bob.

type Todo @model @auth(rules: [{ allow: owner }]) {
id: ID!
description: String
owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
}

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.

type Todo @model @auth(rules: [{ allow: owner, ownerField: "authors" }]) {
content: String
authors: [String]
}

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.

type Todo @model @auth(rules: [{ allow: private }]) {
content: String
}

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, you can use an "Authenticated Role" from the Cognito identity pool for granting access to signed-in users.

When you run amplify add auth, the Amplify CLI generates scoped down IAM policies for the "Authenticated role" in Cognito identity pool automatically.

# public authorization with provider override
type Post @model @auth(rules: [{ allow: private, provider: iam }]) {
id: ID!
title: String!
}

Designate an Amazon Cognito identity pool role for authenticated identities by setting the identityPoolConfig property:

// Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well
import cognito_identitypool from '@aws-cdk/aws-cognito-identitypool-alpha';
const identityPool = new cognito_identitypool.IdentityPool(stack, 'MyNewIdentityPool', {
allowUnauthenticatedIdentities: true,
authenticationProviders: { userPools: [new cognito_identitypool.UserPoolAuthenticationProvider({
userPool: <your_user_pool>,
userPoolClient: <your_user_pool_client>,
})] },
});
new AmplifyGraphqlApi(this, "MyNewApi", {
definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")),
authorizationModes: {
defaultAuthorizationMode: 'API_KEY',
apiKeyConfig: {
expires: cdk.Duration.days(30)
},
identityPoolConfig: {
identityPoolId: identityPool.identityPoolId,
authenticatedUserRole: identityPool.authenticatedRole,
unauthenticatedUserRole: identityPool.unauthenticatedRole,
}
},
})
# public authorization with provider override
type Post @model @auth(rules: [{ allow: private, provider: identityPool }]) {
id: ID!
title: String!
}

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,

type Todo @model @auth(rules: [{ allow: owner }]) {
id: ID!
name: String!
task: [Task] @hasMany
}
type Task
@model
@auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) {
id: ID!
description: String!
}

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.

type Salary @model @auth(rules: [{ allow: groups, groups: ["Admin"] }]) {
id: ID!
wage: Int
currency: String
}

In the example above, only users that are part of the "Admin" user group are granted access to the Salary model.

Custom authorization rule

You can define your own custom authorization rule with a Lambda function.

type Salary @model @auth(rules: [{ allow: custom }]) {
id: ID!
wage: Int
currency: String
}

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:

amplify update api
? Select a setting to edit:
> Authorization modes
> Lambda
? Choose a Lambda source:
> Create a new Lambda function

To configure a Lambda function as the authorization mode, set the lambdaConfig in the CDK construct. Use the ttl to designate the toke expiry time.

const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', {
definition: AmplifyGraphqlDefinition.fromFiles(
path.join(__dirname, 'schema.graphql')
),
authorizationModes: {
defaultAuthorizationMode: 'AWS_LAMBDA',
lambdaConfig: {
function: new lambda.Function(this, 'MyAuthLambda', {
code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/auth')),
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_18_X
}),
ttl: cdk.Duration.seconds(10)
}
}
});

You can leverage this Lambda function code template as a starting point to author your authorization handler code:

// This is sample code. Please update this to suite your schema
/**
* @type {import('@types/aws-lambda').APIGatewayProxyHandler}
*/
exports.handler = async (event) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
const {
authorizationToken,
requestContext: { apiId, accountId }
} = event;
const response = {
isAuthorized: authorizationToken === 'custom-authorized',
resolverContext: {
// eslint-disable-next-line spellcheck/spell-checker
userid: 'user-id',
info: 'contextual information A',
more_info: 'contextual information B'
},
deniedFields: [
`arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`,
`Mutation.createEvent`
],
ttlOverride: 300
};
console.log(`response >`, JSON.stringify(response, null, 2));
return response;
};

You can use the default Amplify provided template as a starting point for your custom authorization rule. The authorization Lambda function receives:

{
"authorizationToken": "ExampleAuthToken123123123", # Authorization token specified by client
"requestContext": {
"apiId": "aaaaaa123123123example123", # AppSync API ID
"accountId": "111122223333", # AWS Account ID
"requestId": "f4081827-1111-4444-5555-5cf4695f339f",
"queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", # GraphQL query
"operationName": "MyQuery", # GraphQL operation name
"variables": {} # any additional variables supplied to the operation
}
}

Your Lambda authorization function needs to return the following JSON:

{
// required
"isAuthorized": true, // if "false" then an UnauthorizedException is raised, access is denied
"resolverContext": { "banana": "very yellow" }, // JSON object visible as $ctx.identity.resolverContext in VTL resolver templates
// optional
"deniedFields": ["TypeName.FieldName"], // Forces the fields to "null" when returned to the client
"ttlOverride": 10 // The number of seconds that the response should be cached for. Overrides default specified in "amplify update api"
}

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.

type Post
@model
@auth(
rules: [
{ allow: public, operations: [read], provider: iam }
{ allow: owner }
]
) {
title: String
content: String
}
import { createPost } from './graphql/mutations';
import { listPosts } from './graphql/queries';
// Creating a post is restricted to Cognito User Pools
const newPostResult = await client.graphql({
query: queries.createPost,
variables: { input: { title: 'Hello World' } },
authMode: 'userPool'
});
// Listing posts is available to all users (verified by IAM)
const listPostsResult = await client.graphql({
query: queries.listPosts,
authMode: 'iam'
});

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.

type Employee
@model
@auth(rules: [{ allow: private, operations: [read] }, { allow: owner }]) {
name: String
email: String
ssn: String @auth(rules: [{ allow: owner }])
}

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

To prevent sensitive data from being sent over subscriptions, the GraphQL Transformer needs to alter the response of mutations for those fields by setting them to null. Therefore, to facilitate field-level authorization with subscriptions, you need to either apply field-level authorization rules to all required fields, make the other fields nullable, or disable subscriptions by setting it to public or off.

In the example above:

  • any signed in user is allowed to read the list of employees' name and email fields
  • only the employee/owner themselves have CRUD access to their ssn field

To prevent unintended loss of data, the user or role that attempts to delete a record should have delete permissions on every field of the @model annotated GraphQL type. For example, in the schema below:

type Todo
@model
@auth(
rules: [
{ allow: private, provider: iam }
{ allow: groups, groups: ["Admin"] }
]
) {
id: ID!
name: String!
@auth(
rules: [
{ allow: private, provider: iam }
{ allow: groups, groups: ["Admin"] }
]
)
description: String @auth(rules: [{ allow: private, provider: iam }])
}

Since the description field is not accessible by "Admin" Cognito group users, they cannot delete any Todo records.

Advanced

Review and print access control matrix

Verify your API's access control matrix, by running the following command:

amplify status api -acm Blog
iam:public
┌─────────┬────────┬──────┬────────┬────────┐
│ (index) │ create │ read │ update │ delete │
├─────────┼────────┼──────┼────────┼────────┤
│ title │ false │ true │ false │ false │
│ content │ false │ true │ false │ false │
└─────────┴────────┴──────┴────────┴────────┘
userPools:owner:owner
┌─────────┬────────┬──────┬────────┬────────┐
│ (index) │ create │ read │ update │ delete │
├─────────┼────────┼──────┼────────┼────────┤
│ title │ true │ true │ true │ true │
│ content │ true │ true │ true │ true │
└─────────┴────────┴──────┴────────┴────────┘

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:

{
"adminRoleNames": ["<YOUR_IAM_USER_OR_ROLE_NAME>"]
}

To grant any IAM principal (AWS Resource, IAM role, IAM user, etc) access, with the exception of Amazon Cognito identity pool roles, to this GraphQL API in CDK, you need to enable IAM authorization mode via the iamConfig property of the CDK construct.

const userRole = Role.fromRoleName(
this,
'MyUserRole',
'<INSERT YOUR USER ROLE NAME HERE>'
);
const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', {
definition: AmplifyGraphqlDefinition.fromFiles(
path.join(__dirname, 'schema.graphql')
),
authorizationModes: {
defaultAuthorizationMode: 'API_KEY',
apiKeyConfig: {
expires: cdk.Duration.days(30)
},
iamConfig: {
// Set this value to true.
enableIamAuthorizationMode: true
}
}
});

These "Admin Roles" have special access privileges that are scoped based on their IAM policy instead of any particular @auth rule.

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.

Use the oidcConfig property to configure the OpenID Connect provider domain, Client ID, Issued at TTL, and Auth Time TTL.

const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', {
definition: AmplifyGraphqlDefinition.fromFiles(
path.join(__dirname, 'schema.graphql')
),
authorizationModes: {
defaultAuthorizationMode: 'OPENID_CONNECT',
oidcConfig: {
oidcIssuerUrl: '...',
oidcProviderName: '...',
tokenExpiryFromAuth: '...',
tokenExpiryFromIssue: '...',
clientId: '...'
}
}
});
type Todo
@model
@auth(
rules: [
{ allow: owner, provider: oidc, identityClaim: "user_id" }
{ allow: private, provider: oidc }
{ allow: group, provider: oidc, groupClaim: "user_groups" }
]
) {
content: String
}

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:

type Post
@model
@auth(
rules: [
{ allow: owner, identityClaim: "user_id" }
{ allow: groups, groups: ["Moderator"], groupClaim: "user_groups" }
]
) {
id: ID!
owner: String
postname: String
content: String
}

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.

Once you grant a function access to the GraphQL API, it is required to redeploy the API to apply the permissions. To do so, run the command amplify api gql-compile --force before deployment via amplify push.

To grant any IAM principal (AWS Resource, IAM role, IAM user, etc), with the exception of Amazon Cognito identity pool roles, to this GraphQL API in CDK, you need to enable IAM authorization mode on the CDK construct.

const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', {
definition: AmplifyGraphqlDefinition.fromFiles(
path.join(__dirname, 'schema.graphql')
),
authorizationModes: {
defaultAuthorizationMode: 'API_KEY',
apiKeyConfig: {
expires: cdk.Duration.days(30)
},
iamConfig: {
// Must be set to `true`. Then grant your Lambda function's execution role access to the API
enableIamAuthorizationMode: true
}
}
});

These "Admin Roles" have special access privileges that are scoped based on their IAM policy instead of any particular @auth rule.

To grant an external AWS Resource or an IAM role access to this GraphQL API, you need to explicitly list the IAM role's name or the AWS Resource's name by adding it 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 name or AWS Resource name:

{
"adminRoleNames": ["<YOUR_IAM_ROLE_NAME>", "<YOUR_AWS_RESOURCE_NAME>"]
}

You can use the symbol ${env} to reference the current Amplify CLI environment.

These "Admin Roles" have special access privileges that are scoped based on their IAM policy instead of any particular @auth rule.

How it works

Definition of the @auth directive:

# When applied to a type, augments the application with
# owner and group-based authorization rules.
directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION
input AuthRule {
allow: AuthStrategy!
provider: AuthProvider
ownerField: String # defaults to "owner" when using owner auth
identityClaim: String # defaults to "sub::username" when using owner auth
groupClaim: String # defaults to "cognito:groups" when using Group auth
groups: [String] # Required when using Static Group auth
groupsField: String # defaults to "groups" when using Dynamic Group auth
operations: [ModelOperation] # Required for finer control
}
enum AuthStrategy {
owner
groups
private
public
custom
}
enum AuthProvider {
apiKey
iam
oidc
userPools
function
}
enum ModelOperation {
create
update
delete
read # Short-hand to allow "get", "list", "sync", "listen", and "search"
get # Retrieves an individual item
list # Retrieves a list of items
sync # Enables ability to sync offline/online changes (including via DataStore)
listen # Subscribes to real-time changes
search # Enables ability to search using @searchable directive
}

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, and delete operations are allowed.
    • read operation: read operation can be replaced with get, list, sync, listen, and search for a more granular query access

If you use DataStore instead of the API category to connect to your AppSync API, then you must allow listen and sync operations for your data model.

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.