The idea is simple. The users get logged in with facebook/google/instagram and get an accessToken from the services mentioned above. This token has to be sent with each request that need authentication.
Use flutter storage to save the persist the token: https://pub.dev/packages/flutter_secure_storage
I will post you an example of one of my flutter app where I implemented the login with instagram.
Resuming the code below:
loginService.dart
is responsible for login api calls and saving the token into the storage
main.dart
is the first view of the app. Here if the user is not logged in the Login widget is shown ( the LoginWidget
is in login.dart ).
login.dart
is the view of the login page. There is a button that calls functions from LoginService
class
Here's the code:
~/lib/service/loginService.dart
import 'dart:async';
import 'dart:convert' as JSON;
import 'package:http/http.dart' as http;
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:choose/model/User.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:choose/config.dart' as config;
String apiBaseUrl = config.apiBase;
String apiPort = config.port;
class LoginService {
String clientID;
String secretID;
String redirectURI;
final FlutterSecureStorage storage = new FlutterSecureStorage();
final String authKey = "token";
LoginService(this.clientID, this.secretID, this.redirectURI);
String get authenticationURI {
return "https://api.instagram.com/oauth/authorize/?client_id=$clientID&redirect_uri=$redirectURI&response_type=code";
}
String get authorizationURI {
return "https://api.instagram.com/oauth/access_token";
}
Future<User> authenticate() async {
clean();
FlutterWebviewPlugin _flutterWebviewPlugin = FlutterWebviewPlugin();
// Stream<String> onCode = await _openServer();
_flutterWebviewPlugin.launch(
authenticationURI
);
Completer<User> loginCompleter = Completer();
_flutterWebviewPlugin.onStateChanged.listen((viewState) async {
String url = viewState.url;
int indexOfCode = url.lastIndexOf("?code=");
if(url != null && indexOfCode >= 0 && viewState.type == WebViewState.finishLoad) {
String code = url.substring(url.indexOf('?code=') + 6, url.length);
http.Response userResponse = await http.get(
"${config.endpoints['getUserByCode']}?code=$code",
);
if(userResponse.statusCode == 200) {
User user = User.fromMap(JSON.jsonDecode(userResponse.body));
saveToken(user.token);
loginCompleter.complete(user);
} else {
loginCompleter.completeError("User not found");
}
_flutterWebviewPlugin.close();
}
});
return loginCompleter.future;
}
void saveToken(token) async {
storage.write(key: authKey, value: token);
}
void clean() async {
storage.delete(key: authKey);
}
Future<String> getToken() async {
return storage.read(key: authKey);
}
static Future<String> getStaticToken() async {
FlutterSecureStorage storage = FlutterSecureStorage();
return storage.read(key: "token");
}
}
~lib/main.dart
import 'package:choose/friends.dart';
import 'package:choose/model/User.dart';
import 'package:flutter/material.dart';
import 'package:choose/login.dart';
import 'package:choose/service/loginService.dart';
import 'package:web_socket_channel/io.dart';
import 'feed.dart';
import 'notification.dart';
import 'newPost.dart';
import 'config.dart' as config;
void main() => runApp(MyApp());
LoginService loginService = new LoginService(
"71b818abd18043fb8b7c1833912b62ae",
"3d9d6f87d2354085a74534c13bdda51f",
config.endpoints['authorize'],
);
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
routes: {
'/feed': (context) => DefaultTabController(
length: 2,
child: FeedWidget()
),
'/newpost': (context) => NewPost(),
'/notifications': (context) => NotificationWidget(
channel: IOWebSocketChannel.connect(config.websocketURI)
),
'/friends': (context) => FriendsWidget(),
}
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
User user;
String label = 'Continue with Instagram';
void openLoginPage() async {
try {
User _user = await loginService.authenticate();
this.openPostPage();
} catch(e) {
this.setState(() {
label = 'Try Again';
});
}
}
void openPostPage() async {
print(await loginService.getToken());
print("token");
Navigator.pushReplacementNamed(context, '/feed');
}
void actionDelegator() {
if(user != null) {
return openPostPage();
} else {
return openLoginPage();
}
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Login(loginAction: actionDelegator, user: user, label: label);
}
}
~/lib/service/login.dart
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:choose/model/User.dart';
class Login extends StatefulWidget {
Login({this.loginAction, this.user, this.label});
final loginAction;
final User user;
final String label;
_Login createState() => _Login();
}
class _Login extends State<Login> {
String imageURI = "https://www.travelcontinuously.com/wp-content/uploads/2018/04/empty-avatar.png";
@override
Widget build(BuildContext context) {
if(widget.user != null && widget.user.profilePicture != '' ) {
imageURI = widget.user.profilePicture;
}
return Container (
color: Colors.green,
child: Column (
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircleAvatar (
backgroundColor: Colors.transparent,
radius: 50.0,
backgroundImage: NetworkImage(imageURI, scale: 2.0),
),
Container(
padding: EdgeInsets.all(0.0),
margin: EdgeInsets.all(20.0),
color: Colors.transparent,
child: MaterialButton(
elevation: 5.0,
color: Color.fromRGBO(193, 53, 132, 1.0),
child: FlatButton.icon(
label: Text(widget.label, style: TextStyle(color: Color.fromRGBO(255,220,128, 1.0), fontWeight: FontWeight.w900, fontSize: 16.0)),
icon: Icon(FontAwesomeIcons.instagram, color: Color.fromRGBO(255,220,128, 1.0)),
),
onPressed: () async {
widget.loginAction();
},
)
)
],
),
);
}
}