diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index d8bcb643a59..a18308f9050 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -317,6 +317,9 @@ export const directory = { }, { path: 'src/pages/[platform]/build-a-backend/data/manage-with-amplify-console/index.mdx' + }, + { + path: 'src/pages/[platform]/build-a-backend/data/aws-appsync-apollo-extensions/index.mdx' } ] }, diff --git a/src/pages/[platform]/build-a-backend/data/aws-appsync-apollo-extensions/index.mdx b/src/pages/[platform]/build-a-backend/data/aws-appsync-apollo-extensions/index.mdx new file mode 100644 index 00000000000..b7068f5ad97 --- /dev/null +++ b/src/pages/[platform]/build-a-backend/data/aws-appsync-apollo-extensions/index.mdx @@ -0,0 +1,398 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'AWS AppSync Apollo Extensions', + description: + 'AWS AppSync Apollo Extensions', + platforms: [ + 'android', + 'swift', + ] +}; + +export function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +AWS AppSync Apollo Extensions provide a seamless way to connect to your AWS AppSync backend using Apollo client, an open-source GraphQL client. + + + +To learn more about Apollo, see https://www.apollographql.com/docs/ios/. + + + + + + +To learn more about Apollo, see https://www.apollographql.com/docs/kotlin. + + + +## Features + +AWS AppSync Apollo Extensions provide AWS AppSync authorizers to be used with the Apollo client to make it simple to apply the correct authorization payloads to your GraphQL operations. + +The Amplify library provides components to facilitate configuring the authorizers with Apollo client by providing configuration values to connect to your Amplify Data backend. + +### Install the AWS AppSync Apollo Extensions library + + + +To connect Apollo to AppSync without using Amplify, add the `apollo-appsync` dependency to your app/build.gradle.kts file. If your +application is using Amplify Android, add the `apollo-appsync-amplify` dependency instead. + +```kotlin title="app/build.gradle.kts" +dependencies { + // highlight-start + // Connect Apollo to AppSync without using Amplify + implementation("com.amplifyframework:apollo-appsync:1.0.0") + // highlight-end + // or + // highlight-start + // Connect Apollo to AppSync, delegating some implementation details to Amplify + implementation("com.amplifyframework:apollo-appsync-amplify:1.0.0") + // highlight-end +} +``` + + + + + +Add AWS AppSync Apollo Extensions into your project using Swift Package Manager. + +Enter its GitHub URL (`https://github.com/aws-amplify/aws-appsync-apollo-extensions-swift`), select **Up to Next Major Version** and click **Add Package** + +* Select the following libraries: + * **AWSAppSyncApolloExtensions** + + + +### Connecting to AWS AppSync with Apollo client + +AWS AppSync supports the following [authorization modes](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html): + +#### API_KEY + + + +```swift +import AWSAppSyncApolloExtensions + +let authorizer = APIKeyAuthorizer(apiKey: "[API_KEY]") +let interceptor = AppSyncInterceptor(authorizer) +``` + + + + + +An `ApiKeyAuthorizer` can be used with a hardcoded API key, by fetching the key from some source, or reading it from `amplify_outputs.json`: + +```kotlin +// highlight-start +// Use a hard-coded API key +val authorizer = ApiKeyAuthorizer("[API_KEY]") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = ApiKeyAuthorizer { fetchApiKey() } +//highlight-end +// or +// highlight-start +// Using ApolloAmplifyConnector to read API key from amplify_outputs.json +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) +val authorizer = connector.apiKeyAuthorizer() +//highlight-end +``` + + + +#### AMAZON_COGNITO_USER_POOLS + + + +If you are using Amplify Auth, you can create a method that retrieves the Cognito access token + +```swift +import Amplify + +func getUserPoolAccessToken() async throws -> String { + let authSession = try await Amplify.Auth.fetchAuthSession() + if let result = (authSession as? AuthCognitoTokensProvider)?.getCognitoTokens() { + switch result { + case .success(let tokens): + return tokens.accessToken + case .failure(let error): + throw error + } + } + throw AuthError.unknown("Did not receive a valid response from fetchAuthSession for get token.") +} +``` + +Then create the AuthTokenAuthorizer with this method. + +```swift +import AWSAppSyncApolloExtensions + +let authorizer = AuthTokenAuthorizer(fetchLatestAuthToken: getUserPoolAccessToken) +let interceptor = AppSyncInterceptor(authorizer) +``` + + + + +You can use `AmplifyApolloConnector` to get an `AuthTokenAuthorizer` instance that supplies the token for the current logged-in Amplify user, or implement the token fetching yourself. + +```kotlin +// highlight-start +// Using ApolloAmplifyConnector to get the authorizer that connects to your +// Amplify instance +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) +val authorizer = connector.authTokenAuthorizer() +//highlight-end +// or +// highlight-start +// Using the ApolloAmplifyConnector companion function +val authorizer = AuthTokenAuthorizer { + ApolloAmplifyConnector.fetchLatestCognitoAuthToken() +} +//highlight-end +// or +// highlight-start +// Use your own token fetching. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = AuthTokenAuthorizer { + fetchLatestAuthToken() +} +//highlight-end +``` + + + +You can provide your own custom `fetchLatestAuthToken` provider for **AWS_LAMBDA** and **OPENID_CONNECT** auth modes. + +#### AWS_IAM + + + +If you are using Amplify Auth, you can use the following method for AWS_IAM auth + +```swift +import AWSCognitoAuthPlugin +import AWSAppSyncApolloExtensions + +let authorizer = IAMAuthorizer( + signRequest: AWSCognitoAuthPlugin.createAppSyncSigner( + region: "[REGION]")) +``` + + + + + +If you are using `apollo-appsync-amplify`, you can use the `ApolloAmplifyConnector` to delegate token fetching and request +signing to Amplify. + +```kotlin +// highlight-start +// Using ApolloAmplifyConnector to get the authorizer that connects to your +// Amplify instance +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) +val authorizer = connector.iamAuthorizer() +//highlight-end +// or +// highlight-start +// Using the ApolloAmplifyConnector companion function +val authorizer = IamAuthorizer { + ApolloAmplifyConnector.signAppSyncRequest(it, "us-east-1") +} +//highlight-end +``` + + + +### Connecting Amplify Data to Apollo client + +Before you begin, you will need an Amplify Data backend deploy. To get started, see [Set up Data](/[platform]/build-a-backend/data/set-up-data/). + +Once you have deployed your backend and created the `amplify_outputs.json` file, you can use Amplify library to read and retrieve your configuration values with the following steps: + + + +1. Enter its GitHub URL (`https://github.com/aws-amplify/amplify-swift`), select **Up to Next Major Version** and click **Add Package** +2. Select the following libraries: + 1. **AWSPluginsCore** +3. Drag and drop the `amplify_outputs.json` file into your Xcode project. +4. Initialize the configuration with `try AWSAppSyncConfiguration(with: .amplifyOutputs)` + +The resulting configuration object will have the `endpoint`, `region`, and optional `apiKey.` The following example shows reading the `amplify_outputs.json` file from the main bundle to instantiate the configuration and uses it to configure the Apollo client for **API_Key** authorization. + +```swift +import Apollo +import ApolloAPI +import AWSPluginsCore +import AWSAppSyncApolloExtensions + +func createApolloClient() throws -> ApolloClient { + let store = ApolloStore(cache: InMemoryNormalizedCache()) + + // 1. Read AWS AppSync API configuration from `amplify_outputs.json` + let configuration = try AWSAppSyncConfiguration(with: .amplifyOutputs) + + // 2. Use `configuration.apiKey` with APIKeyAuthorizer + let authorizer = APIKeyAuthorizer(apiKey: configuration.apiKey ?? "") + let interceptor = AppSyncInterceptor(authorizer) + let interceptorProvider = DefaultPrependInterceptorProvider(interceptor: interceptor, + store: store) + // 3. Use `configuration.endpoint` with RequestChainNetworkTransport + let transport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, + endpointURL: configuration.endpoint) + + return ApolloClient(networkTransport: transport, store: store) +} +``` + + + + + +Add `apollo-appsync-amplify` dependency to your app/build.gradle.kts file: + +```kotlin title="app/build.gradle.kts" +dependencies { + // highlight-start + implementation("com.amplifyframework:apollo-appsync-amplify:1.0.0") + // highlight-end +} +``` + +```kotlin +// Use apiKey auth mode, reading configuration from AmplifyOutputs +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) +val apolloClient = ApolloClient.Builder() + .appSync(connector.endpoint, connector.apiKeyAuthorizer()) + .build() +``` + + + +Depending on your authorization strategy defined on your schema, you can use the corresponding Authorizer. To read more about the strategies and their corresponding auth modes, see [Available authorization strategies](/[platform]/build-a-backend/data/customize-authz/#available-authorization-strategies). + + +Some common ones are + +* `publicAPIkey` strategy, `apiKey` authMode, **APIKeyAuthorizer** +* `guest` strategy, `identityPool` authMode, **IAMAuthorizer** +* `owner` strategy, `userPool` authMode, **AuthTokenAuthorizer** + +If you define multiple authorization strategies on a single model, you will have to create separate Apollo client instances for each Authorizer that you want to use in your app. + +### Downloading the AWS AppSync schema + +The schema is used by Apollo’s code generation tool to generate API code that helps you execute GraphQL operations. The following steps integrate your AppSync schema with Apollo's code generation process: + + +1. Navigate to your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home) +2. On the left side, select Schema +3. When viewing your schema, there should a “Export schema” drop down. Select this and download the `schema.json` file. +4. Add this file to your project as directed by [Apollo Code Generation documentation](https://www.apollographql.com/docs/ios/code-generation/introduction). + +You can alternatively download the introspection schema using the [`fetch-schema`](https://www.apollographql.com/docs/ios/code-generation/codegen-cli#fetch-schema) command with the `amplify-ios-cli` tool. + + + + +1. Navigate to your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home) +2. On the left side, select Schema +3. When viewing your schema, there should a “Export schema” drop down. Select this and download the `schema.json` file. +4. Add this file to your project as directed by [Apollo documentation](https://www.apollographql.com/docs/kotlin/advanced/plugin-recipes#specifying-the-schema-location) + + +### Performing Queries, Mutations, and Subscriptions with Apollo client + +1. Navigate to the **Queries** tab in your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home). Here, you can test queries, mutations, and subscriptions in the GraphQL playground. +2. Enter your GraphQL operation (query, mutation, or subscription) in the editor and click **Run** to execute it. +3. Observe the request and response structure in the results. This gives you insight into the exact call patterns and structure that Apollo will use. +4. Copy the GraphQL operation from the playground and pass it to Apollo's code generation tool to automatically generate the corresponding API code for your project. + +## Connecting to AWS AppSync real-time endpoint + +The following example shows how you can create an Apollo client that allows performing GraphQL subscription operations with AWS AppSync. + + + +```swift +import Apollo +import ApolloAPI +import ApolloWebSocket +import AWSPluginsCore +import AWSAppSyncApolloExtensions + +func createApolloClient() throws -> ApolloClient { + let store = ApolloStore(cache: InMemoryNormalizedCache()) + let configuration = try AWSAppSyncConfiguration(with: .amplifyOutputs) + + // 1. Create your authorizer + let authorizer = /* your Authorizer */ + let interceptor = AppSyncInterceptor(authorizer) + + let interceptorProvider = DefaultPrependInterceptorProvider(interceptor: interceptor, + store: store) + let transport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, + endpointURL: configuration.endpoint) + + // 2. Create the AWS AppSync compatible websocket client + let websocket = AppSyncWebSocketClient(endpointURL: configuration.endpoint, + authorizer: authorizer) + // 3. Add it to the WebSocketTransport + let webSocketTransport = WebSocketTransport(websocket: websocket) + // 4. Create a SplitNetworkTransport + let splitTransport = SplitNetworkTransport( + uploadingNetworkTransport: transport, + webSocketNetworkTransport: webSocketTransport + ) + // 5. Pass the SplitNetworkTransport to the ApolloClient + return ApolloClient(networkTransport: splitTransport, store: store) +} +``` + + + + + +When using `apollo-appsync`, you create `AppSyncEndpoint` and `AppSyncAuthorizer` instances, and pass them to the ApolloClient's Builder extension function. + +```kotlin +val endpoint = AppSyncEndpoint("") +val authorizer = /* your Authorizer */ + +val apolloClient = ApolloClient.Builder() + .appSync(endpoint, authorizer) + .build() +``` + +When using `apollo-appsync-amplify`, you can get the endpoint and authorizer from an `ApolloAmplifyConnector` to connect to your Amplify backend. + +```kotlin +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) + +val apolloClient = ApolloClient.Builder() + .appSync(connector.endpoint, connector.apiKeyAuthorizer()) // or .authTokenAuthorizer(), or .iamAuthorizer() + .build() +``` + +