0% found this document useful (0 votes)
183 views28 pages

Authenticating Flutter Application Using Laravel API and Passport

This document provides instructions for authenticating a Flutter application using a Laravel backend API with Passport authentication. It discusses setting up the Laravel backend to support authentication via Passport, creating the Flutter app and necessary packages/files, implementing API calls to Laravel endpoints for login and getting user data, and checking authentication state on app launch to show either the login or home screen.

Uploaded by

Elsadig Osman
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
Download as docx, pdf, or txt
0% found this document useful (0 votes)
183 views28 pages

Authenticating Flutter Application Using Laravel API and Passport

This document provides instructions for authenticating a Flutter application using a Laravel backend API with Passport authentication. It discusses setting up the Laravel backend to support authentication via Passport, creating the Flutter app and necessary packages/files, implementing API calls to Laravel endpoints for login and getting user data, and checking authentication state on app launch to show either the login or home screen.

Uploaded by

Elsadig Osman
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1/ 28

Authenticating Flutter

Application Using Laravel


API and Passport
In this article, I will be walking you through basic authentication
for your flutter application using a Laravel Backend.

Prerequisite
1. Know how to set up a laravel environment,
2. Have a flutter environment setup
3. Know how to create a flutter starter app
4. Read and complete the tutorial Laravel 6 API
Authentication with Laravel Passport. It will help
in setting up the Backend Service used for this tutorial.
5. Complete the tutorial on No 4. and keep the Laravel
server running

why flutter?

Flutter is Google’s UI toolkit for building beautiful, natively


compiled applications for mobile, web, and desktop from a
single codebase.
See my presentation, explaining the nativity of
flutter https://speakerdeck.com/godilite/the-nativity-of-flutter

First, let’s be on the same page with our Laravel Endpoints.

Setup Laravel backend


Take few minutes, read this detailed article I wrote specially for
building and authenticating Laravel Rest API with Passport. Once
you are ready, move to the next step.

Let’s Flutter on Dart


Create a Flutter application
You can now create a fresh flutter application, of course, you can
use any code editor as recommended by flutter.

Install Flutter packages


We will need the following flutter packages:

 http package, to consume HTTP resources.


 shared_preferences package, providing a persistent
store for simple data.

Add the packages to your  pubspec.yaml file


dependencies:
shared_preferences: ^0.5.6+1
http: ^0.12.0+4

Run the command  flutter pub get

Note: You could also choose to handle data storage


with SQLite or Hive packages especially for large data
structures.

Folder structure
Create your folder structure like so:

Folder Structure

Within the lib directory, create two folders:


 network_utils and

 screen respectively.

In screen directory, create login.dart, home.dart,


register.dart and createapi.dart file in network_utils folder.

network_utils/api.dart

In the api.dart file, write a Network class that manages all API


methods
import
'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';

class Network{
final String _url = 'http://localhost:8000/api/v1';
//if you are using android studio emulator, change localhost to 10.0.2.2
var token;

_getToken() async {
SharedPreferences localStorage = await
SharedPreferences.getInstance();
token = jsonDecode(localStorage.getString('token'))['token'];
}

authData(data, apiUrl) async {


var fullUrl = _url + apiUrl;
return await http.post(
fullUrl,
body: jsonEncode(data),
headers: _setHeaders()
);
}
getData(apiUrl) async {
var fullUrl = _url + apiUrl;
await _getToken();
return await http.get(
fullUrl,
headers: _setHeaders()
);
}

_setHeaders() => {
'Content-type' : 'application/json',
'Accept' : 'application/json',
'Authorization' : 'Bearer $token'
};

Within the Network class, define String _url =


'http://localhost:8000/api/v1 this is basically the BASE_URL for

our Laravel API.

If you are using an android emulator then


change  localhost:8000 to  10.0.2.2:8000

Also, define a variable token, this will store API token for


authentication and would be attached to every request that
requires authentication.

Next, we write an asynchronous function  _getToken


_getToken()async{
SharedPreferences localStorage = await
SharedPreferences.getInstance();
token = jsonDecode(localStorage.getString('token'))['token'];
}

This function checks for token stored in the user device and
assigns it to the initially defined String token;

We used SharedPreferences to allow us to get an instance of the


device's localStorage where we use the _getString method to retrieve a
key token stored during login or registration. Wrap this method
with jsonDecode to convert the key/value pair retrieved into an array.
This way, we can access the value ['token'] .

The authData() function is an asynchronous function that handles


all postrequest to our login and register API Endpoint, it takes
two arguments, data and apiUrl .

We build a fullUrl by appending the baseUrl _url and


the apiUrl passed to the function, the http.post() method takes
the fullUrl, body, and headers to make the POST request.
The authData function returns the response from this request.

You may have observed that for the headers parameter, we


called _setHeaders() where pass in our headers like so:
_setHeaders() => {
'Content-type' : 'application/json',
'Accept' : 'application/json',
'Authorization' : 'Bearer $token'
};
This makes it reusable and cleaner since we would have to call it
here and there while making requests.

Next, getData() function, this is also an asyncfunction that takes an


argument apiUrl , you can add more arguments say  data if your
query requires passing a set of parameters for instance. For the
purpose of this article, our  GET request takes no extra
parameters.

In the getData() function, we build the fullUrl similar to


the postData and call the _getToken() to ensure that tokenis set
otherwise it will be null, and our endpoint will
return unauthorized we didn’t do this for the authData() method
because the register and login routes do not require
authentication.

The http.get() method helps in sending our GETrequest, here,


no body parameter is passed.

main.dart

This is the main entry point to our application, here we will check
if the user is authenticated and return either the login screen or
the home screen.
import
'package:flutter/material.dart';
import 'package:tutorial_app/screen/login.dart';
import 'package:tutorial_app/screen/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {


// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
debugShowCheckedModeBanner: false,
home: CheckAuth(),
);
}
}

