awslabs / aws-mobile-appsync-sdk-js

JavaScript library files for Offline, Sync, Sigv4. includes support for React Native
Apache License 2.0
921 stars 266 forks source link

Use multi-auth with Apollo client #562

Open mdegrees opened 4 years ago

mdegrees commented 4 years ago

When using Apollo client with Appsync, there seems to be no example showing the multi-auth scenario. On my schema I'm using @aws_api_key @aws_cognito_user_pools on multiples queries as in my use case some data is accessible to guests as well as to users.

What is the current behavior? Currently you can either use API key or Cognito Pool as demonstrated here: https://github.com/awslabs/aws-mobile-appsync-sdk-js#using-authorization-and-subscription-links-with-apollo-client-no-offline-support

What I expected Providing both the api Key and Jwt Token with, AppSync picks the appropriate

What I have tried I tried the bellow hack but it seems too cumbersome + subscriptions do not work.

import { createAuthLink } from "aws-appsync-auth-link";
import { SubscriptionHandshakeLink } from "aws-appsync-subscription-link/lib/subscription-handshake-link";
import { NonTerminatingHttpLink } from "aws-appsync-subscription-link/lib/non-terminating-http-link";
import { getMainDefinition } from "apollo-utilities";
import { ApolloLink } from "apollo-link";
import { HttpLink } from "apollo-link-http";
import ApolloClient from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import fetch from "isomorphic-unfetch";
import { setContext } from "apollo-link-context";
import config from "../../src/aws-exports";
import Amplify from "@aws-amplify/core";
import Auth from "@aws-amplify/auth";

Amplify.configure(config);

const url = config.aws_appsync_graphqlEndpoint;

const httpLink = new HttpLink({ uri: url, fetch });

const apolloAuthLink = setContext(async (_, { headers }) => {
  try {
    const session = await Auth.currentSession();
    return {
      headers: {
        ...headers,
        authorization: session.getIdToken().getJwtToken(),
      },
    };
  } catch (e) {
    return {
      headers: {
        ...headers,
        "X-Api-Key": config.aws_appsync_apiKey,
      },
    };
  }
});

const wsLink = ApolloLink.from([
  new NonTerminatingHttpLink("subsInfo", { uri: url }, true),
  new SubscriptionHandshakeLink("subsInfo"),
]);

let link;

if (process.browser) {
  link = ApolloLink.from([
    apolloAuthLink,
    ApolloLink.split(
      (operation) => {
        const { query } = operation;
        const definition = getMainDefinition(query);
        return definition.kind === "OperationDefinition" && definition.operation === "subscription";
      },
      wsLink,
      httpLink
    ),
  ]);
} else {
  link = ApolloLink.from([apolloAuthLink, httpLink]);
}

export default function createApolloClient(initialState, ctx) {
  // The `ctx` (NextPageContext) will only be present on the server.
  // use it to extract auth headers (ctx.req) or similar.
  return new ApolloClient({
    ssrMode: Boolean(ctx),
    link,
    cache: new InMemoryCache().restore(initialState),
  });
}

Thank you

boredcode commented 4 years ago

I'm using a similar hack except it's a little more cumbersome cos the fallback auth scheme I'm using is @aws_iam and not @ aws_api_key key which requires sigv4 signed headers.

dtelaroli commented 4 years ago

+1

anasqadrei commented 4 years ago

I used ApolloLink.split() to decide which link to use

import { ApolloClient, ApolloLink, HttpLink } from '@apollo/client'
import { createAuthLink } from 'aws-appsync-auth-link'

let accessToken

// a function to be called when user login/out or when access token expires
export const setAccessToken = (token) => {
  accessToken = token
}

function createApolloClient() {
  // API key link
  const apiAuthLink = createAuthLink({
    auth: {
      type: 'API_KEY',
      apiKey: 'ABC123...',
    },
  })

  // OpenID Connect link
  const oidcAuthLink = createAuthLink({
    auth: {
      type: 'OPENID_CONNECT',
      jwtToken: async () => accessToken,
    },
  })

  // decide which the proper link from above to use (directional link)
  const awsLink = ApolloLink.split((operation) => {
    // use your own conditions here to decide which link to use. e.g. Auth.currentSession()
    return (operation.operationName === `getUserDetails` || operation.variables.id === 1)
  }, oidcAuthLink, apiAuthLink)

  // http link (the terminating link in the chain)
  const httpLink = new HttpLink({
    uri: 'https://xxx.appsync.aws.com/graphql',
  })

  // create ApolloClient with AWS links and cache
  return new ApolloClient({
    link: ApolloLink.from([ awsLink, httpLink ]),
    cache: new InMemoryCache(),
  })
}
tobiasriemenschneider commented 3 years ago

I'm using a similar hack except it's a little more cumbersome cos the fallback auth scheme I'm using is @aws_iam and not @ aws_api_key key which requires sigv4 signed headers.

Hi @boredcode, could you paste some code? I think I'll need to go for something similar..