Graphql-flutter: Better cookie-based authentication support

Created on 1 Apr 2020  Â·  19Comments  Â·  Source: zino-app/graphql-flutter

how do I add the credentials: 'include' to the httpLink ?

link

Most helpful comment

I too have recently run into needing this option available.

All 19 comments

I am asking my self similar question.
in react apollo we can do something like

const link = createHttpLink({
  uri: '/graphql',
  credentials: 'same-origin' //or credentials: 'include'
});

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link,
});

I don't know how to do this in this library. @mainawycliffe how do we do this?

@rajihawa have you found solution?

@rajihawa thanks for response. Can you also help me how you save cookies to requests library?

@kateile you send a request to an api like that for example

Requests.post("https://api.com/login",
        bodyEncoding: RequestBodyEncoding.JSON,
        body: {
          'email': email,
          'password': password
        }) 

and the api is suppose to store the cookie as HttpOnly. and using the methods in the earlier comment I fetched the cookies.

@rajihawa nice trick. But the server I am working with expose only GraphQL api. I think my workaround would be to use requests to send post to graphQL endpoint.
Thanks for help

Yes that's how you would do it if your api only expose graphql endpoint, But still this workaround is not that efficient and we nned the solution that flutter_graphql should provide.
do you know anyone from the devs that can help us ?

I too have recently run into needing this option available.

Same

is this package still being maintained ??

I have made a new workaround that will make sure the cookies are being sent with every graphql request.
by making a custom httpClient and injecting the cookies with every request

GraphqlConfig graphqlConfig = GraphqlConfig();

Future<String> getCookie() async {
  Map<String, String> cookie = await Requests.getStoredCookies(
      Requests.getHostname("http://10.0.0.8:4000/auth/login"));
  try {
    return cookie.keys.first + "=" + cookie.values.first;
  } catch (e) {
    print(e);
    return '';
  }
}

class ClientWithCookies extends IOClient {
  @override
  Future<StreamedResponse> send(BaseRequest request) async {
    String cookie = await getCookie();
    String getCookieString(String _) => cookie;
    request.headers.update('cookie', getCookieString);
    return super.send(request).then((response) {
      return response;
    });
  }
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final HttpLink httpLink = HttpLink(
      uri: 'http://10.0.0.8:4000/graphql',
      httpClient: ClientWithCookies(),
      headers: {"cookie": await getCookie()});
  ValueNotifier<GraphQLClient> client =
      ValueNotifier(GraphQLClient(link: httpLink, cache: InMemoryCache()));

  return runApp(MyApp(
    client: client,
  ));
} 

just a note: I handle authentication outside of graphql endpoint
hope this will help

@rajihawa I have multiple cookies and try to extract them by using

Future<String> getCookie() async {
  try {
    final hostname = Requests.getHostname(graphqlEndpoint);
    final Map<String, String> map = await Requests.getStoredCookies(hostname);
    final List<String> array = [];

    map.forEach((key, value) => array.add(key + "=" + value));

    return array.join('; ');
  } catch (e) {
    return '';
  }
}
  final cookie = await getCookie();
  print('Cookie: $cookie');
  final HttpLink _httpLink = HttpLink(uri: graphqlEndpoint, headers: {'Cookie': cookie});

But I still get 401. Do I send cookies in correct way? If yes I think I need to set credentials: 'include' but don't know how!

It seems we really should have first-class support for cookies.

My first inclination is a custom link, but that might be awkward

In #531 @chynamyerz suggests adding it as a flag to HttpLink:

final HttpLink httpLink = HttpLink(
  uri: 'https://api.github.com/graphql',
  credentials: 'include' // As it's seen from the apollo-link-http package in React.
);

This is the current workaround I'm using for anyone who is looking for one

// main.dart
import ...

Future<String> getCookie() async {
  Map<String, String> cookie =
      await Requests.getStoredCookies(Requests.getHostname(k_Backend_Url()));
  if (cookie.isEmpty) {
    return '';
  } else {
    return cookie.keys.first + "=" + cookie.values.first + "";
  }
}

class ClientWithCookies extends IOClient {
  @override
  Future<IOStreamedResponse> send(BaseRequest request) async {
    String cookie = await getCookie();
    String getCookieString(String _) => cookie;
    request.headers.update('cookie', getCookieString);
    return super.send(request).then((response) {
      return response;
    });
  }
}

Future<ValueNotifier<GraphQLClient>> createClient() async {
  final HttpLink httpLink = HttpLink(
      uri: k_Backend_Url() + "/graphql",
      httpClient: ClientWithCookies(),
      headers: {"cookie": await getCookie()});
  return ValueNotifier(GraphQLClient(link: httpLink, cache: InMemoryCache()));
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await PushNotificationService().initialise();
  ValueNotifier<GraphQLClient> client = await createClient();
  bool isAuthed = await UserProvider().autoLogin(client);

  runApp(MyApp(isAuthed));
}