class CheckAuth extends StatefulWidget {


@override
_CheckAuthState createState() => _CheckAuthState();
}

class _CheckAuthState extends State<CheckAuth> {


bool isAuth = false;
@override
void initState() {
_checkIfLoggedIn();
super.initState();
}

void _checkIfLoggedIn() async{


SharedPreferences localStorage = await
SharedPreferences.getInstance();
var token = localStorage.getString('token');
if(token != null){
setState(() {
isAuth = true;
});
}
}
@override
Widget build(BuildContext context) {
Widget child;
if (isAuth) {
child = Home();
} else {
child = Login();
}
return Scaffold(
body: child,
);
}
}

Within the _CheckAuthState we define a boolean isAuth = false; in


the initState() we call _checkIfLoggedIn this method
checks localStorage for availability of token and sets isAuth = true like
so:
void _checkIfLoggedIn() async{
SharedPreferences localStorage = await
SharedPreferences.getInstance();
var token = localStorage.getString('token');
if(token != null){
setState(() {
isAuth = true;
});
}
}

Lastly, in the build method, we check if isAuth = true  and


return Home() else Login()

We are here, if you’ve got no errors so far,


Lets Register and Login.

login.dart
import
'dart:convert';
import 'package:flutter/material.dart';
import 'package:tutorial_app/network_utils/api.dart';
import 'package:tutorial_app/screen/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tutorial_app/screen/register.dart';
class Login extends StatefulWidget {
@override
_LoginState createState() => _LoginState();
}

class _LoginState extends State<Login> {


bool _isLoading = false;
final _formKey = GlobalKey<FormState>();
var email;
var password;
final _scaffoldKey = GlobalKey<ScaffoldState>();
_showMsg(msg) {
final snackBar = SnackBar(
content: Text(msg),
action: SnackBarAction(
label: 'Close',
onPressed: () {
// Some code to undo the change!
},
),
);
_scaffoldKey.currentState.showSnackBar(snackBar);
}
@override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Scaffold(
key: _scaffoldKey,
body: Container(
color: Colors.teal,
child: Stack(
children: <Widget>[
Positioned(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Card(
elevation: 4.0,
color: Colors.white,
margin: EdgeInsets.only(left: 20, right: 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[

TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.email,
color: Colors.grey,
),
hintText: "Email",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (emailValue) {
if (emailValue.isEmpty) {
return 'Please enter email';
}
email = emailValue;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
obscureText: true,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.vpn_key,
color: Colors.grey,
),
hintText: "Password",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (passwordValue) {
if (passwordValue.isEmpty) {
return 'Please enter some text';
}
password = passwordValue;
return null;
},
),
Padding(
padding: const EdgeInsets.all(10.0),
child: FlatButton(
child: Padding(
padding: EdgeInsets.only(
top: 8, bottom: 8, left: 10, right:
10),
child: Text(
_isLoading? 'Proccessing...' : 'Login',
textDirection: TextDirection.ltr,
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),
),
),
color: Colors.teal,
disabledColor: Colors.grey,
shape: new RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(20.0)),
onPressed: () {
if (_formKey.currentState.validate()) {
_login();
}
},
),
),
],
),
),
),
),

