Verifying one’s identity to gain access to particular resources within an application is known as authentication. This usually entails entering your username and password to log in. After the client sends these credentials, the server verifies the user’s identity by comparing them with its database.
Authentication with a username and password is a popular technique in web applications. This tutorial will explain how to use Strapi as the backend server to implement such authentication in Flutter.
Required conditions
To proceed with this article, the following information is useful:
- You have installed Node.js on your PC.
- Local development setup for Flutter.
- Installed on your computer is Postman.
- prior exposure to using Flutter.
Configuring Strapi CMS
Let’s use Strapi to set up an authentication server locally on your computer. The process of bootstrapping a server for API development is made easier with Strapi. To begin using Strapi CMS, go to the directory of your choice and run the following command:
@latest strapi-auth npx create-strapi-app
This command installs all required Strapi CMS dependencies and creates a new directory called strapi-auth. Navigate to the new directory to see whether the setup was successful:
CD strapi-auth
Next, start up the environment for development:
npm run develop
- You will be prompted to sign up or log in on the Strapi authentication page after a successful startup.
- You will be prompted to sign up or log in on the Strapi authentication page after a successful startup.
- Greetings from the Strapi page.
Note: After installation, Strapi might take you straight to your browser’s authentication dashboard. If that doesn’t work, just execute the previously mentioned commands.
Setting Up Post and User Content Types
We’ll use Flutter and Strapi to set up user authentication in this project. Users can access the application’s content after authenticating. We will pay particular attention to post data, which is the content that is accessible after authentication. To display user information and post data in the Flutter app, this calls for modeling both in Strapi.
To set these up in the Strapi dashboard:
- From the left pane, click the Content-Type Builder link.
- You can view and modify the fields for a user as follows on the page that appears:
Usually, the default fields are sufficient, but by selecting the Add another field button, you can add more.
- To prepare the content for a post:
- Go back to the section with the Content-Type Builder.
To begin a new data collection, click Create new collection type. Give this collection of posts a name:
Select the fields that are required for your posts. Within the context of this piece, we’ll utilize two:
String Title – Rich Text Description
This is how your configured fields for the posts should appear:
Obtaining an API token for Strapi
An API token is needed to safely access Strapi content from the outside. To create one, take these actions:
- Navigate to the Settings section in the left pane of the Strapi dashboard.
- On API Tokens, click.
- Hit the button labeled “Create new API Token.”
To create new users, fill out the Name and Description fields on the form and choose Full Access as the token type.
Once you leave the token generation panel, the generated API token cannot be recovered, so be sure to copy and save it right away.
Including Information in Posts
Using an API platform such as Postman, or the Strapi Content Manager, you can add data to your posts in Strapi.
Manager of Strapi Content
- Choose the post collection type by navigating to the Content Manager in the Strapi dashboard.
- Enter the post’s details by clicking the Create new entry button.
- Make sure you click the image.png button to add a new entry after entering the post’s details.
Making Use of Postman
Send a POST request to http://localhost:1337/api/posts after opening Postman.
Go to Postman’s Authorization section, choose Bearer Token, and input your API token to authenticate the request.
To add a new post, add the following payload, which is similar to JSON, to the request body:
Make sure the extra fields you added to the post’s collection type are included in the payload of your API request. Following the submission of the request, you ought to get something like this in response:
{
“data”: {
“id”: 5,
“attributes”: {
“title”: “Post five”,
“description”: “Dummy post five”,
“createdAt”: “2022-09-17T14:36:54.353Z”,
“updatedAt”: “2022-09-17T14:36:54.353Z”,
“publishedAt”: “2022-09-17T14:36:54.350Z”
}
},
“meta”: {}
}
You are welcome to make as many posts as you require. Now that our backend is completely set up, let’s install the Flutter application.
Configuring the Flutter program
Run the following command from the directory of your choice to start your Flutter project: flutter create flutter_auth_project.
Go to the freshly created Flutter project directory by using the command cd flutter_auth_project to verify that Flutter is operating as intended.
Next, carry out: Flutter run
Using this command, a simple Flutter application tailored for your testing setup—such as a mobile device or web browser—will open.
Linking Strapi and Flutter Together
Server endpoints and the Strapi API access token are two essential components for establishing a connection between Flutter and a Strapi server. By setting these up, we can enable server-side operations with Flutter acting as the client and make Strapi more accessible locally.
To begin, manage these settings within a.env file. Use the following command to add the Flutter Dotenv library to your application:
Add flutter_dotenv to flutter pub
Make a.env file in the root directory of your Flutter project and define your Strapi constants as follows:
usersEndpoint=/users; baseUrl=http://localhost:1337/api
postsEndpoint=/posts; accesToken=your_access_token
Make sure to substitute the real API token obtained from Strapi for your_access_token.
Next, add the.env file from your assets bundle to the pubspec.yaml file for your project:
One, two, three flutter: assets: -.env
We will make use of the HTTP package for HTTP requests to communicate with the Strapi backend. Use this command to install it:
Flutter Pub Add
You’re coming along quite nicely! Let’s begin putting the Flutter authentication into practice now.
Construct Models and Screens
Navigate to the flutter_auth_project directory after setting up. Inside the lib folder, we’ll create the following directories at this point:
models: To keep user and post models in storage.
screens: To contain various application screens, including dashboard, sign-up, and log in.
For server request functions, use utils.
Building Models
You must model the user details to interact with them. Create a user.dart by navigating to the lib/models directory. After that, make a user model like this:
import ‘dart:convert’;
// getting a list of users from json
List<User> UserFromJson(String str) =>
List<User>.from(json.decode(str).map((x) => User.fromJson(x)));
// getting a single user from json
User singleUserFromJson(String str) => User.fromJson(json.decode(str));
// user class
class User {
User({
required this.id,
required this.username,
required this.email,
required this.provider,
required this.confirmed,
required this.blocked,
required this.createdAt,
required this.updatedAt,
});
int id;
String username;
String email;
String provider;
bool confirmed;
bool blocked;
DateTime createdAt;
DateTime updatedAt;
factory User.fromJson(Map<String, dynamic> json) => User(
id: json[“id”],
username: json[“username”],
email: json[“email”],
provider: json[“provider”],
confirmed: json[“confirmed”],
blocked: json[“blocked”],
createdAt: DateTime.parse(json[“createdAt”]),
updatedAt: DateTime.parse(json[“updatedAt”]),
);
}
Make a model to show the post data similarly. As such, within the create a post.dart file:
List<Post> postFromJson(List<dynamic> post) =>
List<Post>.from(post.map((x) => Post.fromJson(x)));
class Post {
Post({
required this.title,
required this.description,
required this.createdAt,
required this.updatedAt,
required this.id,
});
String title;
DateTime createdAt;
String description;
int id;
DateTime updatedAt;
factory Post.fromJson(Map<dynamic, dynamic> json) {
return Post(
title: json[‘attributes’][‘title’],
description: json[‘attributes’][‘description’],
createdAt: DateTime.parse(json[‘attributes’][‘createdAt’]),
id: json[‘id’],
updatedAt: DateTime.parse(json[‘attributes’][‘updated]));
}
}
Configure the Flutter server.
Flutter needs a way to get and handle the defined user and post models via Strapi to use them. The Flutter HTTP package is used to facilitate server communication to accomplish this. Utilize Strapi’s Authorization headers for secure data handling when implementing these models, which are based on the API endpoints.
To begin with, make a server.dart file in the lib/utils folder. To manage server communications, add this code:
But first, make the following file, server.dart, in the lib/utils directory:
import ‘dart:convert’;
import ‘dart:developer’;
import ‘package:http/http.dart’ as http;
import ‘package:flutter_dotenv/flutter_dotenv.dart’;
import ‘package:flutter_auth_project/models/user.dart’;
import ‘package:flutter_auth_project/models/post.dart’;
class ApiService {
// Getting users
Future<List<User>?> getUsers() async {
try {
var url = Uri.parse(dotenv.get(‘baseUrl’) + dotenv.get(‘usersEndpoint’));
var response = await http.get(url,
headers: {“Authorization”: “Bearer ${dotenv.get(‘accesToken’)}”});
if (response.statusCode == 200) {
List<User> _model = UserFromJson(response.body);
return _model;
} else {
String error = jsonDecode(response.body)[‘error’][‘message’];
throw Exception(error);
}
} catch (e) {
log(e.toString());
}
}
// Adding user
Future<User?> addUser(String email, String username, String password) async {
try {
var url = Uri.parse(dotenv.get(‘baseUrl’) + dotenv.get(‘usersEndpoint’));
var response = await http.post(url,
headers: {“Authorization”: “Bearer ${dotenv.get(‘accesToken’)}”},
body: {“email”: email, “username”: username, “password”: password});
if (response.statusCode == 201) {
User _model = singleUserFromJson(response.body);
return _model;
} else {
String error = jsonDecode(response.body)[‘error’][‘message’];
throw Exception(error);
}
} catch (e) {
throw Exception(e);
}
}
// Getting posts
Future<List<Post>?> getPosts() async {
try {
var url = Uri.parse(dotenv.get(‘baseUrl’) + dotenv.get(‘postsEndpoint’));
var response = await http.get(url,
headers: {“Authorization”: “Bearer ${dotenv.get(‘accesToken’)}”});
if (response.statusCode == 200) {
var _model = postFromJson(jsonDecode(response.body)[‘data’]);
return _model;
} else {
throw Exception(jsonDecode(response.body)[“error”][“message”]);
}
} catch (e) {
throw Exception(e);
}
}
}
Verifying Users: Enrolling Users
When registering or creating an account, users must select and enter their information. Now let’s get started by creating a form that will be used to submit the authentication data. This will make it simple to create any frontend component using material components.
Make a signup_screen.dart file by navigating to the lib/screens directory. Next, include the subsequent code in this file:
Include the necessary modules and dependencies:
import ‘package:flutter/material.dart’;
import ‘package:flutter_auth_project/screens/login_screen.dart’;
import ‘package:flutter_auth_project/models/user.dart’;
import ‘package:flutter_auth_project/utils/server.dart’;
import ‘package:flutter_auth_project/screens/dashboard_screen.dart’;
In this case, creating the form elements requires the Material library. The user models determine what information the user must provide. The user endpoint is then executed by the server.
Also, Take Note: There are two screens in these imports:
- A screen dashboard
- A screen for login
In this guide, we will create them later.
Make a Stateful Signup Widget by doing the following:
class Signup extends StatefulWidget {
static const namedRoute = “signup-screen”;
const Signup({Key? key}) : super(key: key);
}
Make the following state for user registration information inside Signup:
@override
State<Signup> createState() => _SignupState();
Utilizing _SignupState, carry out the following states:
class _SignupState extends State<Signup> {
String _username = “”;
String _email = “”;
String _password = “”;
String _error = “”;
void _signup() async {
try {
User? createduser =
await ApiService().addUser(_email, _username, _password);
if (createduser != null) {
// navigate to the dashboard.
Navigator.pushNamed(context, Dashboard.namedRoute);
}
} on Exception catch (e) {
setState(() {
_error = e.toString().substring(11);
});
}
}
}
As stated above:
An addUser() function from the server is executed using the async function. To send a request body containing user data, this will execute the user endpoint via the POST HTTP method. These consist of email, password, and username.
The user is redirected to the dashboard screen if the values are valid and an account is created; otherwise, an error message is displayed.
To send these details to the Strapi server, let’s now create a form. Add your widget that generated the user signup screen inside the_SignupState class in the following manner:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(‘Create Account’),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 60.0),
child: Center(
child: Column(
children: const [
SizedBox(
height: 150,
),
Text(“Strapi App”),
SizedBox(
height: 20,
)
],
),
)),
if (_error.isNotEmpty)
Column(
children: [
Text(
_error,
style: const TextStyle(color: Colors.red),
),
const SizedBox(
height: 10,
)
],
),
Padding(
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
padding: const EdgeInsets.symmetric(horizontal: 15),
child: TextField(
onChanged: (value) {
setState(() {
_username = value;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: ‘Username’,
hintText: ‘Enter valid username e.g. Paul’),
),
),
const SizedBox(height: 15),
Padding(
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
padding: const EdgeInsets.symmetric(horizontal: 15),
child: TextField(
onChanged: (value) {
setState(() {
_email = value;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: ‘Email’,
hintText: ‘Enter valid email id as abc@gmail.com’),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextField(
obscureText: true,
onChanged: (value) {
setState(() {
_password = value;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: ‘Password’,
hintText: ‘Enter secure password’),
),
),
const SizedBox(height: 15),
Container(
height: 50,
width: 180,
decoration: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(20)),
// ignore: deprecated_member_use
child: TextButton(
onPressed: () => _signup(),
child: const Text(
‘Create Account’,
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
const SizedBox(
height: 30,
),
// ignore: deprecated_member_use
TextButton(
onPressed: () {
// navigate to the signup screen
Navigator.push(
context, MaterialPageRoute(builder: (_) => Login()));
},
child: const Text(
‘Already have an account? Login’,
style: TextStyle(fontSize: 16),
),
),
],
),
)
);
}
As stated above:
- We have an email form with fields for a username and password.
- Values are sent to Strapi after the form is submitted.
The user is taken to the dashboard screen if the values are valid and an account is created. Now let’s get started and make this screen.
Construct a Dashboard Display
Only those users who have successfully registered or logged in will see this page. This is where we will show the list of posts that we previously made using Strapi.
Create a dashboard_screen.dart file by navigating to the lib/screens directory. Next Get the post’s data in this way:
Bring in the:
- To construct the screen elements, use Material UI.
- Post structure model for posts and,
- For the server to carry out the post get method
import ‘package:flutter/material.dart’;
import ‘package:flutter_auth_project/models/post.dart’;
import ‘package:flutter_auth_project/utils/server.dart’;
Make sure to create a Dashboard StatefulWidget and set the post information’s state.
class Dashboard extends StatefulWidget {
static const namedRoute = “dashboard-screen”;
const Dashboard({Key? key}) : super(key: key);
@override
State<Dashboard> createState() => _DashboardState();
}
Utilizing _SignupState, carry out the following states:
class _DashboardState extends State<Dashboard> {
}
Initially, obtain the Strapi list of saved posts:
List<Post> _posts = [];
String _error = “”;
@override
void initState() {
super.initState();
_getData();
}
void _getData() async {
try {
_posts = (await ApiService().getPosts())!;
Future.delayed(const Duration(seconds: 1)).then((value) => setState(() {
_posts = _posts;
_error = “”;
}));
} on Exception catch (e) {
_error = e.toString();
}
}
The Widget to show these posts was then created:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(‘Dashboard’),
),
body: _posts.isEmpty
? const Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: _posts.length,
itemBuilder: (BuildContext ctx, int index) {
Post data = _posts[index];
return ListTile(
contentPadding: const EdgeInsets.all(5.0),
title: Text(data.title),
subtitle: Text(data.description),
leading: CircleAvatar(
backgroundColor: Colors.grey,
child: Text((data.id).toString()),
),
);
}
),
);
}
All we’re doing is pulling past posts from this screen and showing them to the authenticated user.
After registering, you’ll be redirected to the dashboard screen.
The user must log in to verify their identity. Create the login_screen.dart file in the lib/screens directory as indicated below.
import ‘package:flutter/material.dart’;
import ‘package:flutter_auth_project/screens/signup_screen.dart’;
import ‘package:flutter_auth_project/screens/dashboard_screen.dart’;
import ‘package:flutter_auth_project/utils/server.dart’;
import ‘package:flutter_auth_project/models/user.dart’;
class Login extends StatefulWidget {
static const namedRoute = “login-screen”;
const Login({Key? key}) : super(key: key);
@override
State<Login> createState() => _LoginState();
}
class _LoginState extends State<Login> {
String _email = “”;
String _password = “”;
String _error = “”;
void _login() async {
try {
List<User> users = (await ApiService().getUsers())!;
late User? loggedInUser;
if (users.isNotEmpty) {
for (var i = 0; i < users.length; i++) {
if (users[i].email == _email) {
loggedInUser = users[i];
break;
}
}
}
if (loggedInUser == null) {
setState(() {
_error = “Your account does not exist.”;
});
}
else {
// navigate to the dashboard screen.
Navigator.pushNamed(context, Dashboard.namedRoute);
}
} on Exception catch (e) {
setState(() {
_error = e.toString().substring(11);
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(‘Login’),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 60.0),
child: Center(
child: Column(
children: const [
SizedBox(
height: 150,
),
Text(“Strapi App”),
SizedBox(
height: 20,
)
],),
)),
if (_error.isNotEmpty)
Column(
children: [
Text(
_error,
style: const TextStyle(color: Colors.red),
),
const SizedBox(
height: 10,
)
],
),
Padding(
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
padding: const EdgeInsets.symmetric(horizontal: 15),
child: TextField(
onChanged: (value) {
setState(() {
_email = value;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: ‘Email’,
hintText: ‘Enter valid email id as abc@gmail.com’),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextField(
obscureText: true,
onChanged: (value) {
setState(() {
_password = value;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: ‘Password’,
hintText: ‘Enter secure password’),
),
),
const SizedBox(
height: 50,
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(5)),
// ignore: deprecated_member_use
child: TextButton(
onPressed: () => _login(),
child: const Text(
‘Login’,
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
const SizedBox(
height: 130,
),
// ignore: deprecated_member_use
TextButton(
onPressed: () {
// navigate to the signup screen
Navigator.push(
context, MaterialPageRoute(builder: (_) => Signup()));
},
child: const Text(
‘New user? Create Account’,
style: TextStyle(fontSize: 14),
),
),
],
),
));
}
}
From above:
- Both an email address and a password field are present.
When the form is submitted, the application will confirm that the sent email is linked to a user. If the user is present, you will be redirected to the dashboard page; if not, an error message will be displayed.
Assembling the screens
Make the following changes to the main.dart file to prevent running every screen:
import ‘package:flutter/material.dart’;
import ‘package:flutter_auth_project/screens/dashboard_screen.dart’;
import ‘package:flutter_auth_project/screens/login_screen.dart’;
import ‘package:flutter_auth_project/screens/signup_screen.dart’;
void main() {
runApp(const Home());
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: “Strapi App”,
home: const Login(),
routes: {
Dashboard.namedRoute: (ctx) => const Dashboard(),
Login.namedRoute: (ctx) => const Login(),
Signup.namedRoute: (ctx) => const Signup()
},
onGenerateRoute: (settings) {
return MaterialPageRoute(builder: (context) => const Dashboard());
},
onUnknownRoute: (settings) {
return MaterialPageRoute(builder: (context) => const Dashboard());
},
);
}
}
As seen from above, we are:
- establishing the default screen as the login screen.
- establishing the paths for every screen.
- establishing a handler if it is sent to an invalid route.
- Trying out the app
Conclusion
For a safe and effective mobile app, integrating Flutter with Strapi CMS for authentication is a wise decision. To improve user experience and easily streamline authentication procedures, adhere to our step-by-step instructions.
If you’re looking for a Strapi development business to help you create an application using Strapi, another company you should look into is Appic Softwares. Through a range of projects, our talented team of Strapi developers has helped clients all over the world create Strapi apps.
Why then are you hesitant?