In basic authentication, user credentials are sent to a server in raw form or with basic encoding, typically in the body of a request
Follwing sample code is also basic authentication where request header is used to send credential pair:
Map<String, String> headers = new Map();
var temp = base64Encode('${userData.username}:${userData.password}'.codeUnits);
headers["Authorization"] = 'Basic ${temp}';
http.get('http://192.168.43.34:4500/users/getInfo', headers:headers).then((response){ });
It consists of a prefix Basic
(or some other word) and base64 encoding of username and password separated by colon (:)
Provided application requires a backend server which performs the authentication
Check Nodejs Basic Authentication server to implement such a server in Nodejs
Check Flutter installation to setup Flutter
Use flutter create
command to create a Flutter project (here nc_basic_auth :
flutter create nc_basic_auth
Add validate
and http
packages to pubspec.yaml
dependencies:
validate:
http: "0.12.1"
flutter:
sdk: flutter
Run following command to add dependency
$ flutter pub get
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
json.decode()
requires dart:convert
dart:async
for async
and await
keywords, http.dart
for http.post()
method
Example app provided below uses two StatefulWidget widgets LoginPage
and UserPage
,both of which involve sending http requests with user credentials
Login page renders two TextFormField widgets to take username and password from user and a button to initiate login
In login()
function, http.post()
method is used to send request with username and password as body of the request
void login() async {
final url = 'http://192.168.43.34:4500/users/authenticate';
await http.post(url, body: {'username': userData.email, 'password': base64Encode(userData.password.codeUnits)})
.then((response) {
Map<String, dynamic> responseMap = json.decode(response.body);
if(response.statusCode == 200) {
userData.addData(responseMap);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => UserPage(userData),),
);
}
else {
if(responseMap.containsKey("message"))
showDialog(context: context, builder: (BuildContext context) =>
getAlertDialog("Login failed", '${responseMap["message"]}', context));
}
}).catchError((err) {
showDialog(context: context, builder: (BuildContext context) =>
getAlertDialog("Login failed", '${err.toString()}', context));
});
}
The password is encoded to its base64 form using base64Encode()
method which takes a list of UTF-16 code units as input
This encoding is according to server implementation
The body of response is decoded to json format and assigned to a map
If response of server has status code 200 then app is navigated to UserPage
to which the map is passed
The userData object passed to this widget is used to create a basic authentication string which is the prefix Basic
concatenated to the base64 encoded form of username:password
This format can be considered as a convention and can be manipulated as per server implementation
@override
void initState() {
var temp = base64Encode('${userData.username}:${userData.password}'.codeUnits);
headers["Authorization"] = 'Basic ${temp}';
super.initState();
}
This string is assigned to the header Authorization
(for a map denoting request headers) which is added to requests sent to the server
Future<Map> getUserData() async {
Map<String, dynamic> responseMap;
final url = 'http://192.168.43.34:4500/users/getInfo';
await http.get(url, headers: headers)
.then((response) {
responseMap = json.decode(response.body);
if(response.statusCode == 200) {
responseMap = responseMap["userdata"];
}
else {
if(responseMap.containsKey("message"))
throw(Exception(responseMap["message"]));
}
})
.timeout(Duration(seconds:40),onTimeout: () {
throw(new TimeoutException("fetch from server timed out"));
})
.catchError((err) {
throw(err);
});
return responseMap;
}
Valid responses, based on status code of response, are rendered
import 'package:flutter/material.dart';
import 'package:validate/validate.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'dart:typed_data';
void main() => runApp(new MaterialApp(
title: 'Flutter Authentication App',
home: new LoginPage(),
));
AlertDialog getAlertDialog(title, content, context) {
return AlertDialog(
title: Text("Login failed"),
content: Text('${content}'),
actions: <Widget>[
FlatButton(
child: Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
class LoginPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _LoginPageState();
}
class _LoginData {
String email = '';
String password = '';
}
class UserData extends _LoginData {
String username = '';
int id;
void addData (Map<String, dynamic> responseMap) {
this.id = responseMap["id"];
this.username = responseMap["username"];
}
}
class _LoginPageState extends State<LoginPage> {
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
UserData userData = new UserData();
void submit() {
if (this._formKey.currentState.validate()) {
_formKey.currentState.save();
login();
}
}
void login() async {
final url = 'http://192.168.43.34:4500/users/authenticate';
await http.post(url, body: {'username': userData.email, 'password': base64Encode(userData.password.codeUnits)})
.then((response) {
Map<String, dynamic> responseMap = json.decode(response.body);
if(response.statusCode == 200) {
userData.addData(responseMap);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => UserPage(userData),),
);
}
else {
if(responseMap.containsKey("message"))
showDialog(context: context, builder: (BuildContext context) =>
getAlertDialog("Login failed", '${responseMap["message"]}', context));
}
}).catchError((err) {
showDialog(context: context, builder: (BuildContext context) =>
getAlertDialog("Login failed", '${err.toString()}', context));
});
}
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
return new Scaffold(
appBar: new AppBar(title: new Text('Login'),),
body: new Container(
padding: new EdgeInsets.all(20.0),
child: new Form(
key: this._formKey,
child: new ListView(
children: <Widget>[
new TextFormField(
decoration: new InputDecoration(hintText: 'ign', labelText: 'Username'),
onSaved: (String value) { this.userData.email = value; }
),
new TextFormField(
obscureText: true, // To display typed char with *
decoration: new InputDecoration(
hintText: 'Password',
labelText: 'Enter your password'
),
onSaved: (String value) { this.userData.password = value; }
),
new Container(
width: screenSize.width,
child: new RaisedButton(
child: new Text('Login', style: new TextStyle(color: Colors.white),),
onPressed: this.submit,
color: Colors.blue,
),
margin: new EdgeInsets.only(top: 20.0),
),
],
),
)
),
);
}
}
class UserPage extends StatefulWidget {
UserData userData;
UserPage(@required this.userData) : super(key: key);
@override
State<StatefulWidget> createState() => new _UserPageState(userData);
}
class _UserPageState extends State<UserPage> {
UserData userData;
Map<String, String> headers = new Map();
List<Widget> posts = new List();
_UserPageState(this.userData);
@override
void initState() {
var temp = base64Encode('${userData.username}:${userData.password}'.codeUnits);
headers["Authorization"] = 'Basic ${temp}';
super.initState();
}
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
return new Scaffold(
appBar: new AppBar(
title: new Text('User page'),
),
body: new SingleChildScrollView(
child: new Column(
children: [
FutureBuilder<Map>(
future: getUserData(), //sets getUserData method as the expected Future
builder: (context, snapshot) {
List<Widget> widgetList = List();
if (snapshot.hasData) { //checks if response returned valid data
widgetList = getUserInfo(snapshot.data);
}
else if (snapshot.hasError) { //checks if the response contains error
widgetList.add(Text("${snapshot.error}"));
}
else {
widgetList.add(getRowWithText("Id", "${userData.id}"));
widgetList.add(getRowWithText("Username", userData.username));
widgetList.add(CircularProgressIndicator());
}
return Container(
height: (screenSize.height-60) * 0.26,
color: Colors.blue[500],
padding: new EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children:widgetList
),
);
},
),
FutureBuilder<List>(
future: getUserPosts(), //sets getUserPosts method as the expected Future
builder: (context, snapshot) {
if (snapshot.hasData) {
return SingleChildScrollView(
child: Container(
height: (screenSize.height-60) * 0.65,
padding: new EdgeInsets.all(20.0),
child: getPosts(snapshot.data),
),
);
}
else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
],
),
),
);
}
Widget getPosts(List<dynamic> _posts) {
for(int i=0;i<_posts.length;i++) {
posts.add(getPostCard(_posts[i]));
}
return ListView.separated(
shrinkWrap:true,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: posts.length,
itemBuilder: (BuildContext context, int index) {
return posts[index];
},
separatorBuilder: (BuildContext context, int index) => const Divider(),
);
}
Widget getPostCard(post) {
return Card(
color: Colors.teal[300],
child: ListTile(
subtitle: Text(post),
),
);
}
Widget getTextContainer(text) {
return Container(
padding: EdgeInsets.only(left:5, right:5),
child: Text(text),
);
}
Widget getRowWithText(label, value) {
return Row(
children: <Widget>[
getTextContainer(label),
getTextContainer(value),
],
);
}
List<Widget> getUserInfo(map) {
return <Widget>[
Row(
children: <Widget>[
Column(
children: <Widget>[
Container(
width:100,
height:100,
child: Image.network(map["picture"]["medium"], fit: BoxFit.cover),
),
],
),
Expanded( child: Container(
height:100,
padding: EdgeInsets.only(left: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
getRowWithText("Id", '${map["id"]}'),
getRowWithText("Username", map["username"]),
getRowWithText("First name", map["name"]["first"]),
getRowWithText("Last name", map["name"]["last"]),
],
),
),),
],
),
getRowWithText("Phone Number", '${map["phone"]}'),
getRowWithText("Email", map["email"]),
];
}
Future<Map> getUserData() async {
Map<String, dynamic> responseMap;
final url = 'http://192.168.43.34:4500/users/getInfo';
await http.get(url, headers: headers)
.then((response) {
responseMap = json.decode(response.body);
if(response.statusCode == 200) {
responseMap = responseMap["userdata"];
}
else {
if(responseMap.containsKey("message"))
throw(Exception(responseMap["message"]));
}
})
.timeout(Duration(seconds:40),onTimeout: () {
throw(new TimeoutException("fetch from server timed out"));
})
.catchError((err) {
throw(err);
});
return responseMap;
}
Future<List> getUserPosts() async {
final url = 'http://192.168.43.34:4500/users/initialPosts';
Map<String, dynamic> responseMap;
await http.get(url, headers: headers)
.then((response) {
responseMap = json.decode(response.body);
if(response.statusCode == 200) {
if(!responseMap.containsKey("posts"))
throw(Exception('error while server fetch'));
}
else {
if(responseMap.containsKey("message"))
throw(Exception('${responseMap["message"]}'));
else
throw(Exception('error while server fetch'));
}
})
.timeout(Duration(seconds:40),onTimeout: () {
throw(new TimeoutException("fetch from server timed out"));
})
.catchError((err) {
throw(err);
});
return responseMap["posts"];
}
}
Ensure a supported device is connected or emulator/simulator is started
Go to project directory
Use flutter run
command to run
flutter run
It builds and runs app on an available android/ios device