zino-hofmann / graphql-flutter

A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package.
https://zino-hofmann.github.io/graphql-flutter
MIT License
3.24k stars 613 forks source link

AWS AppSync support #209

Closed BerndWessels closed 5 years ago

BerndWessels commented 5 years ago

AppSync is a very popular, complete serverless GraphQL service by AWS.

It would be great if we could support this with a dedicated Link.

There is already a library that helps with the AWS authorization which could be used to get this to work.

AppSync also supports serverless GraphQL subscriptions, but I have no idea how the AWS authorization for that works. Would need help on that.

micimize commented 5 years ago

This would be very cool, but I think it belongs in a stand-alone library. All you really need is an AwsAuthLink using the logic outlined in the post, modeled on an auth link. This should work for subscriptions as well, as according to the AppSync docs:

To control authorization at connection time to a subscription, you can leverage controls such as IAM, Amazon Cognito identity pools, or Amazon Cognito user pools for field-level authorization

My stack is mostly on AWS, so if I get to the point where I'm doing user management myself I might get around to this, or contributing to a standalone / plugin if someone else starts work on it first 😉

bobwiller commented 5 years ago

@micimize @mainawycliffe I think I have the auth part working for this, however I believe there are other problems w/the way AppSync handles subscriptions. I spent most of the weekend walking through things, and would love to help out here. However I have what might be a silly question: What is the best way to trace what is actually going across the wire?

micimize commented 5 years ago

@bobwiller do you have a repo? A logging link would help log the request/response info.

I guess I should have read a bit more in the docs

Subscription connection management is handled automatically by the AWS AppSync client SDK using MQTT over WebSockets as the network protocol between the client and service.

So this might take an mqtt_client link also.

bobwiller commented 5 years ago

I don't have a repo at the moment. Still trying to prove I can connect at all. I have been playing w/the mqtt_client package as well just to see if i can create a connection - as soon as I have something i will post it. Right now I can't get past a connection error that my connection "was not upgraded to websocket" which I am assuming is because the Connection and Upgrade headers need to be set....

bobwiller commented 5 years ago

@micimize I finally have this working in Dart/Flutter (but outside of this package). I don't know a lot about other GraphQL implementations, but for AWS Appsync, this is a two step process: An http query, and then the socket is built based on the response from that call. (The socket gets all the data it needs to connect from the response, including auth token and uri - and yes, it uses MQTT). I would love to contribute a "real" implementation back to this project if someone could help talk through how best to approach it and what changes would need to be made.

For those who are looking for how to set up a Dart-based AppSync Subscription, here is some pseudo-code (Note, this relies on mqtt_client.dart and amazon_cognito_identity_dart packages):

https://gist.github.com/bobwiller/b669f3476fcd2805b4f25b96243d859a

Ibtesam-Mahmood commented 5 years ago

@micimize I finally have this working in Dart/Flutter (but outside of this package). I don't know a lot about other GraphQL implementations, but for AWS Appsync, this is a two step process: An http query, and then the socket is built based on the response from that call. (The socket gets all the data it needs to connect from the response, including auth token and uri - and yes, it uses MQTT). I would love to contribute a "real" implementation back to this project if someone could help talk through how best to approach it and what changes would need to be made.

For those who are looking for how to set up a Dart-based AppSync Subscription, here is some pseudo-code (Note, this relies on mqtt_client.dart and amazon_cognito_identity_dart packages):

https://gist.github.com/bobwiller/b669f3476fcd2805b4f25b96243d859a

Hi, I was wondering if the link for the pseudocode for the aws appsync subscriptions could be reposted

micimize commented 5 years ago

@Ibtesam-Mahmood https://gist.github.com/bobwiller/b669f3476fcd2805b4f25b96243d859a - was a markdown typo.

@bobwiller I think you'd want to implement a custom link with a mqtt client, similar in structure to how the websocket link works

isaiahtaylorhh commented 4 years ago

If you're only needing Cognito pool authorization, you can do that out of the box using AuthLink by passing in your JWT token.

final token = session.getAccessToken().getJwtToken(); // Comes from amazon_cognito_identity_dart_2

final HttpLink httpLink = HttpLink(
  uri: '[your graphql url]',
);

final AuthLink authLink = AuthLink(
  getToken: () => token, // pass your Cognito JWT token to the auth link
);

final Link link = authLink.concat(httpLink);

ValueNotifier<GraphQLClient> client = ValueNotifier(
  GraphQLClient(
    cache: InMemoryCache(),
    link: link,
  ),
);

Then, pass this client in to the provider:

return GraphQLProvider(
    client: client,
    child: MaterialApp(
      title: 'Flutter Demo',
      ...
    ),
  );

Versions:

amazon_cognito_identity_dart_2: ^0.1.9
graphql_flutter: ^3.0.0
subhendukundu commented 4 years ago

@isaiahtaylor I think this a great start. It would be great if we can create a small demo project to explain how it works that will be so much easy to visualize how to use it. I will try this and if it works probably will try to create a PR.

NimaSoroush commented 4 years ago

If you're only needing Cognito pool authorization, you can do that out of the box using AuthLink by passing in your JWT token.

final token = session.getAccessToken().getJwtToken(); // Comes from amazon_cognito_identity_dart_2

final HttpLink httpLink = HttpLink(
  uri: '[your graphql url]',
);

final AuthLink authLink = AuthLink(
  getToken: () => token, // pass your Cognito JWT token to the auth link
);

final Link link = authLink.concat(httpLink);

ValueNotifier<GraphQLClient> client = ValueNotifier(
  GraphQLClient(
    cache: InMemoryCache(),
    link: link,
  ),
);

Then, pass this client in to the provider:

return GraphQLProvider(
    client: client,
    child: MaterialApp(
      title: 'Flutter Demo',
      ...
    ),
  );

Versions:

amazon_cognito_identity_dart_2: ^0.1.9
graphql_flutter: ^3.0.0

@isaiahtaylor : This is very useful but what if I need to get that amazon_cognito_identity_dart_2 token from my login page which further down into my app routes. Basically the authentication happens asynchronously. So at the start time I don't have the token. How would you solve that problem?

isaiahtaylorhh commented 4 years ago

@NimaSoroush notice that the AuthLink getToken parameter does not take a String, but a FutureOr<String>. This means that you can pass it an async function that resolves the token, whenever it's available. The GraphQL library will await the result of calling your function until your token is ready.

Pass getToken a function which returns a Future<String>, and complete that future when your user signs in.

Quick pseudo-code:

abstract class TokenHolder {
  static Completer<String> tokenCompleter = new Completer();

  static Future<String> get token => tokenCompleter.future;
  static set token(String tokenValue) => tokenCompleter.complete(tokenValue);
}

// at top level / wherever you want to initialize
final AuthLink authLink = AuthLink(
  getToken: () => TokenHolder.token,
);

// in your sign in flow
void yourSignInFunction(String token) {
  TokenHolder.token = token;
}

Alternatively, rather than having a class, you can pass your completer down to the widgets that need it.

void runApp() {
  final tokenCompleter = new Completer<String>();

  final AuthLink authLink = AuthLink(
    getToken: () => tokenCompleter.future,
  );

  // now, pass `tokenCompleter` down the chain
  // to your SignIn component, and when the user signs in,
  // call tokenCompleter.complete(token).
}

Hope this helps.

NimaSoroush commented 4 years ago

Hey @isaiahtaylor that was amazingly helpful. Thanks a lot