Implementing Auth using AWS Amplify in Flutter
In this article, we are going to use AWS Amplify for Authentication. We will auth via email and use provider Goggle for Social Auth for a demo app.
If you’re looking for the best flutter app development company for your mobile application then feel free to contact us at — support@flutterdevs.com.
Table of Content
Auth with Social Provider(Google)
Authentication
Authentication helps personalize the experience with user-specific information when we develop an application. It’s also a crucial component for privacy and security for the user. Primarily we use Firebase or Rest API for auth in Flutter. In this article, we will use AWS Amplify for authentication.
AWS Amplify
We are always looking for genuine and secure cloud-based authentication systems and what features they offer.
AWS Amplify is a complete solution that lets frontend web and mobile developers easily build, ship, and host full-stack applications on AWS, with the flexibility to leverage the breadth of AWS services as use cases evolve. No cloud expertise is needed.
AWS Amplify looks similar to all the other cloud-based authentication systems. It provides pre-built sign-up, sign-in, and forgot password authentication for user management.
It also provides Multi-Factor Authentication(MFA) to our app. It could handy when we are developing defense or national security-related apps. Amplify also supports login with a social provider such as Facebook, Apple, Google Sign-In, or Login With Amazon and provides fine-grained access control to mobile and web applications.
It gives us two options the Amplify Studio or Amplify CLI to configure an app backend and use the Amplify libraries and UI components to connect our app to our backend.
For this article, to keep things simple, I will use Amplify Studio to configure the app backend and setState() to state management.
We auth the app by two-way
- Email and Password
- Social Provider(Google)
Pre-requisites
Before starting make sure of these steps.
- Install the latest version of Flutter.
- Setup your IDE.
- Create an account on AWS Amplify if you don’t have one.
- Create a user and generate a security key.
Read the below link for more details
https://docs.amplify.aws/lib/project-setup/prereq/q/platform/flutter/
Note:
- Import Amplify packages into your project inside the
pubspec.yaml
file:
amplify_core: '<1.0.0'
amplify_auth_cognito: '<1.0.0'
- To make sure you have installed the proper amplify cli version run the below command:
amplify --version
it’s time to connect to the AWS cloud and for that, we need to initialize the amplify, use the following command to initialize the Amplify:
amplify init
AWS Setup:-
First of all, create an app on Amplify Studio.
Connect your app to this backend environment using the Amplify CLI by running the following command from your project root folder.amplify pull –appId d1chk4cmm2hbfu –envName staging
Inside the Admin UI, Click ‘Authentication’ to get the screen below.
Default ‘Email’ pre-selected.I removed it.
The Authentication screen has 2 panels, the 1st for configuring Sign-in, and the 2nd for configuring sign-up.
First, we focus on Amplify Studio
Auth with Email and Password:
Let’s configure the login mechanism. Select Email from the dropdown menu.
Do the same thing for the signup mechanism.
You can also set up a password policy for sign-up and formatting verification messages. I removed all the password checks and set the character length at 8 to keep things easy.
Click the ‘Deploy’ button and then ‘Confirm Deployment’ to apply signup and login mechanisms.
run the below command again in the terminal to get the latest configuration.amplify pull –appId d1chk4cmm2hbfu –envName staging
Auth with Social Provider(Google):
Let’s configure the login mechanism. Select Any Social provider that you want to use, I selected Google.
Let’s follow some initial steps to set up for web client Id and Secret.
- Go to Google developer console.
- Click NEW PROJECT
- Type in a project name and click CREATE
- Once the project is created, from the left Navigation menu, select APIs & Services, then select Credentials.
- Click CONFIGURE CONSENT SCREEN and Click CREATE
- Type in App Information and Developer contact information which are required fields and click SAVE AND CONTINUE three times (OAuth consent screen -> Scopes -> Test Users) to finish setting up the consent screen.
- Back to the Credentials tab, Create your OAuth2.0 credentials by choosing OAuth client ID from the Create credentials drop-down list.
- Choose Web application as the Application type and name your OAuth Client then Click Create.
- Take note of Your client ID and Your Client Secret. You will need them in Amplify Studio and Choose OK.
- Select the client you created in the first step and click the edit button.
- Copy your user pool domain which displays on Amplify Studio
- Paste into Authorized Javascript origins.
- Paste your user pool domain with the
/oauth2/idpresponse
endpoint into Authorized Redirect URIs and click save.
- Paste Your client ID and Your Client Secret.
- Type ‘myapp://’ in Sign-in & sign-out redirect URLs section. If You want or have another redirect URL for sign-out then click Separate Sign-in and Sign-out URLs and type other URLs for sign-out.
- Click the ‘Deploy’ button and then ‘Confirm Deployment’ to apply signup and login mechanisms.
- Run the below command again in the terminal to get the latest configuration.
amplify pull --appId d1chk4cmm2hbfu --envName staging
Android Platform Setup:
Add the following activity
and queries
tag to the AndroidManifest.xml
file in your app’s android/app/src/main
directory, replacing myapp
with your redirect URI prefix if necessary.
<queries>
<intent>
<action android:name=
"android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
<application>
...
<activity
android:name="com.amplifyframework.auth.cognito.activities.HostedUIRedirectActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
</activity>
...
</application>
For more details use the below link.
https://docs.amplify.aws/lib/auth/social/q/platform/flutter/
Demo Setup:
Before coming to the UI part of the App. First, implement AWS API calls for the sign-in and sign-up.
I create AWSAuthRepo and call AWS Api.
class AWSAuthRepo {
Future<bool> getCurrentUser({required bool signedIn}) async {
final user = await Amplify.Auth.getCurrentUser();
if(user != null ){
signedIn = true;
}
else {
signedIn = false;
}
print('$user');
return signedIn;
}
Future<void> signUp(String email, String password, context) async{
try {
final CognitoSignUpOptions options = CognitoSignUpOptions();
await Amplify.Auth.signUp(username: email, password: password, options: options);
} on AmplifyException catch(e){
CommonUtils.showError(context, e.toString());
}
}
Future<void> signUpConfirmation(String email, String confirmationCode ) async{
try {
await Amplify.Auth.confirmSignUp(username: email, confirmationCode: confirmationCode);
} on Exception{
rethrow;
}
}
Future<void> signIn(String email, String password, context) async {
try {
await Amplify.Auth.signIn(username: email, password: password,);
await Amplify.Auth.getCurrentUser();
}
on AmplifyException catch(e){
CommonUtils.showError(context, e.toString());
}
}
Future<void> signOut() async {
try{
await Amplify.Auth.signOut();
}
on Exception{
rethrow;
}
}
Future<void> signInWithWebUI() async{
try {
final result =
await Amplify.Auth.signInWithWebUI(provider: AuthProvider.google);
print('Result: $result');
} on AmplifyException catch (e){
print(e.message);
}
}
}
To initialize amplify, call the following method inside the initState() method in the main file.
@override
void initState() {
super.initState();
_configureAmplify();
}
void _configureAmplify() async {
try {
await Amplify.addPlugin(AmplifyAuthCognito());
await Amplify.configure(amplifyconfig);
setState(() =>
_isAmplifyConfigured = true
);
print('Successfully configured');
} on Exception catch (e) {
print('Error configuring Amplify: $e');
}
}
Now let’s run the app to see if, is everything working fine
Note: If you do a hot restart, you will get the following exception.
PlatformException(AmplifyException, User-Agent was already configured successfully., {cause: null, recoverySuggestion: User-Agent is configured internally during Amplify configuration. This method should not be called externally.}, null)
Don’t worry about it because the library did not detect a hot restart. In future releases, they will fix this bug.
Now come to the UI part of Auth flow. I create four screens.
Sign-In Screen
class LogInPage extends StatefulWidget {
const LogInPage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<LogInPage> createState() => _LogInPageState();
}
class _LogInPageState extends State<LogInPage> {
AWSAuthRepo auth = AWSAuthRepo();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController emailTextController = TextEditingController();
final TextEditingController passwordTextController = TextEditingController();
bool validate = false;
bool configuredAmplify = false;
@override
void initState() {
// TODO: implement initState
super.initState();
}
void clearText(){
emailTextController.clear();
passwordTextController.clear();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
FocusScope.of(context).unfocus();
},
child: Scaffold(
backgroundColor: AppColors.lightBlueGrey,
appBar: AppBar(
automaticallyImplyLeading: false,
centerTitle: true,
title: Text(widget.title),
backgroundColor: AppColors.mediumTeal,
),
body:
SingleChildScrollView(
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Column(
children: [
const SizedBox(height: 20,),
Text(AppString.loginPageText,
style: AppTextStyle.text1),
const SizedBox(height: 40,),
SizedBox(
width: MediaQuery.of(context).size.width,
height: 60,
child:
GrayGetTextField(
hint: 'User Email',
obscure: false,
controller: emailTextController,
isVisible: false,
onValidate: CommonUtils.isValidateEmail,
valueDidChange: (String? value) {
if(validate){
_formKey.currentState!.validate();}
}, inputType: TextInputType.emailAddress,)
),
const SizedBox(height: 20,),
SizedBox(
width: MediaQuery.of(context).size.width,
height: 60,
child: GrayGetTextField(
hint: 'Password',
obscure: true,
controller: passwordTextController,
isVisible: true,
onValidate: CommonUtils.isPasswordValid,
valueDidChange: (String? value) { if(validate){
_formKey.currentState!.validate();
} }, inputType: TextInputType.text,)
),
const SizedBox(height: 40,),
GestureDetector(
onTap: () async {
await auth.signIn(emailTextController.text.trim(), passwordTextController.text.trim(),context);
if(await auth.getCurrentUser(signedIn: true))
{
Navigator.pushNamed(context, Routes.welcomeScreen);
}
},
child: Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.symmetric(horizontal: 18),
height: 53,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: AppColors.mediumBlueGrey,
border: Border.all(color: AppColors.mediumTeal,width: 2)
),
child: Center(
child: Text(AppString.loginText,
style: AppTextStyle.text4,),
),
),
),
const SizedBox(height: 20),
Text( AppString.orWithText,
style: AppTextStyle.text3),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: SizedBox(
height: 53,
child: TextButton(
onPressed: () async{
await auth.signInWithWebUI();
if(await auth.getCurrentUser(signedIn: true))
{
Navigator.pushNamed(context, Routes.welcomeScreen);
}
if (!mounted) return;
},
style: ButtonStyle(
side: MaterialStateProperty.all(
const BorderSide(color: AppColors.mediumTeal, width: 2)),
backgroundColor: MaterialStateProperty.all(AppColors.mediumBlueGrey),
foregroundColor: MaterialStateProperty.all(AppColors.mediumBlueGrey),
minimumSize: MaterialStateProperty.all(
Size(MediaQuery.of(context).size.width, 58)),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40.0))),
padding: MaterialStateProperty.all(
const EdgeInsets.symmetric(vertical: 14),
),
textStyle: MaterialStateProperty.all(AppTextStyle.text2)),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
AppIcons.googlePng,
scale: 23,
),
const SizedBox(
width: 10,
),
Text(AppString.signInWithGoogleText,
style: AppTextStyle.text4,),
],
),
),
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(AppString.doesNotHaveText),
TextButton(
onPressed: (){
Navigator.pushNamed(context, Routes.signUpScreen);
},
child: const Text(AppString.signUpText,
style: TextStyle(color: AppColors.mediumTeal),)
),
],
),
],
),
),
),
)
)
);
}
}
When we run the application, we ought to get the screen’s output like the underneath screen capture.
Sign-Up Screen
class SignUpPage extends StatefulWidget {
const SignUpPage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<SignUpPage> createState() => _SignUpPageState();
}
class _SignUpPageState extends State<SignUpPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController emailTextController = TextEditingController();
final TextEditingController passwordTextController = TextEditingController();
final TextEditingController cPasswordTextController = TextEditingController();
bool validate = false;
AWSAuthRepo auth = AWSAuthRepo();
void clearText()
{
emailTextController.clear();
passwordTextController.clear();
cPasswordTextController.clear();
}
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
FocusScope.of(context).unfocus();
},
child: Scaffold(
backgroundColor:AppColors.lightBlueGrey,
appBar: AppBar(
automaticallyImplyLeading: false,
centerTitle: true,
title: Text(widget.title),
backgroundColor: AppColors.mediumTeal,
),
body: SingleChildScrollView(
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Column(
children: [
const SizedBox(height: 20,),
Text(AppString.signUpPageText,
style: AppTextStyle.text1),
const SizedBox(height: 40,),
SizedBox(
width: MediaQuery.of(context).size.width,
height: 60,
child: Center(
child:
GrayGetTextField(
hint: 'User Email',
obscure: false,
controller: emailTextController,
isVisible: false,
onValidate: CommonUtils.isValidateEmail,
valueDidChange: (String? value) {
if(validate){
_formKey.currentState!.validate();}
}, inputType: TextInputType.emailAddress,)
),
),
const SizedBox(height: 20,),
SizedBox(
width: MediaQuery.of(context).size.width,
height: 60,
child:Center(
child:
GrayGetTextField(
hint: 'Password',
obscure: true,
controller: passwordTextController,
isVisible: true,
onValidate: CommonUtils.isPasswordValid,
valueDidChange: (String? value) {
if(validate){
_formKey.currentState!.validate();}
}, inputType: TextInputType.text,)
),
),
const SizedBox(height: 20,),
SizedBox(
width: MediaQuery.of(context).size.width,
height: 51,
child:GrayGetTextField(
hint: 'Confirm Password',
obscure: true,
controller: cPasswordTextController,
isVisible: true,
onValidate: CommonUtils.isPasswordValid,
valueDidChange: (String? value) {
if(validate){
_formKey.currentState!.validate();}
}, inputType: TextInputType.text,),
),
const SizedBox(height: 40,),
GestureDetector(
onTap: () async {
if(_formKey.currentState?.validate() ?? false) {
if (_formKey.currentState!.validate() &&
cPasswordTextController.text !=
passwordTextController.text) {
CommonUtils.showSnackBar(context,
'Password not match');
return;
}
await auth.signUp(emailTextController.text.trim(),
passwordTextController.text.trim(), context);
if(await auth.getCurrentUser(signedIn: true))
{
Navigator.pushNamed(context, Routes.welcomeScreen);
}
if (!mounted) return;
}
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 18),
width: MediaQuery.of(context).size.width,
height: 53,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: AppColors.mediumBlueGrey,
border: Border.all(color: AppColors.mediumTeal,width: 2)
),
child: Center(
child: Text(AppString.signUpText,
style: AppTextStyle.text4,)
),
),
),
const SizedBox(height: 20,),
Text( AppString.orWithText,
style: AppTextStyle.text3),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: SizedBox(
height: 53,
child: TextButton(
onPressed: ()async{
await auth.signInWithWebUI();
if(await auth.getCurrentUser(signedIn: true))
{
Navigator.pushNamed(context, Routes.welcomeScreen);
}
if (!mounted) return;
},
style: ButtonStyle(
side: MaterialStateProperty.all(
const BorderSide(color: AppColors.mediumTeal, width: 2)),
backgroundColor: MaterialStateProperty.all(AppColors.mediumBlueGrey),
foregroundColor: MaterialStateProperty.all(AppColors.mediumBlueGrey),
minimumSize: MaterialStateProperty.all(
Size(MediaQuery.of(context).size.width, 58)),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40.0))),
padding: MaterialStateProperty.all(
const EdgeInsets.symmetric(vertical: 14),
),
textStyle: MaterialStateProperty.all(AppTextStyle.text2)),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
AppIcons.googlePng,
scale: 23,
),
const SizedBox(
width: 10,
),
Text(AppString.signUpWithGoogleText,
style: AppTextStyle.text4,
),
],
),
),
),
),
const SizedBox( height: 20,),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(AppString.haveAccountText),
TextButton(
onPressed: (){
Navigator.pushNamed(context, Routes.signInScreen);
},
child: const Text(AppString.loginText,
style: TextStyle(color: AppColors.mediumTeal),)
),
],
),
],
),
),
),
),
),
);
}
}
When we run the application, we ought to get the screen’s output like the underneath screen capture.
Confirmation Page
class ConfirmationSignUP extends StatefulWidget {
const ConfirmationSignUP({Key? key}) : super(key: key);
@override
State<ConfirmationSignUP> createState() => _ConfirmationSignUPState();
}
class _ConfirmationSignUPState extends State<ConfirmationSignUP> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController emailTextController = TextEditingController();
final TextEditingController confirmationTextController = TextEditingController();
bool validate = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
FocusScope.of(context).unfocus();
},
child: Scaffold(
backgroundColor: AppColors.lightBlueGrey,
appBar: AppBar(
automaticallyImplyLeading: false,
centerTitle: true,
title: Text("Confirmation Page"),
backgroundColor: AppColors.mediumTeal,
),
body:
SingleChildScrollView(
child: Center(
child: Form(
key: _formKey,
child: Column(
children: [
const SizedBox(height: 40,),
Text(AppString.confirmSignUpText,
style: AppTextStyle.text1),
const SizedBox(height: 70,),
Container(
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.symmetric(horizontal: 32),
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
//color: AppColors.mediumBlueGrey
),
child:
GrayGetTextField(
hint: 'User Email',
obscure: false,
controller: emailTextController,
isVisible: false,
onValidate: CommonUtils.isValidateEmail,
valueDidChange: (String? value) {
if(validate){
_formKey.currentState!.validate();}
}, inputType: TextInputType.emailAddress,)
),
const SizedBox(height: 30,),
Container(
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.symmetric(horizontal: 32),
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
//color: AppColors.mediumBlueGrey
),
child:
TextField(
controller: confirmationTextController,
decoration: const InputDecoration(
fillColor: AppColors.mediumBlueGrey,
filled: true,
border: InputBorder.none,
labelStyle: TextStyle(color: AppColors.mediumTeal),
hintText: 'Confirmation Code',
isDense: true,
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 15),
),
)
),
SizedBox(height: MediaQuery.of(context).size.height*0.3,),
GestureDetector(
onTap: () async {
if (_formKey.currentState?.validate() ?? false)
{
if (confirmationTextController.text.isEmpty) {
CommonUtils.showSnackBar(context, 'Please Enter Confirmation Code');
return;}
else {
await AWSAuthRepo().signUpConfirmation(emailTextController.text.trim(), confirmationTextController.text.trim());
Navigator.pushNamed(context, Routes.welcomeScreen);
}
}
},
child: Container(
width: MediaQuery.of(context).size.width / 1.6,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: AppColors.mediumTeal
),
child: Center(
child: Text(AppString.submitText,
style: AppTextStyle.text2,),
),
),
),
const SizedBox(height: 20),
],
),
),
),
)
),
);;
}
}
When we run the application, we ought to get the screen’s output like the underneath screen capture.
Home Screen
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
AWSAuthRepo auth = AWSAuthRepo();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController nameTextController = TextEditingController();
final TextEditingController phoneNoTextController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.lightBlueGrey,
bottomNavigationBar: BottomAppBar(
color: AppColors.lightBlueGrey,
child: GestureDetector(
onTap: () async {
auth.signOut();
Navigator.pushNamed(context, Routes.signInScreen);
},
child: Container(
key: const Key("signOutButton"),
margin: const EdgeInsets.symmetric(horizontal: 32),
width: MediaQuery.of(context).size.width,
height: 60,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(30),
color: AppColors.mediumTeal),
child: Center(
child: Text(AppString.signOutText,
style: AppTextStyle.text2,),
),),
Demo Video
Sign-up with Email and Password
Auth with Social Provider(Google)
Conclusion
In this article, We learn auth user management using AWS Amplify Studio via Email and Password, and Social Login with Gmail.
There are some useful run commands.
amplify configure
amplify init
amplify auth
amplify add auth
amplify push
amplify status
To know more details about these use the below link
https://docs.amplify.aws/cli/start/workflows/#optional-update-projects-using-latest-amplify-cli
AWS Amplify also provides rebuild SignIn and SignUp UI, you can use it.
amplify_authenticator | Flutter Package
The Amplify Flutter Authenticator simplifies the process of authenticating users by providing a fully-customizable flow…pub.dev
GitHub
You can get the full code on the below link
GitHub — RitutoshAeologic/aws_demo
A new Flutter project. This project is a starting point for a Flutter application. A few resources to get you started…github.com
❤ ❤ Thanks for reading this article ❤❤
If I got something wrong? Let me know in the comments. I would love to improve.
Clap 👏 If this article helps you.
Reference :
https://docs.amplify.aws/lib/auth/getting-started/q/platform/flutter/
AWS Amplify Features | Front-End Web & Mobile | Amazon Web Services
With Amplify CLI and Libraries, add features such as auth, storage, data, AI/ML, and analytics to your app. Deploy web…aws.amazon.com
Feel free to connect with us:
And read more articles from FlutterDevs.com.
FlutterDevs team of Flutter developers to build high-quality and functionally-rich apps. Hire a flutter developer for your cross-platform Flutter mobile app project on an hourly or full-time basis as per your requirement! For any flutter-related queries, you can connect with us on Facebook, GitHub, Twitter, and LinkedIn.
We welcome feedback and hope that you share what you’re working on using #FlutterDevs. We truly enjoy seeing how you use Flutter to build beautiful, interactive web experiences.