Amplify UI

Customization

Override and customize your Authenticator.

Unauthenticated Routes

Providing a builder value of Authenticator.builder() to your MaterialApp will make all routes in your application require authentication. The Authenticator can also be used to protect a subset of routes, and leave other routes unauthenticated.

In the example below, the default route (/) will not require authentication. When the app launches, the HomeScreen widget will show to authenticated and unauthenticated users. /profile returns an AuthenticatedView widget, with a child of ProfileScreen(). This route will require authentication. If an unauthenticated user navigates to this route, they will see the authenticator. As soon as they authenticate, they will see the ProfileScreen widget.

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

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

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  
  void initState() {
    super.initState();
    // configure amplify
  }

  
  Widget build(BuildContext context) {
    return Authenticator(
      child: MaterialApp(
        initialRoute: '/',
        routes: {
          '/': (BuildContext context) {
            return const Center(child: Text('Home'));
          },
          '/profile': (BuildContext context) {
            return const AuthenticatedView(
              child: Center(child: Text('Profile')),
            );
          },
        },
      ),
    );
  }
}

AuthenticatedView can be used along side the routing package of your choice. The example shows the same example using go_router.

import 'package:amplify_authenticator/amplify_authenticator.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

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

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  
  void initState() {
    super.initState();
    // configure amplify
  }

  static final _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (BuildContext _, GoRouterState __) {
          return const Center(child: Text('Home'));
        },
      ),
      GoRoute(
        path: '/profile',
        builder: (BuildContext _, GoRouterState __) {
          return const AuthenticatedView(child: Center(child: Text('Profile')));
        },
      ),
    ],
  );

  
  Widget build(BuildContext context) {
    return Authenticator(
      child: MaterialApp.router(
        routerConfig: _router,
      ),
    );
  }
}

Internationalization (I18n)

To use localization, start by following the steps in the Flutter Localization guide. When the guide says to create an .arb file, enter the following into lib/l10n/amplify_en.arb:

{
  "signIn": "Sign In - Localized!",
  "@signIn": {
    "description": "Label of the button to sign in the user."
  }
}

Then, to add support for another language, create a another file for that language. For example, to add Spanish support, create a file at lib/l10n/amplify_es.arb, and add the following:

{
  "signIn": "Iniciar SesiĆ³n"
}

This example only updates the 'Sign In' button. To find the full schema for Authenticator-compatible .arb file(s), you can check the default .arb files that come packaged with the Amplify Authenticator in the /src/l10n/src/ directory.

Next, import the following in the ./lib/main.dart file:

import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

Then create a ButtonResolver and pass it to an AuthStringResolver, and in turn pass this to the Authenticator API:

class LocalizedButtonResolver extends ButtonResolver {
  const LocalizedButtonResolver();

  /// Override the signIn function with a localized value
  
  String signIn(BuildContext context) {
    return AppLocalizations.of(context).signIn;
  }
}

const stringResolver = AuthStringResolver(
  buttons: LocalizedButtonResolver(),
);

return Authenticator(
  stringResolver: stringResolver,
  child: // ...
);

Finally, update your MaterialApp:

return MaterialApp(
  title: 'Authenticator Demo',
  theme: ThemeData.light(useMaterial3: true),
  darkTheme: ThemeData.dark(useMaterial3: true),
  localizationsDelegates: const [
    AppLocalizations.delegate,
  ],
  supportedLocales: const [
    Locale('en'), // English
    Locale('es'), // Spanish
  ],
  builder: Authenticator.builder(),
  home: const Scaffold(
    body: Center(
      child: Text('You are logged in!'),
    ),
  ),
);

There may be some analysis exceptions in your IDE because, as the Flutter instructions note, Flutter will automatically generate the files at build time. You may need to enable automatic code generation by setting generate: true in your app's pubspec.yaml file.

Labels & Text

Using a similar technique as Internationalization (I18n), you can customize the labels and text of the components.

Instead of implementing full fledged localization through the use of arb file, the example below shows how customize only the text within the "Sign In" button and the titles of the username, email and password fields:

/// Instantiate a ButtonResolver
class CustomButtonResolver extends ButtonResolver {
  const CustomButtonResolver();

  /// Override the signin function
  
  String signin(BuildContext context) {
    //the text of the signin button can be customized
    return 'Login to Amplify';
  }
}

/// Instantiate an InputResolver
class CustomInputResolver extends InputResolver {
  const CustomInputResolver();

  /// Override the title function
  /// Since this function handles the title for numerous fields,
  /// we recommend using a switch statement so that `super` can be called
  /// in the default case.
  
  String title(BuildContext context, InputField field) {
    switch (field) {
      case InputField.username:
        return 'Custom Username Title';
      case InputField.email:
        return 'Custom Email Title';
      case InputField.password:
        return 'Custom Password Title';
      case InputField.passwordConfirmation:
        return 'Custom Password Confirmation Title';
      default:
        return super.title(context, field);
    }
  }
}

/// Instantiate an AuthStringResolver with your two custom resolvers
const stringResolver = AuthStringResolver(
  buttons: CustomButtonResolver(),
  inputs: CustomInputResolver(),
);