Padding(
padding: const EdgeInsets.only(top: 20),
child: InkWell(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Register()));
},
child: Text(
'Create new Account',
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),
),
),
),
],
),
),
)
],
),
),
);
}
void _login() async{
setState(() {
_isLoading = true;
});
var data = {
'email' : email,
'password' : password
};

var res = await Network().authData(data, '/login');


var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.setString('token', json.encode(body['token']));
localStorage.setString('user', json.encode(body['user']));
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Home()
),
);
}else{
_showMsg(body['message']);
}

setState(() {
_isLoading = false;
});

}
}

In the Login class, we start by building a basic login form with


input validation using the Form Widget.
you can copy and paste from the code snippet above or build a
form for yourself. I will explain a few necessary parts of the code.

Define a Boolean _isLoading = false; we’ll use this to change the state


of the login button to processing...

Also, define email and password variables, set their values from their


respective form fields after validation. we’ll define a _formKey and
a _scaffoldKey as follows
final _formKey = GlobalKey<FormState>();This uniquely identifies
the Form, and allows validation of the form in a later step.final
_scaffoldKey = GlobalKey<ScaffoldState>();Assign a GlobalKey to the
Scaffold, then use the key.currentState property to obtain the
ScaffoldState for a snackBar later.

Now, just below the build widget, we write the


async _login() method like so:
void _login() async{
setState(() {
_isLoading = true;
});
var data = {
'email' : email,
'password' : password
};

var res = await Network().authData(data, '/login');


var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await
SharedPreferences.getInstance();
localStorage.setString('token', json.encode(body['token']));
localStorage.setString('user', json.encode(body['user']));
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Home()
),
);
}else{
_showMsg(body['message']);
}

setState(() {
_isLoading = false;
});

We set the  _isLoading = true to change the  login button


to  processing...

Collect the form values and build a data Object which will be pass
to our API call.

Define a var res = await Network().authData(data, '/login)

Here, we make an API call to authData() method which we initially


created at api.dart file within the Network class, remember it takes
two parameters right? Hence, we pass in our data and API
route  '/login’ .

Once there is a response from our backend service, it will be


stored in the  res variable, we then decode this
JSON  res.body into  var body = json.decode(res.body)

Using an if conditional statement, we check for body['success'] this


is a Boolean from our API, if it's true, then we have successfully
authenticated into our application, hence, we set localStoragevalues
for token and user using SharedPreferences as shown in the
snippets. And using Navigator.push we navigate to the Home() route.

Else, if there is an error from our endpoint, we call


the _showMsg() and pass in the message body['message'] this triggers
a snackBar notification with the error message.

And Lastly, we set the _isLoading = false  .

register.dart

The register.dart is similar to login.dart, significant change is


in the number of form fields, and in the API endpoint '/register' as
seen below:
import
'dart:convert';
import 'package:flutter/material.dart';
import 'package:tutorial_app/network_utils/api.dart';
import 'package:tutorial_app/screen/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tutorial_app/screen/login.dart';

class Register extends StatefulWidget {


@override
_RegisterState createState() => _RegisterState();
}

class _RegisterState extends State<Register> {


bool _isLoading = false;
final _formKey = GlobalKey<FormState>();
var email;
var password;
var fname;
var lname;
var phone;
@override
Widget build(BuildContext context) {
return Material(
child: Container(
color: Colors.teal,
child: Stack(
children: <Widget>[
Positioned(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Card(
elevation: 4.0,
color: Colors.white,
margin: EdgeInsets.only(left: 20, right: 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[

TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.email,
color: Colors.grey,
),
hintText: "Email",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (emailValue) {
if (emailValue.isEmpty) {
return 'Please enter email';
}
email = emailValue;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.insert_emoticon,
color: Colors.grey,
),
hintText: "First Name",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (firstname) {
if (firstname.isEmpty) {
return 'Please enter your first name';
}
fname = firstname;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.insert_emoticon,
color: Colors.grey,
),
hintText: "Last Name",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (lastname) {
if (lastname.isEmpty) {
return 'Please enter your last name';
}
lname = lastname;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.phone,
color: Colors.grey,
),
hintText: "Phone",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (phonenumber) {
if (phonenumber.isEmpty) {
return 'Please enter phone number';
}
phone = phonenumber;
return null;
},
),
TextFormField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
keyboardType: TextInputType.text,
obscureText: true,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.vpn_key,
color: Colors.grey,
),
hintText: "Password",
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
validator: (passwordValue) {
if (passwordValue.isEmpty) {
return 'Please enter some text';
}
password = passwordValue;
return null;
},
),
Padding(
padding: const EdgeInsets.all(10.0),
child: FlatButton(
child: Padding(
padding: EdgeInsets.only(
top: 8, bottom: 8, left: 10, right:
10),
child: Text(
_isLoading? 'Proccessing...' :
'Register',
textDirection: TextDirection.ltr,
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),
),
),
color: Colors.teal,
disabledColor: Colors.grey,
shape: new RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(20.0)),
onPressed: () {
if (_formKey.currentState.validate()) {
_register();
}
},
),
),
],
),
),
),
),

