export const meta = { title: `Changing the default identity claim for owner-based auth`, description: `` }; 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: ```graphql ## 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 } ``` ```graphql ## 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: ```graphql 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](https://docs.amplify.aws/cli/graphql/data-modeling/#configure-a-secondary-index). 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 `::`, 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: ```graphql 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 (https://docs.amplify.aws/cli/graphql/search-and-result-aggregations/#supported-search-operations). Here's an example of using the `match` operation: ```graphql 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 `::`, and the API will return ``. If you would like to use username for the identity claim without storing sub, specify in your schema the following: ```graphql 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: ```graphql 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: ```graphql 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"`. ```graphql 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 `::` 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: ```graphql 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: ```graphql 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.