/// Pass the string resolver to the Authenticator API
final authenticator = Authenticator(
  stringResolver: stringResolver,
  child: MaterialApp(
    title: 'Authenticator Demo',
    theme: ThemeData.light(),
    darkTheme: ThemeData.dark(useMaterial3: true),
    builder: Authenticator.builder(),
    home: const Scaffold(
      body: Center(
        child: Text('You are logged in!'),
      ),
    ),
  ),
);

Sign Up Fields

The following example customizes the Sign Up screen by:

  • Re-using the default Sign Up form fields
  • Adding a "Website" field with custom validation
  • Add a custom "Bio" field

Widget build(BuildContext context) {
  return Authenticator(
signUpForm: SignUpForm.custom(
fields: [
SignUpFormField.username(),
SignUpFormField.email(required: true),
SignUpFormField.custom(
required: true,
validator: ((value) {
if (value == null || value.isEmpty) {
return 'You must provide a website';
}
if (!value.contains('example.com')) {
return 'Your website must have a domain of example.com';
}
return null;
}),
title: 'Website',
attributeKey: CognitoUserAttributeKey.website,
),
SignUpFormField.custom(
title: 'Bio',
attributeKey: const CognitoUserAttributeKey.custom('bio'),
),
SignUpFormField.password(),
SignUpFormField.passwordConfirmation(),
],
),
child: MaterialApp( builder: Authenticator.builder(), home: const Scaffold( body: Center( child: Text('You are logged in!'), ), ), ), ); }
The Authenticator demo below uses a mock backend. Any users you create are stored in memory. You can verify accounts that you create with the code "123456".

Force New Password Fields

The following example customizes the Force New Password screen by:

  • Re-using the default Force New Password form fields
  • Appending a custom "Zone Info" text field

Widget build(BuildContext context) {
  return Authenticator(
confirmSignInNewPasswordForm: ConfirmSignInNewPasswordForm.custom(
fields: [
ConfirmSignInFormField.newPassword(),
ConfirmSignInFormField.confirmNewPassword(),
ConfirmSignInFormField.custom(
title: 'Zone Info',
attributeKey: CognitoUserAttributeKey.zoneinfo,
),
],
),
child: MaterialApp( builder: Authenticator.builder(), home: const Scaffold( body: Center( child: Text('You are logged in!'), ), ), ), ); }

Form Field Customization

The Authenticator allows for customization of multiple aspects of the form fields. The sections below will describe several use cases, on how to modify these fields.

Default international dial code

Default Dial code customization is available via the defaultDialCode prop in the dialCodeOptions parameter. Changing the defaultDialCode will change the default dial code in the phone number field for all forms, including the sign up, sign in, and forgot password forms.

The dial codes are a list of the ISO 3166-1 alpha-2 codes. For example, the United States is us, and Canada is ca. A list of the codes can also be found in the DialCode class.



Widget build(BuildContext context) {
  return Authenticator(
dialCodeOptions: const DialCodeOptions(defaultDialCode: DialCode.jp),
child: MaterialApp( builder: Authenticator.builder(), home: Scaffold( body: Center( child: Text('You are logged in!'), ), ), ), ), }

TOTP Issuer

The TOTP issuer is the name that will be shown in TOTP applications preceding the account name. In most cases, this should be the name of your app. For example, if your app is called "My App", your user will see "My App" - "username" in their TOTP app. This can be customized by adding the TotpOptions argument to the Authenticator component with a value for issuer.

Note: Unless changed, the default issuer is the application name retrieved from the project configuration. The key for this value is CFBundleDisplayName on iOS found in info.plist, application:label on Android found in AndroidManifest.xml.


Widget build(BuildContext context) {
  return Authenticator(
    totpOptions: const TotpOptions(issuer: 'Authenticator Demo'),
child: MaterialApp(
builder: Authenticator.builder(), home: Scaffold( body: Center( child: Text('You are logged in!'), ), ), ), ), }

Full UI Customization

In addition to customizing form fields and theming, you can also build a custom UI for one or more of the authenticator steps using a combination of prebuilt widgets from the amplify_authenticator package, and widgets that you build yourself. This can be used for simple use cases, such as adding a logo to the sign in screen, as well as more complex use cases.

This is done by providing an authenticatorBuilder argument to the Authenticator. If you are familiar with widgets like StreamBuilder, FutureBuilder, or LayoutBuilder, this may feel familiar. authenticatorBuilder is a builder method that takes arguments for the current BuildContext and AuthenticatorState. AuthenticatorState is a representation of the state being managed by the authenticator. It contains all the form field data (username, email, etc.), methods to perform authentication actions (sign in, sign up, etc.), and methods to transition between steps.

Below is an example of how you can use authenticatorBuilder to add a logo to both the sign up and sign in screen, as well as change the layout to replace the tab bar with a footer button.

