This article gives a fully working solution to support Facebook Oauth login flow with Flutter.

Difficulty: Beginner

Forewords

I have been struggling so much to find a working solution to the basic needs for almost all Flutter application: integration with Facebook authentication.

After hours of Google research, trial and errors I finally came with a solution that works for me and I simply wanted to share this solution with you.

Credits

This solution is not mine, at first. As I said, after many hours of research on Google, I finally found this article, written by Kevin Segaud, which explains in very much details the process and gives the source code.

However, this solution did not fully satisfy me in 2 aspects:

  • it does not fully integrate the Facebook authentication HTML page as embedded in the Flutter application
  • it does not explain a very critical parameter to be set up at Facebook developers page.

This article simply complements the solution from Kevin and explains how to use it.

Solution

How to embed the Facebook HTML login page?

In order to fully embed the Facebook HTML authentication page in your Flutter application, rather than using the url_launcher plugin, prefer to use the flutter_webview_plugin plugin.

To use it, add the following line to your dependencies:

dependencies:
  flutter:
    sdk: flutter

  flutter_localizations:
    sdk: flutter
    
  flutter_webview_plugin: ^0.1.4

Valid OAuth Redirect URIs

In order to have the Flutter application work with Facebook, you need to tell Facebook where to return the result of the user’s authentication.

As this solution binds a loopback to the following URI: “http://localhost:8080”, you need to configure it in Facebook for developers. In the “Products > Facebook Login > Settings” panel, add “http://localhost:8080/” to the list of “Valid OAuth Redirect URIs”.

The final source code

Here below, you will find the final source. For the main explanation on the Facebook flow, please refer to the page from Kevin. I will only focus on the changes I made (see highlighted). Create a new file, named: facebook.dart and copy the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:http/http.dart' as http;

Future<Stream<String>> _server() async {
  final StreamController<String> onCode = new StreamController();
  HttpServer server =
  await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 8080);
  server.listen((HttpRequest request) async {
    final String code = request.uri.queryParameters["code"];
    request.response
      ..statusCode = 200
      ..headers.set("Content-Type", ContentType.HTML.mimeType)
      ..write("<html>You can now close this window</html>");
    await request.response.close();
    await server.close(force: true);
    onCode.add(code);
    await onCode.close();
  });
  return onCode.stream;
}

Future<Token> getToken(String appId, String appSecret) async {
  Stream<String> onCode = await _server();
  String url = "https://www.facebook.com/dialog/oauth?client_id=$appId&redirect_uri=http://localhost:8080/&scope=public_profile";

  ///
  /// Implementation using a webviewFlugin (full page)
  ///
  final FlutterWebviewPlugin webviewPlugin = new FlutterWebviewPlugin();
  webviewPlugin.launch(url /*, clearCache: true, clearCookies: true*/);           // Uncomment to force new login via Facebook
  final String code = await onCode.first;
  webviewPlugin.close();

  final http.Response response = await http.get(
      "https://graph.facebook.com/v2.2/oauth/access_token?client_id=$appId&redirect_uri=http://localhost:8080/&client_secret=$appSecret&code=$code");
  return new Token.fromMap(json.decode(response.body));
}

class Token {
  final String access;
  final String type;
  final num expiresIn;

  Token(this.access, this.type, this.expiresIn);

  Token.fromMap(Map<String, dynamic> json)
      : access = json['access_token'],
        type = json['token_type'],
        expiresIn = json['expires_in'];
}

class Id {
  final String id;

  Id(this.id);
}

class Cover {
  final String id;
  final int offsetY;
  final String source;

  Cover(this.id, this.offsetY, this.source);

  Cover.fromMap(Map<String, dynamic> json)
      : id = json['id'],
        offsetY = json['offset_y'],
        source = json['source'];
}

class PublicProfile extends Id {
  final Cover cover;
  final String name;

  PublicProfile.fromMap(Map<String, dynamic> json)
      : cover =
  json.containsKey('cover') ? new Cover.fromMap(json['cover']) : null,
        name = json['name'],
        super(json['id']);
}

class FacebookGraph {
  final String _baseGraphUrl = "https://graph.facebook.com/v2.8/";
  final Token token;

  FacebookGraph(this.token);

  Future<PublicProfile> me(List<String> fields) async {
    String _fields = fields.join(",");
    final http.Response response = await http
        .get("$_baseGraphUrl/me?fields=$_fields&access_token=${token.access}");
    return new PublicProfile.fromMap(json.decode(response.body));
  }
}
  • Lines #25-40: As you can see the only modifications to be applied to have the Facebook HTML login page fully embedded into your application, are applied to the getToken method. I am using the FlutterWebviewPlugin rather than the _launchURL in the original code.
  • Line #33: during your tests, should you need to force a new login with Facebook, remove the commented part of the line.

Integration

The following piece of code shows you how to integrate the Facebook authentication within your application.

import 'facebook.dart' as fb;

Future<Null> onLoginWithFacebook() async {
	String facebookAppId = "put your facebook app_id here";
	String facebookAppSecret = "put your facebook app_secret here";
	
    fb.Token token;
    fb.FacebookGraph graph;
    fb.PublicProfile profile;

    final fb.Token _token = await fb.getToken(facebookAppId, facebookAppSecret);
    fb.FacebookGraph _graph = new fb.FacebookGraph(_token);
    fb.PublicProfile _profile = await _graph.me(["name", "email"]);

///print(_profile);

    ///
    /// If a Facebook ProfileId is returned, the authentication went OK
    /// 
    if (_profile.id != null){
      ///
	  /// Proceed with further activities linked to the _profile.id
	  ///
    }
}

This is a very basic code to illustrate the use of the facebook.dart solution.

Simply fill in both facebookAppId and facebookAppSecret. To find these 2 parameters, go to the Facebook for developers page and, under Settings > Basic”.

Conclusion

As I try to collect and share as many hints and tipcs, I could not leave this topic aside…

I hope this will help somebody. Meanwhile, stay tuned for other articles and happy coding.