GraphQL Transformer @auth identity claim changes
The Amplify CLI is changing the default owner value in GraphQL APIs in v8.1.0. Previously, the API stored only the username
by default. In this next release behind a feature flag, and in an upcoming major CLI release, the GraphQL Transformer will store a user's unique sub
and username
.
What is changing?
In the Amplify CLI >= v8.0.3 owner-based @auth
rule uses "username"
as the default identity claim from a JSON Web Token from AWS Cognito and stores it under the owner
field in your app's DynamoDB table. This means the owner
field of a record will be queried and populated with a user's username. The Amplify CLI GraphQL Transformer is changing the default identityClaim
to use a combination of sub
- the unique ID (uuid) given by Cognito in your User Pool - and username
from the JWT token with a delimiter of ::
.
What are the breaking changes?
The sort key fields for your @primaryKey
s can no longer be part of an owner-based authorization's ownerField
. This also applies to the auto-generated owner
field when you add owner-based authorization @auth(rules: [{ allow: owner }])
. Here are two primary examples that don't support this:
## NOT supported schema example 1 ##type Item @auth(rules: [{ allow: owner }]) @model { id: ID! @primaryKey(sortKeyFields: ["owner"]) owner: String ## "owner" is an auto-generated field of the owner-based authorization rule}
## NOT supported schema example 2 ##type Item @auth(rules: [{ allow: owner, ownerField: "user" }]) @model { id: ID! @primaryKey(sortKeyFields: ["owner"]) user: String ## "user" is the designated "ownerField" of the owner-based authorization rule}
If your table requires this configuration, it is recommended to set the identityClaim
to username
.
Additionally, if you want to continue making queries with the owner field as the secondary query parameter, consider using the @index
directive instead. Using the mentioned example, you can set up a query as the following:
type Todo @auth(rules: [{ allow: owner, ownerField: "user" }]) { listId: ID! @primaryKey @index(name: "byUser", sortKeyFields: ["user"], queryField: "todoByUser") user: String!}
You will be able to query your Todo
by user
with the following query:
query byUser($user: String!) { todoByUser(user: $user) { items { listId user } nextToken }}
Learn more about configuring the @index
directive here.
There are no other breaking changes to your GraphQL API when using the new default identity claim. The resolvers will store these values in your DynamoDB tables in the format of <sub>::<username>
, and return username
to the client code by default.
While the other directives will work as they are currently expected to function (i.e. @searchable
, @function
), using custom queries to your databases may need to be changed. For example, if you are performing a query with the filter
parameter, you will need to change it from using the filter
query with just 1 argument to using an or
conditional statement like the following:
query MyQuery { listPrivateNotes( filter: { or: [{ owner: { contains: "::user1" } }, { owner: { eq: "user1" } }] } ) { nextToken items { id content } }}
Another example of changing your queries is if you are using OpenSearch for a custom query like the one below, you will need your queries to account for the format of the stored owner field with a matching operation. See supported search operations. Here's an example of using the match
operation:
query SearchAndSort { searchStudents(filter: or: [{ owner: { match: "*::user1" } }, { owner: { eq: "user1" } }], ) { items { name id } }}
How do I migrate my GraphQL API?
Automatic changes the CLI will make for new apps
If you are creating a new Amplify project, your app's GraphQL API will be created using the "sub::username"
identity claim, and no further action is required from you. Your database records will store the uuid and username in the owner
field in the format of <sub>::<username>
, and the API will return <username>
.
If you would like to use username for the identity claim without storing sub, specify in your schema the following:
type Todo @model @auth( rules: [ { allow: owner identityClaim: "username" # explicit use of username } ] ) { id: ID! title: String!}
Manual changes to continue using "username"
for existing apps
If you have an existing schema that uses owner-based @auth
with an implicit identity claim rule on a type or field similar to this:
type Todo @model @auth( rules: [ { allow: owner # no explicit identity claim } ] ) { id: ID! title: String!}
Your GraphQL schema is using the default identity claim "username"
and the Amplify CLI GraphQL Transformer is using the default value to generate your VTL files. Therefore, your schema is read as:
type Todo @model @auth( rules: [ { allow: owner identityClaim: "username" # explicit identity claim } ] ) { id: ID! title: String!}
The Amplify CLI is changing the default to "sub::username"
in v9.0.0, so if your identityClaim
is not explicitly defined, the transformers will use "sub::username"
unless you have set "username"
to your schema as shown above.
In the initial release, this functionality will be behind a feature flag, useSubUsernameForDefaultIdentityClaim
, with false
as the default value. Setting the feature flag to true
enables the transformers to use the new identity claim; however, it is recommended to explicitly state your identity claim moving forward as this feature flag is only temporary (as shown above).
Manual changes to use "sub::username"
for existing apps
If you wish to migrate your VTL files before the changes in v9.0.0, set your identityClaim
to "sub::username"
.
type Todo @model @auth( rules: [ { allow: owner identityClaim: "sub::username" # set your identity claim } ] ) { id: ID! title: String!}
Keep in mind that if you have existing owner records in your database and owner-reliant code in your code base, these changes will not migrate your data. The resolvers are backwards compatible, so your API will still be compatible with the former contract that uses username
for the owner
field. In other words, when using the sub::username
identity claim, your resolvers will authorize both username
and sub
values from the record that match the JWT, but the resolvers will write <sub>::<username>
when no owner field input is specified.
What is the timeline?
v8.1.0 of the Amplify CLI is introducing the feature flag, useSubUsernameForDefaultIdentityClaim
, that will allow developers to opt into the sub::username
default. New Amplify projects created will already be opted into the feature flag, but developers that want to use the new default will have to opt in. For existing Amplify projects, that do not have the feature flag, useSubUsernameForDefaultIdentityClaim
will default to false
.
The Amplify CLI team will release v9.0.0, with the feature flag removed, and developers will need to manually set "username"
to their schemas to maintain their former API contract, or the resolvers will start to store sub::username
in their databases.
Check for invalid owner identity claims
Amplify CLI v10.0.0
is adding stricter checks to ensure that valid owner identity claims exist in the customer's JWT token before storing these values in the owner field. Previously, if the developer-specified owner identity claim does not exist in the customer's JWT token, a default none value was stored in the owner field.
Amplify CLI v10.0.0
and above, if the developer-specified identity claim does not exist in the customer's JWT token, the customer will be not be authorized to access the record.
Since you cannot determine the owner from the previously stored default none value in the owner field, any requests to access these records will not be authorized.
Consider the following schema with custom owner identity claim:
type Todo @model @auth(rules: [{ allow: owner, identityClaim: "userID" }]) { id: ID! title: String!}
The developer-specified owner identity claim i.e userID
should be present in the customer's JWT token for them to be authorized.
Consider the following schema with default owner identity claim:
type Todo @model @auth(rules: [{ allow: owner }]) { id: ID! title: String!}
The claims sub
and username
should be present in the customer's JWT token for them to be authorized.