This example uses several prebuilt widgets from the amplify_authenticator package, such as SignInForm(), SignUpForm(), SignInFormField.username(), and SignInButton(). All of the prebuilt widgets are integrated into the authenticator's state, meaning that you do not have to add an onChanged callback for the Form Fields, or an onTap callback for the Buttons. You can find a full list of the prebuilt forms, form fields, and buttons in the API docs.

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:flutter/material.dart';
import 'amplifyconfiguration.dart';

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

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  
  void initState() {
    super.initState();
    _configureAmplify();
  }

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

  
  Widget build(BuildContext context) {
    return Authenticator(
      // `authenticatorBuilder` is used to customize the UI for one or more steps
      authenticatorBuilder: (BuildContext context, AuthenticatorState state) {
        switch (state.currentStep) {
          case AuthenticatorStep.signIn:
            return CustomScaffold(
              state: state,
              // A prebuilt Sign In form from amplify_authenticator
              body: SignInForm(),
              // A custom footer with a button to take the user to sign up
              footer: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text('Don\'t have an account?'),
                  TextButton(
                    onPressed: () => state.changeStep(
                      AuthenticatorStep.signUp,
                    ),
                    child: const Text('Sign Up'),
                  ),
                ],
              ),
            );
          case AuthenticatorStep.signUp:
            return CustomScaffold(
              state: state,
              // A prebuilt Sign Up form from amplify_authenticator
              body: SignUpForm(),
              // A custom footer with a button to take the user to sign in
              footer: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text('Already have an account?'),
                  TextButton(
                    onPressed: () => state.changeStep(
                      AuthenticatorStep.signIn,
                    ),
                    child: const Text('Sign In'),
                  ),
                ],
              ),
            );
          case AuthenticatorStep.confirmSignUp:
            return CustomScaffold(
              state: state,
              // A prebuilt Confirm Sign Up form from amplify_authenticator
              body: ConfirmSignUpForm(),
            );
          case AuthenticatorStep.resetPassword:
            return CustomScaffold(
              state: state,
              // A prebuilt Reset Password form from amplify_authenticator
              body: ResetPasswordForm(),
            );
          case AuthenticatorStep.confirmResetPassword:
            return CustomScaffold(
              state: state,
              // A prebuilt Confirm Reset Password form from amplify_authenticator
              body: const ConfirmResetPasswordForm(),
            );
          default:
            // Returning null defaults to the prebuilt authenticator for all other steps
            return null;
        }
      },
      child: MaterialApp(
        builder: Authenticator.builder(),
        home: const Scaffold(
          body: Center(
            child: Text('You are logged in!'),
          ),
        ),
      ),
    );
  }
}

/// A widget that displays a logo, a body, and an optional footer.
class CustomScaffold extends StatelessWidget {
  const CustomScaffold({
    super.key,
    required this.state,
    required this.body,
    this.footer,
  });

  final AuthenticatorState state;
  final Widget body;
  final Widget? footer;

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: SingleChildScrollView(
          child: Column(
            children: [
              // App logo
              const Padding(
                padding: EdgeInsets.only(top: 32),
                child: Center(child: FlutterLogo(size: 100)),
              ),
              Container(
                constraints: const BoxConstraints(maxWidth: 600),
                child: body,
              ),
            ],
          ),
        ),
      ),
      persistentFooterButtons: footer != null ? [footer!] : null,
    );
  }
}
The Authenticator demo below uses a mock backend. Any users you create are stored in memory. You can verify accounts that you create with the code "123456".

TOTP Setup

By utilizing the authenticatorBuilder argument, you can also customize the TOTP setup experience. We make available two particularly helpful widgets: TotpSetupFormField.totpQrCode() and TotpSetupFormField.totpCopyKey(). In the example below, examine how you could customize the setup screen by conditionally hiding the QR code on mobile platforms.


Widget build(BuildContext context) {
  // Check if the current platform is mobile. If it is, we will hide the QR code
  final isMobile = Theme.of(context).platform == TargetPlatform.iOS ||
Theme.of(context).platform == TargetPlatform.android;
return Authenticator( // `authenticatorBuilder` is used to customize the UI for one or more steps authenticatorBuilder: (BuildContext context, AuthenticatorState state) { switch (state.currentStep) { case AuthenticatorStep.continueSignInWithTotpSetup: return Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Hide on mobile if (!isMobile) ...[ const Text( 'If you are on a mobile device, you can scan the QR code ' 'using the Authenticator app.', ), // TOTP QR Code widget TotpSetupFormField.totpQrCode(), const SizedBox(height: 20), ], const Text('Copy the key below into your Authenticator app'), // TOTP copy key widget TotpSetupFormField.totpCopyKey(), const SizedBox(height: 20), FilledButton( onPressed: () => state.changeStep( AuthenticatorStep.confirmSignInWithTotpMfaCode, ), child: const Text('Continue'), ), ], ), ); default: // Returning null defaults to the prebuilt authenticator for all other steps return null; } }, child: MaterialApp( builder: Authenticator.builder(), home: const Scaffold( body: Center( child: Text('You are logged in!'), ), ), ), ); }

Amplify open source software, documentation and community are supported by Amazon Web Services.

Ā© 2024 Amazon Web Services, Inc. and its affiliates. All rights reserved. View the site terms and privacy policy.

Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.