Now that our authentication is completed, we can work on consuming API tokens within our application. Tokens are important to limit access to our application.
In this lesson, we will:
- Store the token in our Auth Provider
- Add Headers to our API requests - including the token
- Refactor our API Service to use the token
- And some other minor changes
Let's start by refactoring our API service to use the token.
Refactoring API Service - Consuming API Tokens
Currently, we have no way to pass the token to our API service, so let's add that:
Note: We will declare the token as a [non-nullable variable] using the late
keyword (https://dart.dev/language/variables#late-variables).
lib/services/api.dart
class ApiService { ApiService(); late String token; ApiService(String token) { this.token = token; }}
Next, we need to use that token by adding headers to our API requests:
lib/services/api.dart
// ... Future<List<Category>> fetchCategories() async { final http.Response response = await http.get(Uri.parse('$baseUrl/api/categories')); final http.Response response = await http .get(Uri.parse('$baseUrl/api/categories'), headers: <String, String>{ 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer $token' }); // ...} Future saveCategory(Category category) async { String url = '$baseUrl/api/categories/${category.id}'; final http.Response response = await http.put( Uri.parse(url), headers: <String, String>{ 'Content-Type': 'application/json; charset=UTF-8', 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer $token' }, body: jsonEncode(<String, String>{ 'name': category.name, }), ); // ...} Future<void> deleteCategory(id) async { String url = '$baseUrl/api/categories/$id'; final http.Response response = await http.delete( Uri.parse(url), ); final http.Response response = await http.delete(Uri.parse(url), headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer $token' }); // ...} Future addCategory(String name) async { String url = '$baseUrl/api/categories'; final http.Response response = await http.post( Uri.parse(url), headers: <String, String>{ 'Content-Type': 'application/json; charset=UTF-8', 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer $token' }, body: jsonEncode(<String, String>{ 'name': name, }), ); // ...} // ...
Updating Category Provider
Now that we have the token in our API service, we can update our Category Provider to use the token:
import 'package:laravel_api_flutter_app/providers/auth_provider.dart'; // ... class CategoryProvider extends ChangeNotifier { List<Category> categories = []; late ApiService apiService; late AuthProvider authProvider; CategoryProvider() {// apiService = ApiService(); CategoryProvider(AuthProvider authProvider) { this.authProvider = authProvider; apiService = ApiService(authProvider.token); init(); } // ...}
Updating Registration and Login
We have to fix one thing that we left out - we are storing the token
as a response from our Login/Register functions:
lib/screens/auth/Login.dart
// ... try { String token = await provider.login( await provider.login( emailController.text, passwordController.text, deviceName);} catch (Exception) { // ...
And:
lib/screens/auth/Register.dart
// ... try { String token = await provider.register( await provider.register( nameController.text, emailController.text, // ...
While this might work even without the changes, it is better to store the token in the AuthProvider and use it from there.
Removing API Service From Add/Edit Category
We have some redundant code on our Add/Edit Category pages, as we did with Auth pages. We can remove the API service from there:
lib/widgets/category_add.dart
import 'package:laravel_api_flutter_app/services/api.dart'; // ... class CategoryAddState extends State<CategoryAdd> { final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final categoryNameController = TextEditingController(); ApiService apiService = ApiService(); String errorMessage = ''; // ...
And:
lib/widgets/category_edit.dart
import 'package:laravel_api_flutter_app/services/api.dart'; // ... class CategoryEditState extends State<CategoryEdit> { final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final categoryNameController = TextEditingController(); ApiService apiService = ApiService(); String errorMessage = ''; // ...
Modifying How Auth Provider is Registered
The last change we have to do is to modify how we register our AuthProvider in our main.dart
file:
// ...return MultiProvider( providers: [ ChangeNotifierProvider<CategoryProvider>( create: (context) => CategoryProvider()), create: (context) => CategoryProvider(authProvider)), ChangeNotifierProvider<AuthProvider>( create: (context) => AuthProvider()), ], child: MaterialApp(title: 'Welcome to Flutter', routes: { '/': (context) { final authProvider = Provider.of<AuthProvider>(context); return authProvider.isAuthenticated ? Home() : Login(); return authProvider.isAuthenticated ? CategoriesList() : Login(); },
Laravel Changes
We have to make some changes to our Laravel application to make it work with API tokens. First, we need to add the sanctum
middleware to our API routes:
routes/api.php
// ... Route::group([], function () {Route::group(['middleware' => 'auth:sanctum'], function () { //...
And restore the CategoryController
create()
method:
app/Http/Controllers/CategoryController.php
// ... $category = Category::create($request->validated());$category = auth()->user()->categories()->create($request->validated()); // ...
That's it - we should be able to log in normally and use the API token to access our API.
In the next lesson, we will add a bottom navigation bar to navigate between multiple pages like Categories, Transactions, and Home.
Check out the GitHub Commit for this lesson.
I found some errors:
lib/services/api.dart
in the methodFuture saveCategory(Category category)
theheaders
was added in thebody
.lib/providers/auth_provider.dart
I've updated the
saveCategory
method to be correct, thanks!As for the changes - they did not impact the code at this point (for example, the removal of
return
has 0 impact at this current stage) :)