class MyApp extends StatelessWidget {
  final bool isAuthed;
  MyApp(this.isAuthed);

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => UserProvider(),
        ),
      ],
      child: MyMaterialApp(isAuthed),
    );
  }
}

class MyMaterialApp extends StatelessWidget {
  final bool _isAuthed;
  MyMaterialApp(this._isAuthed);

  @override
  Widget build(BuildContext context) {
    Provider.of<UserProvider>(context).info;
    return FutureBuilder(
      future: createClient(),
      builder: (_ctx, AsyncSnapshot<ValueNotifier<GraphQLClient>> e) =>
          e.hasData
              ? GraphQLProvider(
                  client: e.data,
                  child: MaterialApp(
                    title: 'My App',
                    home: _isAuthed ? HomeScreen() : LoginScreen(),
                    routes: {
                      k_LoginScreen_Route: (_) => LoginScreen(),
                      k_HomeScreen_Route: (_) => HomeScreen()
                    },
                  ),
                )
              : MaterialApp(
                  title: 'My App',
                  home: LoginScreen(),
                ),
    );
  }
}


Hey @rajihawa, this is actually helpful. However, you may have forgotten to set the cookies.

... (update cookie headers)

return super.send(request).then((response) {
   // Retrieve cookies from request headers here after updating it 
   return response;
});

v4 have option for using other links. I use gql_dio_link and with dio you can use

  final dio = Dio();
  final cookieJar = CookieJar(); //PersistCookieJar(); //todo.
  dio.interceptors.add(CookieManager(cookieJar));

  final Link _dioLink = DioLink(
    graphqlEndpoint,
    client: dio,
  );

No need for using request package. Lets play with and wait for stable version

v4 have option for using other links. I use gql_dio_link and with dio you can use

  final dio = Dio();
  final cookieJar = CookieJar(); //PersistCookieJar(); //todo.
  dio.interceptors.add(CookieManager(cookieJar));

  final Link _dioLink = DioLink(
    graphqlEndpoint,
    client: dio,
  );

No need for using request package. Lets play with and wait for stable version

Thanks @kateile!

Thanks @kateile, your solution works well!

But I got this error during mutation call for upload file due to DioLink :

flutter: *** DioError ***:
flutter: uri: http://localhost:4000/graphql
flutter: DioError [DioErrorType.DEFAULT]: Converting object to an encodable object failed: Instance of 'MultipartFile'
#0      _JsonStringifier.writeObject (dart:convert/json.dart:687:7)
#1      _JsonStringifier.writeMap (dart:convert/json.dart:768:7)
#2      _JsonStringifier.writeJsonValue (dart:convert/json.dart:723:21)
#3      _JsonStringifier.writeObject (dart:convert/json.dart:678:9)
#4      _JsonStringifier.writeMap (dart:convert/json.dart:768:7)
#5      _JsonStringifier.writeJsonValue (dart:convert/json.dart:723:21)
#6      _JsonStringifier.writeObject (dart:convert/json.dart:678:9)
#7      _JsonStringStringifier.printOn (dart:convert/json.dart:876:17)
#8      _JsonStringStringifier.stringify (dart:convert/json.dart:861:5)
#9      JsonEncoder.convert (dart:convert/json.dart:261:30)
#10     JsonCodec.encode (dart:convert/json.dart:171:45)
#11     DefaultTransformer.transformRequest (package:dio/src/transformer.dart:62:21)
#12     DioMixin._transformData (package:dio/src/dio.dart:1014:39)
<…>

Here is my graphql client :

  final Link link = DioLink(
      '${DotEnv().env['BASE_URL']}/graphql',
      client: httpService.dio,
    );
    client = GraphQLClient(
      cache: GraphQLCache(),
      link: link,
    );

Mutation :

const String uploadFile = """
  mutation uploadRequestedFile(\$requestedFileId: ID!, \$file: Upload!) {
    uploadRequestedFile(requestedFileId: \$requestedFileId, file: \$file) {
      id
      documentUrl
    }
  }
""";

```dart
final QueryResult result = await graphqlService.client.mutate(
MutationOptions(
document: gql(uploadFile),
variables: {
'file': await imagePickerService.multipartFileFrom(document.file),
'requestedFileId': document.id,
},
),
);

This problem occurs when serializing the body http.
Therefore i checked HttpLink source code, and here is how httpLink handles this case :
```dart
    final httpBody = _encodeAttempter(
      request,
      (Map body) => json.encode(
        body,
        toEncodable: (dynamic object) =>
            (object is http.MultipartFile) ? null : object.toJson(),
      ),
    )(body);

Are there any updates on this? Just curious.

Has anyone started with this? I have an idea for an API.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mainawycliffe picture mainawycliffe  Â·  16Comments

juicycleff picture juicycleff  Â·  13Comments

kevinrodriguez-io picture kevinrodriguez-io  Â·  15Comments

micimize picture micimize  Â·  14Comments

smkhalsa picture smkhalsa  Â·  15Comments