Padding(
padding: const EdgeInsets.only(top: 20),
child: InkWell(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Login()));
},
child: Text(
'Already Have an Account',
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),
),
),
),
],
),
),
)
],
),
),
);
}
void _register()async{
setState(() {
_isLoading = true;
});
var data = {
'email' : email,
'password': password,
'phone': phone,
'fname': fname,
'lname': lname
};

var res = await Network().authData(data, '/register');


var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.setString('token', json.encode(body['token']));
localStorage.setString('user', json.encode(body['user']));
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Home()
),
);
}

setState(() {
_isLoading = false;
});
}
}

Take a minute and go through the async_register() method, then we


move!

lib/main.dart
Here, we will retrieve user data stored in localStorage, and also
implement logout action.
import
'dart:convert';
import 'package:flutter/material.dart';
import 'package:tutorial_app/screen/login.dart';
import 'package:tutorial_app/network_utils/api.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}

class _HomeState extends State<Home>{


String name;
@override
void initState(){
_loadUserData();
super.initState();
}
_loadUserData() async{
SharedPreferences localStorage = await SharedPreferences.getInstance();
var user = jsonDecode(localStorage.getString('user'));

if(user != null) {
setState(() {
name = user['fname'];
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test App'),
backgroundColor: Colors.teal,
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Hi, $name',
style: TextStyle(
fontWeight: FontWeight.bold
),
),
Center(
child: RaisedButton(
elevation: 10,
onPressed: (){
logout();
},
color: Colors.teal,
shape: RoundedRectangleBorder(borderRadius:
BorderRadius.all(Radius.circular(10))),
child: Text('Logout'),
),
),
],
),
),
);
}

void logout() async{


var res = await Network().getData('/logout');
var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.remove('user');
localStorage.remove('token');
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>Login()));
}
}
}
home.dart
At the  initState() method, we call  _loadUserData which returns
the user data stored in localStorage during register/login,
_loadUserData() async{
SharedPreferences localStorage = await
SharedPreferences.getInstance();
var user = jsonDecode(localStorage.getString('user'));

if(user != null) {
setState(() {
name = user['fname'];
});
}
}

You can set this user data to any variable declared to hold user
data, I set  user['fname'] to a String  name , this is later used in
the  Text widget on line 40,
Text('Hi, $name',
style: TextStyle(
fontWeight: FontWeight.bold
),
),

Next, we create a logout button, using a  RaisedButton widget,


and  onPressed we call the  logout() method.
void logout() async{
var res = await Network().getData('/logout');
var body = json.decode(res.body);
if(body['success']){
SharedPreferences localStorage = await
SharedPreferences.getInstance();
localStorage.remove('user');
localStorage.remove('token');
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>Login()));
}
}
The logout method calls the getData method of our Network class and
passes the apiUrl  /logout once we receive a success response,
indicating that the token has been invalidated by our backend
passport service, we call .remove() method on
our localStorage which is an instance of SharedPreferences, we
pass in the token key and do same for user and Navigate to
the Login() screen.

Run application
Run your application on an emulator device or host your laravel
backend on heroku to properly test from an external
device. Ensure edit your base URL in any case.

If you’ve successfully reached this point, I want to say,

Thank You! and Well Done!


Here is my git repository for the flutter application
https://medium.com/swlh/authenticating-flutter-application-with-laravel-api-caea30abd57

You might also like