Webux Lab - Blog
Webux Lab Logo

Webux Lab

By Studio Webux

Search

By Tommy Gingras

Last update 2025-01-14

FlutterAWS

Introduction

This is my notes to implement AWS Cognito User Pool with a Flutter IOS App. I tried to keep it as simple as possible and using only aws-amplify dependencies.

The User Pool has been created manually and uses the default configuration as it is only a test.

Setup Zed Editor

Install Dart Plugin.

Create Flutter Project

flutter create my_app
cd my_app

open -a Simulator
flutter run

At this point the counter app should be opened.

Setup AWS Cognito

AWS Cognito User Pool

Go to the service AWS Cognito, Select User Pool, Create a new Pool.

Then create a new App Client for Mobile App.

You will need the following:

  • User pool ID available on the Overview Page.
  • Client ID available on the App Clients Page.
  • region represents the region you created the User Pool in.
  • Identity Pool ID is the same as the User pool ID.

Create the following configuration file lib/data/amplifyconfiguration.dart

Update the parameters to fit your configuration

const amplifyconfig = '''{
  "version": "1",
  "auth": {
    "aws_region": "<REGION>",
    "user_pool_id": "<USER_POOL_ID>",
    "user_pool_client_id": "<USER_POOL_CLIENT_ID>",
    "identity_pool_id": "<IDENTITY_POOL_ID>",
    "username_attributes": ["email"],
    "standard_required_attributes": ["email"],
    "user_verification_types": ["email"],
    "unauthenticated_identities_enabled": true,
    "password_policy": {
      "min_length": 8,
      "require_lowercase": true,
      "require_uppercase": true,
      "require_numbers": true,
      "require_symbols": true
    }
  }
}''';

Setup Flutter Packages

flutter pub add amplify_flutter
flutter pub add amplify_auth_cognito
flutter pub add amplify_authenticator

My main.dart

import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_authenticator/amplify_authenticator.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:my_app/data/amplifyconfiguration.dart';

import 'package:flutter/material.dart';

import 'pages/home_page.dart';
Future<void> main() async {
  try {
    WidgetsFlutterBinding.ensureInitialized();
    await _configureAmplify();

    runApp(const MyApp());
  } on AmplifyException catch (e) {
    runApp(Text("Error configuring Amplify: ${e.message}"));
  }
}

Future<void> _configureAmplify() async {
  try {
    await Amplify.addPlugin(AmplifyAuthCognito());
    await Amplify.configure(amplifyconfig);
    safePrint('Successfully configured');
  } on Exception catch (e) {
    safePrint('Error configuring Amplify: $e');
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Authenticator(
      child: MaterialApp(
        builder: Authenticator.builder(),
        debugShowCheckedModeBanner: false,
        // HomePage is out of scope.
        home: HomePage(),
        theme: ThemeData(
          primarySwatch: Colors.yellow,
        ),
      ),
    );
  }
}

HomePage.dart Example

import "package:amplify_authenticator/amplify_authenticator.dart";
import "package:flutter/material.dart";

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 0;

  void _navigate(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  // Out of scope.
  final List _pages = [
    CounterPage(),
    InputPage(),
    ToDoPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(

      appBar: AppBar(
        // To add the cognito sign out button.
        actions: [
          SignOutButton(),
        ],
      ),
      // Out of scope
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: _navigate,
        items: [
          BottomNavigationBarItem(
            label: "Counter",
            icon: Icon(Icons.plus_one),
          ),
          BottomNavigationBarItem(
            label: "Input",
            icon: Icon(Icons.input),
          ),
          BottomNavigationBarItem(
            label: "Todo",
            icon: Icon(Icons.list),
          )
        ],
      ),
      body: _pages[_selectedIndex],
    );
  }
}

Update this file: ios/Podfile

Uncomment the following line and set the version to at least 13.0 (or like the error mesage shows if any.)

# Uncomment this line to define a global platform for your project
platform :ios, '13.0'

Results

Using this simple implementation you get an email/password implementation using AWS Cognito User Pool, meaning that you get out of the box the following features:

  • Power of AWS and all of its services
  • Sign In
  • Sign Out
  • Sign Up
  • Email Verification (code sent by email)
  • Lost / Recover Password
  • And much more (take a look at AWS documentation to configure all the possible features with cognito.)

Screenshots

Sign In Page Sign Up Page Lost Password Page Sign Out Button

References

Getting the tokens (id token)

import "package:amplify_flutter/amplify_flutter.dart";

// ...

Future<void> whoAmI() async {
  final authSession = await Amplify.Auth.fetchAuthSession();

  setState(() {
    // Here you can use either accessToken or idToken.
    // use accessToken if you want to verify a scope and a client id
    token = (authSession.toJson()["userPoolTokens"] as CognitoUserPoolTokens)
        .accessToken
        .raw;

    _controller.text = token;
  });
}

// ...

Button(
  onPressed: whoAmI,
  name: "Click me",
),

// ...

To be able to copy the accessToken for testing purposes. That accessToken will be use to call a custom backend (built with Deno/Hono)

SizedBox(
  child: SingleChildScrollView(
    child: TextField(controller: _controller),
  ),
),

Deno Backend

Using your custom backend service, you can simply create a middleware that does the following:

Documentation: https://www.npmjs.com/package/aws-jwt-verify

import { CognitoJwtVerifier } from "npm:aws-jwt-verify";

// Verifier that expects valid access tokens:
const verifier = CognitoJwtVerifier.create({
  userPoolId: "<USER_POOL_ID>",
  tokenUse: "access", // can be id or access token, be sure that you use the same in the application code as well.
  clientId: "<USER_POOL_CLIENT_ID>",
});

try {
  const payload = await verifier.verify(
    "<ID_TOKEN_RECEIVED_IN_AUTHORIZATION_HEADER>" // the JWT as string
  );
  console.log("Token is valid. Payload:", payload);
} catch {
  console.log("Token not valid!");
}

You can also add the groups and scope

This implementation verifies if the token received is valid and will ensure that the user is authenticated.