Authenticating Flutter Application Using Laravel API and Passport

Godwin Asuquo
The Startup
Published in
7 min readFeb 7, 2020

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

ready to rock and roll

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

api.dart

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.

main.dart

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

login.dart

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:

register.dart

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.

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

Having issues? Do let me know.

--

--