Fook
Fook

Reputation: 5550

How do I configure Amplify to to use multiple AppSync endpoints?

I need to support authenticated and unauthenticated AppSync requests in a React Native app. Since AppSync only allows one authorization type per API, I am setting up two APIs: one for authenticated users (Cognito User Pools), and one for guests (API Key).

I think to make this work I need to have two distinct AWSAppSyncClient configs in the same app.

  // authenticated user    
  const appSyncAuthenticatedClient = new AWSAppSyncClient({
    url: Config.APPSYNC_AUTHENTICATED_ENDPOINT,
    region: Config.APPSYNC_REGION,
    auth: {
      type: 'AMAZON_COGNITO_USER_POOLS',
      jwtToken: async () =>
        (await Auth.currentSession()).getAccessToken().getJwtToken()
    }
  });

  // guest    
  const appSyncUnauthenticatedClient = new AWSAppSyncClient({
    url: Config.APPSYNC_UNAUTHENTICATED_ENDPOINT,
    region: Config.APPSYNC_REGION,
    auth: {
      type: 'API_KEY',
      apiKey: Config.APPSYNC_API_ID
    }
  });

and then determine which to use based on whether or not they are logged in

    Auth.currentAuthenticatedUser()
      .then(user => this.appSyncRunningClient = appSyncAuthenticatedClient)
      .catch(err => this.appSyncRunningClient = appSyncUnauthenticatedClient);

    const App = props => {
      return (
        <ApolloProvider client={this.appSyncRunningClient}>
          <Rehydrated>
              <RootStack/>
            </Root>
          </Rehydrated>
        </ApolloProvider>
      );
    };

    export default App;

This fails because currentAuthenticatedUser returns a promise, and I'm stuck at how to resolve a promise at this top level instantiation of the app. I'll also need to swap out this config during auth events.

In what way can I dynamically select and change the ApolloProvider config at startup and authentication events?

Upvotes: 4

Views: 3622

Answers (2)

Martin
Martin

Reputation: 130

Things may have moved on as AppSync now supports multiple authentication types per API; however providing an answer as to how to auth/unauth on same endpoint for prosperity. Doesn't answer the how-to multiple endpoints question which is what led me here, but that's no longer required in OPs scenario.

Note: This answer applies to typescript - I'm not overly familiar with react but I think it will work exactly the same...

  • Unauthenticated access uses AWS_IAM / i.e. CognitoIdentityPool (configured to allow unauthenticated access)
  • Authenticated Access users AMAZON_COGNITO_USER_POOLS authentication.

To switch between unauthenticated and authenticated API.graphql() calls. You need to test the current authentication status and use that to override the authMode as in the arguments to the API.graphql() call.

As a prerequisite:

  • The types in graphql must be setup to allow access via both @aws_iam and @aws_cognito_user_pools (see sample below)
  • The AppSync API must be configured to allow both authentication types (The code below assumes the API is configured for AWS_IAM by default, but allowed CognitoUserPools as an additional authentication type). This can be configured in console, or via cloudFormation.

Sample code for API call

      let authMode;
      try {
        authMode = (await Auth.currentUserPoolUser()) ? GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS : undefined;
      } catch (err) { }

      const result = await API.graphql({
        ...graphqlOperation(statement, gqlAPIServiceArguments),
        authMode
      });

Example grqphql type

type Profile @aws_iam @aws_cognito_user_pools {
  username: ID!
  stuff: String!
}

My Amplify Configuration

{
  aws_project_region: 'VALUE_HERE',
  aws_appsync_graphqlEndpoint: 'https://VALUE_HERE/graphql',
  aws_appsync_region: 'VALUE_HERE',
  aws_appsync_authenticationType: 'AWS_IAM',
  aws_appsync_apiKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXX', // This field seems to be required, but the value is ignored.
  Auth: {
    identityPoolId: 'VALUE_HERE',
    region: 'VALUE_HERE',
    userPoolId: 'VALUE_HERE',
    userPoolWebClientId: 'VALUE_HERE',
    oauth: {
      domain: 'VALUE_HERE',
      redirectSignIn: 'VALUE_HERE',
      redirectSignOut: 'VALUE_HERE',
      scope: ['email', 'openid', 'profile', 'aws.cognito.signin.user.admin'],
      responseType: 'code'
    }
  }
};

Upvotes: 2

user1564354
user1564354

Reputation: 110

This is currently not possible. Until top-level await is officially supported you should create two Apollo clients one for the API and one for the Cognito.

for example: in your App.js

export default function App(props) {
  const [client, setClient] = useState(null);

  useEffect(() => {
   checkAuth()
  }, []);

  function checkAuth() {
    Auth.currentSession().then(session => {
      const token = session.getIdToken();
      const jwtToken = token.getJwtToken();
      if (typeof jwtToken == "string") {
        const authClientConfig = {
          url: awsmobile.aws_appsync_graphqlEndpoint,
          region: awsmobile.aws_appsync_region,
          disableOffline: true,
          auth: {
            type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
            jwtToken: jwtToken
          }
        }
        const link = ApolloLink.from([createAuthLink(authClientConfig), createSubscriptionHandshakeLink(authClientConfig)]);
        const authClient = new ApolloClient({ link, cache: new InMemoryCache({ addTypename: false }) });
        setClient(authClient);
      } else {
        throw "error";
      }
    }).catch(e => {
      console.log(e);
      const config = {
        url: awsmobile.aws_appsync_graphqlEndpoint,
        region: awsmobile.aws_appsync_region,
        disableOffline: true,
        auth: {
          type: AUTH_TYPE.API_KEY,
          apiKey: awsmobile.aws_appsync_apiKey
        }
      }
      const link = ApolloLink.from([createAuthLink(config), createSubscriptionHandshakeLink(config)]);
      const authClient = new ApolloClient({ link, cache: new InMemoryCache({ addTypename: false }) });
        setClient(authClient);
    })
  }

    if (!client) {
      return "Loading..."
    }

    return (
      <ApolloProvider client={client}>
          ...
      </ApolloProvider>
    );
}`

Upvotes: 2

Related Questions