diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 8a524bbf593..8e83e9f5d5d 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -1599,6 +1599,9 @@ export const directory = { }, { path: 'src/pages/gen1/[platform]/tools/cli/migration/identity-claim-changes/index.mdx' + }, + { + path: 'src/pages/gen1/[platform]/tools/cli/migration/iam-auth-updates-for-cdk-construct/index.mdx' } ] }, diff --git a/src/fragments/lib/auth/android/social_signin_web_ui/20_signin.mdx b/src/fragments/lib/auth/android/social_signin_web_ui/20_signin.mdx index 4541aa888b1..307c0aaf76b 100644 --- a/src/fragments/lib/auth/android/social_signin_web_ui/20_signin.mdx +++ b/src/fragments/lib/auth/android/social_signin_web_ui/20_signin.mdx @@ -4,8 +4,9 @@ For now, just add this method to the `onCreate` method of MainActivity with what ```java +// Replace facebook with your chosen auth provider such as google, amazon, or apple Amplify.Auth.signInWithSocialWebUI( - AuthProvider.facebook(), + AuthProvider.facebook(), this, result -> Log.i("AuthQuickstart", result.toString()), error -> Log.e("AuthQuickstart", error.toString()) @@ -16,8 +17,9 @@ Amplify.Auth.signInWithSocialWebUI( ```kotlin +// Replace facebook with your chosen auth provider such as google, amazon, or apple Amplify.Auth.signInWithSocialWebUI( - AuthProvider.facebook(), + AuthProvider.facebook(), this, { Log.i("AuthQuickstart", "Sign in OK: $it") }, { Log.e("AuthQuickstart", "Sign in failed", it) } @@ -29,6 +31,7 @@ Amplify.Auth.signInWithSocialWebUI( ```kotlin try { + // Replace facebook with your chosen auth provider such as google, amazon, or apple val result = Amplify.Auth.signInWithSocialWebUI(AuthProvider.facebook(), this) Log.i("AuthQuickstart", "Sign in OK: $result") } catch (error: AuthException) { @@ -40,6 +43,7 @@ try { ```java +// Replace facebook with your chosen auth provider such as google, amazon, or apple RxAmplify.Auth.signInWithSocialWebUI(AuthProvider.facebook(), this) .subscribe( result -> Log.i("AuthQuickstart", result.toString()), diff --git a/src/fragments/lib/storage/android/upload.mdx b/src/fragments/lib/storage/android/upload.mdx index c1783d213ee..bbf16c8c9fa 100644 --- a/src/fragments/lib/storage/android/upload.mdx +++ b/src/fragments/lib/storage/android/upload.mdx @@ -10,7 +10,7 @@ To upload data to S3 from an `InputStream`: private void uploadInputStream() { try { InputStream exampleInputStream = getContentResolver().openInputStream(uri); - + Amplify.Storage.uploadInputStream( "ExampleKey", exampleInputStream, @@ -61,10 +61,10 @@ private suspend fun uploadInputStream(uri: Uri) { private void uploadInputStream() { try { InputStream exampleInputStream = getContentResolver().openInputStream(uri); - + RxProgressAwareSingleOperation rxUploadOperation = RxAmplify.Storage.uploadInputStream("ExampleKey", exampleInputStream); - + rxUploadOperation .observeResult() .subscribe( @@ -81,7 +81,7 @@ private void uploadInputStream() { ## Upload files -To upload to S3 from a data object, specify the key and the file to be uploaded. +To upload to S3 from a data object, specify the key and the file to be uploaded. @@ -257,6 +257,134 @@ upload +## Transfer with Object Metadata + +To upload a file accompanied by metadata, utilize the `StorageUploadFileOptions` builder. Start by creating a hashMap object, then incorporate it into the `StorageUploadFileOptions` during the build process before passing it along to the upload function. + + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "ExampleKey"); + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + // Create metadata + Map userMetadata = new HashMap<>(); + userMetadata.put("myKey", "myVal"); + + // Configure upload options with metadata + StorageUploadFileOptions options = StorageUploadFileOptions.builder() + .metadata(userMetadata) + .build(); + + // Perform the upload + Amplify.Storage.uploadFile( + "ExampleKey", + exampleFile, + options, + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getKey()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); +} +``` + + +```kotlin +fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "ExampleFileName") + exampleFile.writeText("Example file contents") + + // Create metadata + val userMetadata: MutableMap = HashMap() + userMetadata["myKey"] = "myVal" + + // Configure upload options with metadata + val options = StorageUploadFileOptions.builder() + .metadata(userMetadata) + .build() + + // Perform the upload + Amplify.Storage.uploadFile( + "ExampleKey", + exampleFile, + options, + { result -> Log.i("MyAmplifyApp", "Successfully uploaded: ${result.key}") }, + { error -> Log.e("MyAmplifyApp", "Upload failed", error) } + ) +} +``` + + +```kotlin +fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "ExampleFileName") + exampleFile.writeText("Example file contents") + + // Create metadata + val userMetadata: MutableMap = HashMap() + userMetadata["myKey"] = "myVal" + + // Configure upload options with metadata + val options = StorageUploadFileOptions.builder() + .metadata(userMetadata) + .build() + + val upload = Amplify.Storage.uploadFile("ExampleKey", exampleFile, options) + val progressJob = activityScope.async { + upload.progress().collect { + Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") + } + } + try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.key}") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed", error) + } + progressJob.cancel() +} +``` + + +```Java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "ExampleKey"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + Map userMetadata = new HashMap<>(); + userMetadata.put("myKey", "myVal"); + + StorageUploadFileOptions options = StorageUploadFileOptions.builder() + .metadata(userMetadata) + .build(); + + RxStorageBinding.RxProgressAwareSingleOperation rxUploadOperation = + RxAmplify.Storage.uploadFile("ExampleKey", exampleFile, options); + + rxUploadOperation + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getKey()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); +} + ``` + + + + ## MultiPart upload Amplify will automatically perform a S3 multipart upload for objects that are larger than 5MB. For more information about S3's multipart upload, see [Uploading and copying objects using multipart upload](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) diff --git a/src/pages/[platform]/build-a-backend/data/connect-to-API/index.mdx b/src/pages/[platform]/build-a-backend/data/connect-to-API/index.mdx index 7ae2ee7465b..ec1f18ad6e8 100644 --- a/src/pages/[platform]/build-a-backend/data/connect-to-API/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/connect-to-API/index.mdx @@ -111,7 +111,7 @@ const client = generateClient({ ``` - + ```ts import { generateClient } from 'aws-amplify/data'; @@ -179,7 +179,7 @@ const { data: todos, errors } = await client.models.Todo.list({ ``` - + ```ts const { data: todos, errors } = await client.models.Todo.list({ diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/configure-custom-identity-and-group-claim/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/configure-custom-identity-and-group-claim/index.mdx index ebc73ec5f8f..b7c5a16a72b 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/configure-custom-identity-and-group-claim/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/configure-custom-identity-and-group-claim/index.mdx @@ -32,7 +32,7 @@ Amplify Data supports using custom identity and group claims if you do not wish To use custom claims specify `identityClaim` or `groupClaim` as appropriate. In the example below, the `identityClaim` is specified and the record owner will check against this `user_id` claim. Similarly, if the `user_groups` claim contains a "Moderator" string then access will be granted. -```ts +```ts title="amplify/data/resource.ts" import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; const schema = a.schema({ @@ -54,3 +54,24 @@ export type Schema = ClientSchema; export const data = defineData({ schema }); ``` + +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + postname: 'My New Post' + content: 'My post content', + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/custom-data-access-patterns/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/custom-data-access-patterns/index.mdx index 384ec24a6b8..2722fbc0846 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/custom-data-access-patterns/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/custom-data-access-patterns/index.mdx @@ -30,7 +30,7 @@ export function getStaticProps(context) { You can define your own custom authorization rule with a Lambda function. -```ts +```ts title="amplify/data/resource.ts" import { type ClientSchema, a, @@ -68,6 +68,26 @@ export const data = defineData({ }); ``` +In your application, you can perform CRUD operations against the model using `client.models.` with the `lambda` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'lambda', + } + // highlight-end +); +``` + 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. To configure a Lambda function as the authorization mode, create a new file `amplify/data/custom-authorizer.ts`. You can use this Lambda function code template as a starting point for your authorization handler code: diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/index.mdx index 7166c4474be..aace23f453f 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/index.mdx @@ -57,9 +57,9 @@ Use the guide below to select the correct authorization strategy for your use ca | **Recommended use case** | **Strategy** | **Provider** | |---|---|---| | [Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access.](/[platform]/build-a-backend/data/customize-authz/public-data-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.](/[platform]/build-a-backend/data/customize-authz/public-data-access/#add-public-authorization-rule-using-iam-authentication) | `public` | `iam` | +| [Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using Amazon Cognito identity pool's role for unauthenticated identities.]( /[platform]/build-a-backend/data/customize-authz/public-data-access/#add-public-authorization-rule-using-iam-authentication) | `public` | `identityPool` | | [Per user data access. Access is restricted to the "owner" of a record. Leverages `amplify/auth/resource.ts` Cognito user pool by default.](/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access) | `owner` | `userPools` / `oidc` | -| [Any signed-in data access. Unlike owner-based access, **any** signed-in user has access.](/[platform]/build-a-backend/data/customize-authz/signed-in-user-data-access) | `private` | `userPools` / `oidc` / `iam` | +| [Any signed-in data access. Unlike owner-based access, **any** signed-in user has access.](/[platform]/build-a-backend/data/customize-authz/signed-in-user-data-access) | `private` | `userPools` / `oidc` / `identityPool` | | [Per user group data access. A specific or dynamically configured group of users has access.](/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access) | `group` | `userPools` / `oidc` | | [Define your own custom authorization rule within a serverless function.](/[platform]/build-a-backend/data/customize-authz/custom-data-access-patterns) | `custom` | `function` | @@ -71,8 +71,6 @@ Amplify will always use the most specific authorization rule that is available. If there are multiple authorization rules present, they will be logically OR'ed. Review [Configure multiple authorization rules](#configure-multiple-authorization-rules) to learn more. -Finally, there are special "Admin IAM Roles" that allow you to overrule any authorization logic applied on the API and determine the API access completely via its IAM policy. - ### Global authorization rule (only for getting started) To help you get started, you can define an authorization rule on the data schema that will be applied to all data models that **do not** have a model-level authorization rule. Instead of having a global authorization rule for all production environments, we recommend creating specific authorization rules for each model or field. @@ -149,7 +147,7 @@ const schema = a.schema({ ### Configure multiple authorization rules When combining multiple authorization rules, they are "logically OR"-ed. In the following example: -- Any user (signed in or not, verified by IAM) is allowed to read all posts +- Any user (using Amazon Cognito identity pool's unauthenticated roles) is allowed to read all posts - Owners are allowed to create, read, update, and delete their own posts ```ts @@ -158,7 +156,7 @@ const schema = a.schema({ title: a.string(), content: a.string() }).authorization([ - a.allow.public("iam").to(["read"]), + a.allow.public("identityPool").to(["read"]), a.allow.owner() ]) }) @@ -179,7 +177,7 @@ const { data: newPostResult , errors } = await client.models.Post.create({ authMode: 'userPool', }); -// Listing posts is available to all users (verified by IAM) +// Listing posts is available to unauthenticated users (verified by Amazon Cognito identity pool's unauthenticated role) const { data: listPostsResult , errors } = await client.models.Post.list({ query: queries.listPosts, authMode: 'iam', diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/multi-user-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/multi-user-data-access/index.mdx index 0a690bdac89..67ec3d8e3b6 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/multi-user-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/multi-user-data-access/index.mdx @@ -35,7 +35,7 @@ The `multipleOwners` rule grants a set of users access to a record by automatica If you want to grant a set of users access to a record, you use the `multipleOwners` rule. This automatically creates a `owner: a.string().array()` field to store the allowed owners. -```ts +```ts title="amplify/data/resource.ts" const schema = a.schema({ Todo: a .model({ @@ -45,6 +45,41 @@ const schema = a.schema({ }); ``` +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +// Create a record with current user as first owner +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` +```ts +// Add another user as an owner +await client.models.Todo.update( + { + id: newTodo.id, + owner: [...(newTodo.owner as string[]), otherUserId], + }, + // highlight-start + { + authMode: "userPool" + } + // highlight-end +); +``` + ## Override to a list of owners You can override the `inField` to a list of owners. Use this if you want a dynamic set of users to have access to a record. In the example below, the `authors` list is populated with the creator of the record upon record creation. The creator can then update the `authors` field with additional users. Any user listed in the `authors` field can access the record. diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access/index.mdx index 9f78802097d..3942e0c53f5 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access/index.mdx @@ -35,7 +35,8 @@ The `owner` authorization strategy restricts operations on a record to only the You can use the `owner` authorization strategy to restrict a record's access to a specific user. When `owner` authorization is configured, only the record's `owner` is allowed the specified operations. -```ts + +```ts title="amplify/data/resource.ts" // The "owner" of a Todo is allowed to create, read, update, and delete their own todos const schema = a.schema({ Todo: a @@ -46,7 +47,7 @@ const schema = a.schema({ }); ``` -```ts +```ts title="amplify/data/resource.ts" // 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. const schema = a.schema({ @@ -58,6 +59,26 @@ const schema = a.schema({ }); ``` +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` + Behind the scenes, Amplify will automatically add a `owner: a.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 `::`. The API will authorize against the full value of `::` or `sub` / `username` separately and return `username`. You can alternatively configure [OpenID Connect as an authorization provider](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider). diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx index 6ad0a363634..7f95293923f 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx @@ -34,7 +34,7 @@ The public authorization strategy grants everyone access to the API, which is pr To grant everyone access, use the `.public()` authorization strategy. Behind the scenes, the API will be protected with an API key. -```ts +```ts title="amplify/data/resource.ts" const schema = a.schema({ Todo: a .model({ @@ -44,16 +44,81 @@ const schema = a.schema({ }); ``` -## Add public authorization rule using IAM authentication - -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. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. +In your application, you can perform CRUD operations against the model using `client.models.` by specifying the `apiKey` auth mode. ```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'apiKey', + } + // highlight-end +); +``` + +## Add public authorization rule using Amazon Cognito identity pool's unauthenticated role + +You can also override the authorization provider. In the example below, `identityPool` 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. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. + +```ts title="amplify/data/resource.ts" const schema = a.schema({ Todo: a .model({ content: a.string(), }) - .authorization([a.allow.public('iam')]), + .authorization([a.allow.public('identityPool')]), }); ``` + +In your application, you can perform CRUD operations against the model using `client.models.` with the `iam` auth mode. + + +If you're not using the auto-generated **amplifyconfiguration.json** file, then you must set the Amplify Library resource configuration's `allowGuestAccess` flag to `true`. This lets the Amplify Library use the unauthenticated role from your Cognito identity pool when your user isn't logged in. + + +```ts title="src/App.tsx" +import { Amplify } from "aws-amplify"; +import config from "../amplifyconfiguration.json"; + +Amplify.configure( + { + ...config, + Auth: { + Cognito: { + identityPoolId: config.aws_cognito_identity_pool_id, + userPoolClientId: config.aws_user_pools_web_client_id, + userPoolId: config.aws_user_pools_id, + allowGuestAccess: true, + }, + }, + } +); +``` + + + +```ts title="src/App.tsx" +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'iam', + } + // highlight-end +); +``` diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/signed-in-user-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/signed-in-user-data-access/index.mdx index 40f49e862f2..ef01b0f9a79 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/signed-in-user-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/signed-in-user-data-access/index.mdx @@ -40,7 +40,7 @@ You can use the `private` authorization strategy to restrict a record's access t In the example below, anyone with a valid JWT token from the Cognito user pool is allowed access to all Todos. -```ts +```ts title="amplify/data/resource.ts" const schema = a.schema({ Todo: a .model({ @@ -50,18 +50,63 @@ const schema = a.schema({ }); ``` -## Override the authentication provider - -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. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. ```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` + +## Use identity pool for signed-in user authentication + +You can also override the authorization provider. In the example below, `identityPool` 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. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. + + +```ts title="amplify/data/resource.ts" const schema = a.schema({ Todo: a .model({ content: a.string(), }) - .authorization([a.allow.private('iam')]), + .authorization([a.allow.private('identityPool')]), }); ``` +In your application, you can perform CRUD operations against the model using `client.models.` with the `iam` auth mode. + + + The user must be logged in for the Amplify Library to use the authenticated role from your Cognito identity pool. + + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'iam', + } + // highlight-end +); +``` + In addition, you can also use OpenID Connect with `private` authorization. See [OpenID Connect as an authorization provider](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider/). diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx index c19019e0366..9241f16729b 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx @@ -34,7 +34,7 @@ You can use the `group` authorization strategy to restrict access based on user When you want to restrict access to a specific set of user groups, provide the group names in the `groups` parameter. In the example below, only users that are part of the "Admin" user group are granted access to the Salary model. -```ts +```ts title="amplify/data/resource.ts" // allow one specific group const schema = a.schema({ Salary: a @@ -46,6 +46,28 @@ const schema = a.schema({ }); ``` +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +// As a signed-in user that belongs to the 'Admin' User Pool Group +const { errors, data: newSalary } = await client.models.Salary.create( + { + wage: 50.25, + currency: 'USD' + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` + This can then be updated to allow access to multiple defined groups; in this example below we added access for "Leadership". ```ts diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider/index.mdx index afe24ca38ca..4f32e5e2ef6 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider/index.mdx @@ -66,3 +66,18 @@ export const data = defineData({ }, }); ``` + +In your application, you can perform CRUD operations against the model using `client.models.` by specifying the `oidc` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: todos } = await client.models.Todo.list({ + // highlight-start + authMode: "oidc", + // highlight-end +}); +``` diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx index e32e4194071..86001fa817a 100644 --- a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx @@ -31,44 +31,41 @@ export function getStaticProps(context) { You can optimize your list queries based on "secondary indexes". For example, if you have a **Customer** model, you can query based on the customer's **id** identifier field by default but you can add a secondary index based on the **accountRepresentativeId** to get list customers for a given account representative. -A secondary index consists of a "hash key" and, optionally, a "sort key". Use the "hash key" to perform strict equality and the "sort key" for greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), equals (eq), begins with, and between operations. +A secondary index consists of a "hash key" and, optionally, a "sort key". Use the "hash key" to perform strict equality and the "sort key" for greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), equals (eq), begins with, and between operations. -```ts +```ts title="amplify/data/resource.ts" export const schema = a.schema({ - Customer: a.model({ - name: a.string(), - phoneNumber: a.phone(), - accountRepresentativeId: a.id().required() - // highlight-start - }).secondaryIndexes(index => [ - a.index('accountRepresentativeId') - ]) - // highlight-end - .authorization([a.allow.public()]), + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + // highlight-next-line + .secondaryIndexes((index) => [index("accountRepresentativeId")]) + .authorization([a.allow.public()]), }); ``` The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: -```ts +```ts title="src/App.tsx" import { type Schema } from '../amplify/data/resource'; import { generateClient } from 'aws-amplify/data'; const client = generateClient(); -const { - data, - errors +const { data, errors } = // highlight-start -} = await client.models.Customer.listByAccountRepresentativeId({ - accountRepresentativeId: "YOUR_REP_ID" -}); - // highlight-end + await client.models.Customer.listByAccountRepresentativeId({ + accountRepresentativeId: "YOUR_REP_ID", + }); +// highlight-end ``` -Amplify uses Amazon DynamoDB tables as the default data source for `a.model()`. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `.index()` modifier to configure a secondary index. +Amplify uses Amazon DynamoDB tables as the default data source for `a.model()`. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `.index()` modifier to configure a secondary index. **Amazon DynamoDB** is a key-value and document database that delivers single-digit millisecond performance at any scale but making it work for your access patterns requires a bit of forethought. DynamoDB query operations may use at most two attributes to efficiently query data. The first query argument passed to a query (the hash key) must use strict equality and the second attribute (the sort key) may use gt, ge, lt, le, eq, beginsWith, and between. DynamoDB can effectively implement a wide variety of access patterns that are powerful enough for the majority of applications. @@ -76,35 +73,39 @@ Amplify uses Amazon DynamoDB tables as the default data source for `a.model()`. ## Add sort keys to secondary indexes -You can define "sort keys" to add a set of flexible filters to your query, such as "greater than" (gt), "greater than or equal to" (ge), "less than" (lt), "less than or equal to" (le), "equals" (eq), "begins with" (beginsWith), and "between" operations. +You can define "sort keys" to add a set of flexible filters to your query, such as "greater than" (gt), "greater than or equal to" (ge), "less than" (lt), "less than or equal to" (le), "equals" (eq), "begins with" (beginsWith), and "between" operations. ```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Customer: a.model({ - name: a.string(), - phoneNumber: a.phone().required(), - accountRepresentativeId: a.id().required(), - }).secondaryIndexes(index => [ - a.index('accountRepresentativeId') - // highlight-next-line - .sortKeys(["name"]), - ]) -}).authorization([a.allow.owner()]); +export const schema = a.schema({ + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + .secondaryIndexes((index) => [ + index("accountRepresentativeId") + // highlight-next-line + .sortKeys(["name"]), + ]) + .authorization([a.allow.owner()]), +}); ``` On the client side, you should find a new `listBy...` query that's named after hash key and sort keys. For example, in this case: `listByAccountRepresentativeIdAndName`. You can supply the filter as part of this new list query: -```ts +```ts title="src/App.tsx" const { data, errors - // highlight-next-line -} = await client.models.Customer.listByAccountRepresentativeIdAndName({ - accountRepresentativeId: 'YOUR_REP_ID', - name: { - beginsWith: 'Rene', - }, -}); +} = +// highlight-next-line + await client.models.Customer.listByAccountRepresentativeIdAndName({ + accountRepresentativeId: 'YOUR_REP_ID', + name: { + beginsWith: 'Rene', + }, + }); ``` ## Customize the query field for secondary indexes @@ -113,21 +114,24 @@ You can also customize the auto-generated query name under `client.models. [ - a.index('accountRepresentativeId') - // highlight-next-line - .queryField("listByRep"), - ]), -}).authorization([a.allow.owner()]); + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + .secondaryIndexes((index) => [ + index("accountRepresentativeId") + // highlight-next-line + .queryField("listByRep"), + ]) + .authorization([a.allow.owner()]), +}); ``` In your client app code, you'll see query updated under the Data client: -```ts +```ts title="src/App.tsx" const { data, errors @@ -141,16 +145,19 @@ const { To customize the underlying DynamoDB's index name, you can optionally provide the `name()` modifier. -```ts +```ts title="amplify/data/resource.ts" const schema = a.schema({ - Customer: a.model({ - name: a.string(), - phoneNumber: a.phone(), - accountRepresentativeId: a.id().required(), - }).secondaryIndexes(index => [ - a.index('accountRepresentativeId') - // highlight-next-line - .name("MyCustomIndexName"), - ]) -}).authorization([a.allow.owner()]); + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + .secondaryIndexes((index) => [ + index("accountRepresentativeId") + // highlight-next-line + .name("MyCustomIndexName"), + ]) + .authorization([a.allow.owner()]), +}); ``` diff --git a/src/pages/[platform]/start/mobile-support/index.mdx b/src/pages/[platform]/start/mobile-support/index.mdx index d90970836d2..c00d340a320 100644 --- a/src/pages/[platform]/start/mobile-support/index.mdx +++ b/src/pages/[platform]/start/mobile-support/index.mdx @@ -135,7 +135,7 @@ To use the Amplify UI libraries, you need to add the following dependencies to y ```kotlin dependencies { implementation("androidx.compose.material3:material3:1.1.0") - implementation("com.amplifyframework.ui:authenticator:1.0.1") + implementation("com.amplifyframework.ui:authenticator:1.1.0") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") } ``` diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx index d41a7878feb..57d95b1c841 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx @@ -123,7 +123,7 @@ Use the guide below to select the correct authorization strategy for your use ca | **Recommended use case** | **Strategy** | **Provider** | | --- | --- | --- | | Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access. | [`public`](#public-data-access) | `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`](#public-data-access) | `iam` | +| Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using AWS IAM controls. | [`public`](#public-data-access) | `iam` (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. | [`owner`](#per-user--owner-based-data-access) | `userPools` / `oidc` | | Any signed-in data access. Unlike owner-based access, **any** signed-in user has access. | [`private`](#signed-in-user-data-access) | `userPools` / `oidc` / `iam` | | Per user group data access. A specific or dynamically configured group of users have access | [`groups`](#user-group-based-data-access) | `userPools` / `oidc` | @@ -139,17 +139,25 @@ type Todo @model @auth(rules: [{ allow: public }]) { } ``` -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. +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. +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: public, provider: iam }]) { + id: ID! + title: String! +} +``` + -Designate an IAM role for unauthenticated identities by setting the `iamConfig` property: +Designate an Amazon Cognito identity pool's role for unauthenticated identities by setting the `identityPoolConfig` property: ```ts // Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well @@ -170,7 +178,7 @@ new AmplifyGraphqlApi(this, "MyNewApi", { apiKeyConfig: { expires: cdk.Duration.days(30) }, - iamConfig: { + identityPoolConfig: { identityPoolId: identityPool.identityPoolId, authenticatedUserRole: identityPool.authenticatedRole, unauthenticatedUserRole: identityPool.unauthenticatedRole, @@ -179,6 +187,14 @@ new AmplifyGraphqlApi(this, "MyNewApi", { }) ``` +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: public, provider: identityPool }]) { + id: ID! + title: String! +} +``` + In the Amplify Library's client configuration file (`amplifyconfiguration.json`) set `allowGuestAccess` to `true`. This lets the Amplify Library use the unauthenticated role from your Cognito identity pool when your user isn't logged in. @@ -208,13 +224,7 @@ In the Amplify Library's client configuration file (`amplifyconfiguration.json`) -```graphql -# public authorization with provider override -type Post @model @auth(rules: [{ allow: public, provider: iam }]) { - id: ID! - title: String! -} -``` + ### Per-user / owner-based data access @@ -297,17 +307,24 @@ type Todo @model @auth(rules: [{ allow: private }]) { 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. +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. +```graphql +# public authorization with provider override +type Post @model @auth(rules: [{ allow: private, provider: iam }]) { + id: ID! + title: String! +} +``` -Designate an IAM role for authenticated identities by setting the `iamConfig` property: +Designate an Amazon Cognito identity pool role for authenticated identities by setting the `identityPoolConfig` property: ```ts // Note: this sample uses the alpha Cognito Identity Pool construct, but is not required, CfnIdentityPool can be used as well @@ -328,7 +345,7 @@ new AmplifyGraphqlApi(this, "MyNewApi", { apiKeyConfig: { expires: cdk.Duration.days(30) }, - iamConfig: { + identityPoolConfig: { identityPoolId: identityPool.identityPoolId, authenticatedUserRole: identityPool.authenticatedRole, unauthenticatedUserRole: identityPool.unauthenticatedRole, @@ -337,15 +354,18 @@ new AmplifyGraphqlApi(this, "MyNewApi", { }) ``` - - - ```graphql -type Todo @model @auth(rules: [{ allow: private, provider: iam }]) { - content: String +# 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](#using-oidc-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, @@ -672,12 +692,11 @@ userPools:owner:owner ### 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//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: +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//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: ```json { @@ -688,7 +707,7 @@ Add the allow-listed IAM users by adding them to `amplify/backend/api/ -To grant an external AWS Resource or an IAM role access to this GraphQL API in CDK, you need to explicitly list the IAM role by adding them to `adminRoles` property. +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. ```typescript const userRole = Role.fromRoleName( @@ -706,7 +725,10 @@ const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { apiKeyConfig: { expires: cdk.Duration.days(30) }, - adminRoles: [userRole] // <-- pass in the role of the console user into here to grant it access via IAM + iamConfig: { + // Set this value to true. + enableIamAuthorizationMode: true + } } }); ``` @@ -811,7 +833,7 @@ Once you grant a function access to the GraphQL API, it is required to redeploy -To grant an external AWS Resource or an IAM role access to this GraphQL API in CDK, you need to explicitly list the IAM role by adding them to `adminRoles` property. +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. ```typescript const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { @@ -823,9 +845,10 @@ const amplifyApi = new AmplifyGraphqlApi(this, 'MyNewApi', { apiKeyConfig: { expires: cdk.Duration.days(30) }, - adminRoles: [ - myFunction.role // <- Add your function's role here to grant it access to issue queries, mutations, and subscriptions - ] + iamConfig: { + // Must be set to `true`. Then grant your Lambda function's execution role access to the API + enableIamAuthorizationMode: true + } } }); ``` diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/mutate-data/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/mutate-data/index.mdx index d4491622aeb..867bec5bdc9 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/mutate-data/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/mutate-data/index.mdx @@ -322,7 +322,7 @@ const updatedTodo = await client.graphql({ To update data, replace the request with `.update`. ```swift -try await Amplify.API.mutate(request: .create(todo)) +try await Amplify.API.mutate(request: .update(todo)) ``` diff --git a/src/pages/gen1/[platform]/tools/cli/migration/iam-auth-updates-for-cdk-construct/index.mdx b/src/pages/gen1/[platform]/tools/cli/migration/iam-auth-updates-for-cdk-construct/index.mdx new file mode 100644 index 00000000000..ed556f89364 --- /dev/null +++ b/src/pages/gen1/[platform]/tools/cli/migration/iam-auth-updates-for-cdk-construct/index.mdx @@ -0,0 +1,72 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Improved IAM authorization when using GraphQL API CDK construct', + description: 'This reference documents the behavioral improvements coming to IAM authorization when you deploy your GraphQL API using the Amplify GraphQL CDK construct.', + platforms: [ + 'android', + 'angular', + 'flutter', + 'javascript', + 'nextjs', + 'react', + 'react-native', + 'swift', + 'vue' + ], +}; + +export const getStaticPaths = async () => { + return getCustomStaticPath(meta.platforms); +}; + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + + +## What is changing? + +In an effort to improve the usability of the GraphQL API CDK construct with other AWS resources, such as AWS Lambda functions, we're providing a new way to configure IAM authorization. This change does not affect customers using the Amplify CLI (Gen 1) to build and deploy their GraphQL APIs (amplify add api). + +### How did IAM authorization work before this improvement? + +There are two use cases for IAM auth with the Amplify GraphQL CDK construct. + +**Use case 1: Provide a deny-by-default authorization pattern for Amazon Cognito identity pool's unauthenticated and authenticated role access to the API.** We wanted to ensure if you want any unauthenticated roles to access a model, you must explicit allow-list that behavior. For example: `@auth(rules: [{ allow: public, provider: iam }])`. + +**Use case 2: Provide access to the GraphQL API from other IAM principals, such as AWS Lambda functions' execution role, or to use the GraphQL query explorer from the AWS AppSync console.** In this case, despite these IAM roles having the required IAM policies to access the GraphQL API, you needed to also supply these IAM roles into the Amplify GraphQL API CDK construct's `allowListedRoles` properties. This often led to circular dependency issues that are difficult to untangle. + +### How does IAM authorization work after this improvement? + +We'll issue a non-breaking feature improvement that'll make the use case clearer for customers. + +**Changes to use case 1:** To make the API more descriptive and clarify its intended behavior, we're renaming the `provider: iam` variable to `provider: identityPool` in the `@auth` rules. At the same time, we're introducing a `identityPoolConfig` property on the CDK construct that'll accept the existing values for (`identityPoolId`, `authenticatedUserRole`, `unauthenticatedUserRole`). + +**Changes to use case 2:** Instead of explicitly setting the `allowListedRoles`, you now only need to set the `enableIamAuthorizationMode: true` under the `iamConfig` property. Now all resource access to the API with the exception of Amazon Cognito identity pool roles are governed by the resources' IAM role and policies. + +### How do I take advantage of this improvement? + +To take advantage of this improvement, follow these steps: + +1. Upgrade to the latest version of the Amplify GraphQL API CDK construct by running `npm install @aws-amplify/graphql-api-construct@latest` +2. Set the `enableIamAuthorizationMode` to `true` under `iamConfig`. +3. Move the existing values for `identityPoolId`, `authenticatedUserRole`, `unauthenticatedUserRole` from `iamConfig` to `identityPoolConfig`. +4. Validate that all IAM roles has the appropriate permissions to access the GraphQL API. **If you have configured prior overly permissive policies, modify the policies accordingly.** +5. Unset the values for `allowListedRoles`. Your `iamConfig` should only have a `enableIamAuthorizationMode: true` value at this point. +6. Deploy your GraphQL API with the updated configuration via `cdk deploy`. + +## Is this a breaking change? + +No. Your existing IAM authorization behavior will continue to work but we plan on deprecating the old behavior in the future, thus we highly recommend you to upgrade to the new behavior. If the following values under `iamConfig` are present, then the behavior remains as before: `identityPoolId`, `authenticatedUserRole`, `unauthenticatedUserRole`, `allowListedRoles`. + +## Who can take advantage of this improvement? + +Only customers using the [Amplify GraphQL API CDK construct](https://constructs.dev/packages/@aws-amplify/graphql-api-construct/v/1.6.0?lang=typescript) will be able to take advantage of this improvement. + +If you use the Amplify CLI (Gen 1) to deploy your GraphQL API (`amplify add api`), this improvement **does not** affect you.