Facebook Oauth login flow with Flutter

I decided to give Flutter a deeper try than previously. But I encounter some troubles with authentication. So in this story I will share what I have done to implement Facebook authentication in my Flutter app with only dart code.

As you probably know Flutter is a mobile app SDK for building high-performance, high-fidelity, apps for iOS and Android, from a single codebase.

Let’s start!

Invoking the Login Dialog and setting the Redirect URL

What we want is a manually login flow for facebook. Here is the link to the facebook documentation. We need to call the facebook dialog page with our cliend_id and a redirect_uri.

This step gives us some informations that is in fact not the first step. We need a redirect_uri but we only have our app, so we will create a little http server inside our app, for that we will start with a little server which handles one request and shutdowns.

Here is the code:

HttpServer server =
await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 8080);
server.listen((HttpRequest request) async {
  request.response
..statusCode = 200
..headers.set("Content-Type", ContentType.HTML.mimeType)
..write("<html><h1>You can now close this window</h1></html>");
await request.response.close();
await server.close(force: true);
});

If you execute this code and then go to http://localhost:8080/ you will see a page telling you ‘You can now close this window’ and if you reload the page you will see a page not found.

Next we need to open a browser and go to https://www.facebook.com/v2.8/dialog/oauth?client_id={app-id}&redirect_uri=http://localhost:8080/ you will need to change {app-id} by your app id.

So we will use one service provided by the Flutter framework called UrlLauncher.

final String url = "https://www.facebook.com/v2.8/dialog/oauth?client_id={app-id}&redirect_uri=http://localhost:8080/"
UrlLauncher.launch(url);

Now we have to get the query param named code that is added by Facebook when they do the redirection to our url. We will modify our server a little to do that. And transform it in an usable function.

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><h1>You can now close this window</h1></html>");
await request.response.close();
await server.close(force: true);
onCode.add(code);
await onCode.close();
});
return onCode.stream;
}

Almost the same functionality we had previously, plus getting the code from the query parameters and put this code inside a StreamController.

Let’s put all these pieces together!

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";
UrlLauncher.launch(url);
final String code = await onCode.first;
}

We can pass to the second step of the oauth flow!

Exchanging Code for an Access Token

Here we just need to call this url with the code we get before and our app credential.

https://graph.facebook.com/v2.8/oauth/access_token?client_id={app-id}&redirect_uri={redirect-uri}&client_secret={app-secret}&code={code-parameter}

We will use the http package from pub for that task!

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");

The response is a JSON that looks like that:

{
"access_token": {access-token},
"token_type": {type},
"expires_in": {seconds-til-expiration}
}

So we will wrap that in a dart object.

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'];
}

Now a quick resume of the code for this second step.

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";
UrlLauncher.launch(url);
final String code = await onCode.first;
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));
}

Before we can write an app we need one more thing! Write an utility class to call the Graph API. Here is something really basic:

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));
}
}

It’s a basic call to an API in Dart. Now we can pass to the flutter part of the code :)

const String appId = "your app id";
const String appScecret = "your app secret";
Future<Null> main() async {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
fb.Token token;
fb.FacebookGraph graph;
fb.PublicProfile profile;

@override
Widget build(BuildContext context) {
if (token == null) {
return new Scaffold(
body: new Center(
child: new RaisedButton(
child: new Text("Login with Facebook"),
onPressed: () async {
final fb.Token _token = await fb.getToken(appId, appSecret);
fb.FacebookGraph _graph = new fb.FacebookGraph(_token);
fb.PublicProfile _profile = await _graph.me(["name"]);
setState(() {
token = _token;
graph = _graph;
profile = _profile;
});
},
),
),
);
}
return new Scaffold(
body: new Center(child: new Text("Hello ${profile.name}")),
);
}
}

And Tadam! You have an app where you can authenticate your user with Facebook :)

You can also use almost the same workflow for other authentication service that supports oauth.

You can find the full source code of this here.


Don’t hesitate to follow me on twitter (@kevin_segaud) and clap if you have appreciate the article :)