flutter logo
dart logo

Flutter Authentication By Example

Published on June 3, 2024

This Dart guide will help you learn how to secure a Flutter mobile application using token-based authentication. You'll learn how to use Flutter to implement the following security features:

  • Adding user login, signup, and logout to Flutter applications.
  • Making API calls from a Flutter application to request data from a protected API.
  • Getting user profile information to personalize a Flutter user interface.

This guide uses the Auth0 Flutter SDK, which provides developers with a high-level API to handle many authentication implementation details. You can now secure your Flutter applications following security best practices while writing less code.

How Does Auth0 Work?

With the help of Auth0, you don't need to be an expert on identity protocols, such as OAuth 2.0 or OpenID Connect, to understand how to secure your web application stack.

Quick Flutter Setup

You first integrate your Flutter application with Auth0. Your application will then redirect users to a customizable Auth0 login page when they need to log in. Once your users log in successfully, Auth0 redirects them back to your Flutter app, returning JSON Web Tokens (JWTs) containing their authentication and user information.

Get the Flutter Starter Application

We have created a starter project using the Flutter CLI to help you learn Flutter security concepts through hands-on practice. You can focus on building the components and services to secure your application.

Start by cloning the mobile_flutter_dart_hello-world repository on its starter branch:

COMMAND
git clone -b starter [email protected]:auth0-developer-hub/mobile_flutter_dart_hello-world.git

Once you clone the repo, make mobile_flutter_dart_hello-world your current directory:

COMMAND
cd mobile_flutter_dart_hello-world

Run the application on Android or iOS. Start by launching the Android emulator or iOS simulator or connecting an actual device to your machine.

Verify that Flutter detects any connected devices, emulators, or simulators by running the following command:

If you see the target device in the list of devices from the previous command, run the following command to launch the application on that device:

COMMAND
flutter run

You are ready to start implementing user authentication in this Flutter project. First, you'll need to configure the application to connect successfully to Auth0. Afterward, you'll use the Auth0 Flutter SDK to log in/log out, display user profile information, and request protected data from an external API server to hydrate some of the application pages.

Configure Flutter with Auth0

Follow these steps to get started with the Auth0 Identity Platform quickly:

Sign up and create an Auth0 Application

A free account also offers you:

During signup, you create an Auth0 Tenant representing the product or service you are adding authentication to.

Once you sign in, Auth0 takes you to the Dashboard. In the left sidebar menu, click on "Applications".

Then, click the "Create Application" button. A modal dialog box will appear, asking you to provide a name for the application and choose its type. Use the following values:

Name
Auth0 Flutter Code Sample

Click the "Create" button to complete the process. Your Auth0 application page will load.

In the next step, you'll learn how to help Flutter and Auth0 communicate.

What's the relationship between Auth0 Tenants and Auth0 Applications?

Suppose you have a photo-sharing Flutter app called "FL-Gram". You then would create an Auth0 tenant called fl-gram. From a customer perspective, FL-Gram is that customer's product or service.

Now, say that FL-Gram is available on three platforms: web as a single-page application and Android and iOS as a native mobile application. If each platform needs authentication, you need to create three Auth0 applications to provide the product with everything it needs to authenticate users through that platform.

FL-Gram users belong to the Auth0 FL-Gram tenant, which shares them across its Auth0 applications.

Create a communication bridge between Flutter and Auth0

When using the Auth0 Identity Platform, you don't have to build login forms. Auth0 offers a Universal Login Page to reduce the overhead of adding and managing authentication.

How does Universal Login work?

Your Flutter application will redirect users to Auth0 whenever they trigger an authentication request. Auth0 will present them with a login page. Once they log in, Auth0 will redirect them back to your Flutter application. For that redirecting to happen securely, you must specify in your Auth0 Application Settings the URLs to which Auth0 can redirect users once it authenticates them.

Get the Auth0 Domain and Auth0 Client ID

From the Auth0 Application Settings page, you need the Auth0 Domain and Client ID values to allow your Flutter application to use the communication bridge you created.

What exactly is an Auth0 Domain and an Auth0 Client ID?

Domain

When you created a new Auth0 account, Auth0 asked you to pick a name for your tenant. This name, appended with auth0.com, is your Auth0 Domain. It's the base URL that you will use to access the Auth0 APIs and the URL where you'll redirect users to log in.

You can also use custom domains to allow Auth0 to do the authentication heavy lifting for you without compromising your branding experience.

Client ID

Each application is assigned a Client ID upon creation, which is an alphanumeric string, and it's the unique identifier for your application (such as q8fij2iug0CmgPLfTfG1tZGdTQyGaTUA). You cannot modify the Client ID. You will use the Client ID to identify the Auth0 Application to which the Auth0 Flutter SDK needs to connect.

Warning: Another critical piece of information present in the "Settings" is the Client Secret. This secret protects your resources by only granting tokens to requestors if they're authorized. Think of it as your application's password, which must be kept confidential at all times. If anyone gains access to your Client Secret, they can impersonate your application and access protected resources.

Head back to your Auth0 application page and click on the "Settings" tab.

Locate the "Basic Information" section and follow these steps to get the Auth0 Domain and Auth0 Client ID values:

When you enter a value in the input fields present on this page, any code snippet that uses such value updates to reflect it. Using the input fields makes it easy to copy and paste code as you follow along.

As such, enter the "Domain" and "Client ID" values in the following fields to set up your native application in the next section:

For security, these configuration values are stored in memory and only used locally. They are gone as soon as you refresh the page! As an extra precaution, you should use values from an Auth0 test application instead of a production one.

These variables let your Flutter application identify itself as an authorized party to interact with the Auth0 authentication server.

Create a env.json file in the Flutter project's directory and add the following to it:

env.json
{
"AUTH0_DOMAIN": "AUTH0-DOMAIN",
"AUTH0_CLIENT_ID": "AUTH0-CLIENT-ID"
}

Next, create a lib/helpers/constants.dart file to allow easy access to the environment variables setup in the previous setup from your Flutter application.

lib/helpers/constants.dart
const auth0Domain = String.fromEnvironment('AUTH0_DOMAIN');
const auth0ClientId = String.fromEnvironment('AUTH0_CLIENT_ID');

Once you reach the "Call a Protected API from Flutter" section of this guide, you'll learn how to use API_SERVER_URL along with an Auth0 Audience value to request protected resources from an external API that is also protected by Auth0.

Do not close the Auth0 Application yet. You'll need some of its information in the next section.

Set Auth0 allowed URLs (running on Android and iOS)

Click on the "Settings" tab of your Auth0 Application page, locate the "Application URIs" section, and fill in the following values:

com.example.helloworld is your application's package ID. The package ID will be used as the default bundle ID on iOS. On Android, this automatically sets helloworld as the default scheme. This was configured when creating the Flutter application. You'll be using the following values for Auth0's callback and logout urls:

  • Android: <SCHEME>://<AUTH0_DOMAIN>/android/<PACKAGE_NAME>/callback
  • iOS: <BUNDLE_ID>://<AUTH0_DOMAIN>/ios/<BUNDLE_ID>/callback
Allowed Callback URLs
helloworld://AUTH0-DOMAIN/android/com.example.helloworld/callback, com.example.helloworld://AUTH0-DOMAIN/ios/com.example.helloworld/callback

The above value is the URL that Auth0 can use to redirect your users after they successfully log in.

Allowed Logout URLs
helloworld://AUTH0-DOMAIN/android/com.example.helloworld/callback, com.example.helloworld://AUTH0-DOMAIN/ios/com.example.helloworld/callback

The above value is the URL that Auth0 can use to redirect your users after they log out.

Click the "Save Changes" button near the lower right corner of the page.

Install and Set Up the Auth0 Flutter SDK

Execute the following command to install the Auth0 Flutter SDK:

COMMAND
flutter pub add auth0_flutter

The Auth0 Flutter SDK exposes several methods, variables, and types that help you integrate Auth0 with your Flutter application idiomatically.

Create lib/services/auth_service.dart and add the following code:

lib/services/auth_service.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:helloworld/helpers/constants.dart';
class AuthService {
static final AuthService instance = AuthService._internal();
factory AuthService() => instance;
Auth0 auth0 = Auth0(auth0Domain, auth0ClientId);
UserProfile? profile;
AuthService._internal();
}

The authentication process won't happen within your Flutter application layer when using Auth0. Your Flutter application will redirect your users to the Auth0 Universal Login page, where Auth0 will ask them for credentials and redirect them back to your application with the result of the authentication process.

User authentication is a mechanism that controls who can access your application. You can integrate your Flutter application with Auth0 to prevent users who have not logged in from accessing a profile or admin page.

Setup iOS CFBundleURLTypes

iOS requires additional setup to enable the app to respond to callback and logout URL calls. Update the Info.plist file (located in ios/Runner/) to include the CFBundleURLTypes property with the following configurations:

ios/Runner/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Helloworld</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>helloworld</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>auth0</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</array>
</dict>
</plist>

The Auth0 SDK requires a minimum iOS deployment version of 13.0. Open ios/Podfile and set the platform:ios property to 13.0 or higher.

ios/Podfile
# Uncomment this line to define a global platform for your project
platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
# workaround for the following error
# Swift Compiler Error (Xcode): Compiling for iOS 12.0, but module 'Auth0' has a minimum deployment target of iOS 13.0:
target.build_configurations.each do |config|
config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET'
end
end
end

Setup Android string resources

Create a strings.xml file in android/app/src/main/res/values and replace the {SCHEME} placeholder with the scheme of your Android application, for example, if you are using the same values as suggested in this guide, use: helloworld.

android/app/src/main/res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="com_auth0_domain">AUTH0-DOMAIN</string>
<string name="com_auth0_scheme">{SCHEME}</string>
</resources>

The Auth0 SDK requires manifest placeholders to define an intent-filter, which captures the authentication callback URL. You must set the Auth0 tenant domain and the callback URL scheme. Update your android/app/build.gradle to include the auth0Domain and auth0Scheme manifest placeholders.

android/app/build.gradle
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
namespace "com.example.helloworld"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID
applicationId "com.example.helloworld"
// You can update the following values to match your application needs.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
manifestPlaceholders['auth0Domain'] = "@string/com_auth0_domain"
manifestPlaceholders['auth0Scheme'] = "@string/com_auth0_scheme"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

The Auth0 SDK also requires a minimum SDK version to be 21+. Update the minSdkVersion property in the build.gradle to 21

android/app/build.gradle
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
namespace "com.example.helloworld"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID
applicationId "com.example.helloworld"
// You can update the following values to match your application needs.
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
manifestPlaceholders['auth0Domain'] = "@string/com_auth0_domain"
manifestPlaceholders['auth0Scheme'] = "@string/com_auth0_scheme"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

Auth0 and Flutter connection set

You have completed setting up an authentication module that your Flutter application can consume. All that is left is for you to continue building up the starter project throughout this guide by implementing Flutter components that trigger and manage the authentication flow.

Feel free to dive deeper into the Auth0 Documentation to learn more about how Auth0 helps you save time implementing and managing identity.

Add User Login to Flutter

The steps on how to build a Flutter login form or login page are complex. You can save development time by using a login page hosted by Auth0 that has a built-in login form that supports different types of user authentication: username and password, social login, and Multi-Factor Authentication (MFA). You just need to create a button that takes users from your Flutter application to the login page.

Start by adding a login function in lib/services/auth_service.dart

lib/services/auth_service.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:helloworld/helpers/constants.dart';
class AuthService {
static final AuthService instance = AuthService._internal();
factory AuthService() => instance;
Auth0 auth0 = Auth0(auth0Domain, auth0ClientId);
UserProfile? profile;
AuthService._internal();
Future login() async {
final credentials = await auth0.webAuthentication().login();
profile = credentials.user;
return profile;
}
}

Add a login button in lib/widgets/app_drawer.dart and connect it to the login() function set up in the previous step.

lib/widgets/app_drawer.dart
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:helloworld/pages/admin.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/pages/profile.dart';
import 'package:helloworld/pages/protected.dart';
import 'package:helloworld/pages/public.dart';
import 'package:helloworld/services/auth_service.dart';
class AppDrawer extends StatefulWidget {
final String activePage;
const AppDrawer(this.activePage, {super.key});
State<AppDrawer> createState() => _AppDrawerState();
}
class _AppDrawerState extends State<AppDrawer> {
void initState() {
super.initState();
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
void login() async {
final navigator = Navigator.of(context);
await AuthService.instance.login();
navigator.pop();
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const ProfilePage()));
}
return Drawer(
backgroundColor: Colors.black,
width: double.infinity,
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(context: context, tiles: [
Column(children: [
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 10, bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage()));
}),
const CloseButton(
color: Colors.white,
)
]),
)
]),
ListTile(
title: Text(
'Profile',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'profile' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfilePage()));
},
),
ListTile(
title: Text(
'Public',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'public' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const PublicPage()));
},
),
ListTile(
title: Text(
'Protected',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness:
widget.activePage == 'protected' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProtectedPage()));
},
),
ListTile(
title: Text(
'Admin',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'admin' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
shape: const Border(
bottom: BorderSide(color: Color(0xffB9B3BD), width: 1)),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const AdminPage()));
},
),
Padding(
padding: const EdgeInsets.all(20),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
ElevatedButton(
onPressed: () {
login();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log In',
style: TextStyle(color: Colors.white)),
)
]))
]).toList(),
));
}
}

In the next section, you'll add another button to the drawer that users can click to sign up for your application.

Add User Sign-Up to Flutter

You can make users land directly on a sign-up page instead of a login page by specifying the screen_hint=signup property in the parameters object of login():

authorizationParams: {
screen_hint: "signup",
}

To see this in practice, add a signup() function to lib/services/auth_service.dart with the following code:

lib/services/auth_service.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:helloworld/helpers/constants.dart';
class AuthService {
static final AuthService instance = AuthService._internal();
factory AuthService() => instance;
Auth0 auth0 = Auth0(auth0Domain, auth0ClientId);
UserProfile? profile;
AuthService._internal();
Future login() async {
final credentials = await auth0.webAuthentication().login();
profile = credentials.user;
return profile;
}
Future signup() async {
final credentials = await auth0
.webAuthentication()
.login(parameters: {
'screen_hint': 'signup',
});
profile = credentials.user;
return profile;
}
}

Add a signup button to lib/widgets/app_drawer.dart and connect the signup button to the signup() function setup in the previous step.

lib/widgets/app_drawer.dart
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:helloworld/pages/admin.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/pages/profile.dart';
import 'package:helloworld/pages/protected.dart';
import 'package:helloworld/pages/public.dart';
import 'package:helloworld/services/auth_service.dart';
class AppDrawer extends StatefulWidget {
final String activePage;
const AppDrawer(this.activePage, {super.key});
State<AppDrawer> createState() => _AppDrawerState();
}
class _AppDrawerState extends State<AppDrawer> {
void initState() {
super.initState();
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
void login() async {
final navigator = Navigator.of(context);
await AuthService.instance.login();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void signup() async {
final navigator = Navigator.of(context);
await AuthService.instance.signup();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
return Drawer(
backgroundColor: Colors.black,
width: double.infinity,
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(context: context, tiles: [
Column(children: [
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 10, bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage()));
}),
const CloseButton(
color: Colors.white,
)
]),
)
]),
ListTile(
title: Text(
'Profile',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'profile' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfilePage()));
},
),
ListTile(
title: Text(
'Public',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'public' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const PublicPage()));
},
),
ListTile(
title: Text(
'Protected',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness:
widget.activePage == 'protected' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProtectedPage()));
},
),
ListTile(
title: Text(
'Admin',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'admin' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
shape: const Border(
bottom: BorderSide(color: Color(0xffB9B3BD), width: 1)),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const AdminPage()));
},
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton(
onPressed: () {
signup();
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Sign Up',
style: TextStyle(color: Colors.white)),
),
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: () {
login();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log In',
style: TextStyle(color: Colors.white)),
)
]
))
]).toList(),
));
}
}

Using the Auth0 Signup feature requires you to enable the Auth0 New Universal Login Experience in your tenant.

Open the Universal Login section of the Auth0 Dashboard and choose the "New" option under the "Experience" subsection.

Auth0 Universal Login Experience options

Scroll down and click on the "Save Changes" button.

The difference between the login and sign-up user experience will be more evident once you integrate those components with your Flutter application and see them in action. You'll do that in the following sections.

Add User Logout to Flutter

You can log out users from your Flutter application by logging them out of their Auth0 sessions using the logout() method from the Auth0 Flutter SDK.

To see this in practice, add a logout() function in lib/services/auth_service.dart with the following code:

lib/services/auth_service.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:helloworld/helpers/constants.dart';
class AuthService {
static final AuthService instance = AuthService._internal();
factory AuthService() => instance;
Auth0 auth0 = Auth0(auth0Domain, auth0ClientId);
UserProfile? profile;
AuthService._internal();
Future login() async {
final credentials = await auth0.webAuthentication().login();
profile = credentials.user;
return profile;
}
Future signup() async {
final credentials = await auth0
.webAuthentication()
.login(parameters: {
'screen_hint': 'signup',
});
profile = credentials.user;
return profile;
}
Future logout() async {
await auth0.webAuthentication().logout();
profile = null;
return;
}
}

When using the logout() method, the Auth0 Flutter SDK clears the application session and redirects to the Auth0 /logout endpoint to clear the Auth0 session under the hood. By default, the SDK's logout() method opens up the default browser application on the user's device to perform a logout, like the login sequence.

Add a logout button to lib/widgets/app_drawer.dart and connect the logout button to the logout() function setup in the previous step.

lib/widgets/app_drawer.dart
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:helloworld/pages/admin.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/pages/profile.dart';
import 'package:helloworld/pages/protected.dart';
import 'package:helloworld/pages/public.dart';
import 'package:helloworld/services/auth_service.dart';
class AppDrawer extends StatefulWidget {
final String activePage;
const AppDrawer(this.activePage, {super.key});
State<AppDrawer> createState() => _AppDrawerState();
}
class _AppDrawerState extends State<AppDrawer> {
void initState() {
super.initState();
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
void login() async {
final navigator = Navigator.of(context);
await AuthService.instance.login();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void signup() async {
final navigator = Navigator.of(context);
await AuthService.instance.signup();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void logout() async {
final navigator = Navigator.of(context);
await AuthService.instance.logout();
navigator.pop();
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const HomePage()));
}
return Drawer(
backgroundColor: Colors.black,
width: double.infinity,
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(context: context, tiles: [
Column(children: [
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 10, bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage()));
}),
const CloseButton(
color: Colors.white,
)
]),
)
]),
ListTile(
title: Text(
'Profile',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'profile' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfilePage()));
},
),
ListTile(
title: Text(
'Public',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'public' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const PublicPage()));
},
),
ListTile(
title: Text(
'Protected',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness:
widget.activePage == 'protected' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProtectedPage()));
},
),
ListTile(
title: Text(
'Admin',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'admin' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
shape: const Border(
bottom: BorderSide(color: Color(0xffB9B3BD), width: 1)),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const AdminPage()));
},
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton(
onPressed: () {
signup();
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Sign Up',
style: TextStyle(color: Colors.white)),
),
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: () {
login();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log In',
style: TextStyle(color: Colors.white)),
),
ElevatedButton(
onPressed: () {
logout();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log Out',
style: TextStyle(color: Colors.white)),
)
]))
]).toList(),
));
}
}

Check for stored credentials

Auth0 includes a Credentials Manager utility that allows you to securely store and retrieve the user's credentials. The credentials will be stored encrypted in Shared Preferences on Android, and in the Keychain on iOS.

You can use the credentials manager to check for valid credentials when the users open your app. If they exist, you can retrieve them and automatically display the app's logged-in state without any additional login steps.

Add an init function in the AuthService to check if the user has any valid credentials or not using Auth0's credential manager's hasValidCredentials() function. If a user has a valid credential, call credentials() function and set it to the profile variable so it can be easily accessed from other parts of the application.

lib/services/auth_service.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:helloworld/helpers/constants.dart';
class AuthService {
static final AuthService instance = AuthService._internal();
factory AuthService() => instance;
Auth0 auth0 = Auth0(auth0Domain, auth0ClientId);
UserProfile? profile;
AuthService._internal();
Future init() async {
final isLoggedIn = await auth0.credentialsManager.hasValidCredentials();
if (isLoggedIn) {
final credentials = await auth0.credentialsManager.credentials();
profile = credentials.user;
}
return profile;
}
Future login() async {
final credentials = await auth0.webAuthentication().login();
profile = credentials.user;
return profile;
}
Future signup() async {
final credentials = await auth0
.webAuthentication()
.login(parameters: {
'screen_hint': 'signup',
});
profile = credentials.user;
return profile;
}
Future logout() async {
await auth0.webAuthentication().logout();
profile = null;
return;
}
}

Update AppDrawer to call AuthService.instance.init() and set the profile variable when the widget initializes.

lib/widgets/app_drawer.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:helloworld/pages/admin.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/pages/profile.dart';
import 'package:helloworld/pages/protected.dart';
import 'package:helloworld/pages/public.dart';
import 'package:helloworld/services/auth_service.dart';
class AppDrawer extends StatefulWidget {
final String activePage;
const AppDrawer(this.activePage, {super.key});
State<AppDrawer> createState() => _AppDrawerState();
}
class _AppDrawerState extends State<AppDrawer> {
UserProfile? profile;
void initState() {
super.initState();
initAuth();
}
void initAuth() async {
await AuthService.instance.init();
setState(() => profile = AuthService.instance.profile);
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
void login() async {
final navigator = Navigator.of(context);
await AuthService.instance.login();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void signup() async {
final navigator = Navigator.of(context);
await AuthService.instance.signup();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void logout() async {
final navigator = Navigator.of(context);
await AuthService.instance.logout();
navigator.pop();
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const HomePage()));
}
return Drawer(
backgroundColor: Colors.black,
width: double.infinity,
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(context: context, tiles: [
Column(children: [
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 10, bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage()));
}),
const CloseButton(
color: Colors.white,
)
]),
)
]),
ListTile(
title: Text(
'Profile',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'profile' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfilePage()));
},
),
ListTile(
title: Text(
'Public',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'public' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const PublicPage()));
},
),
ListTile(
title: Text(
'Protected',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness:
widget.activePage == 'protected' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProtectedPage()));
},
),
ListTile(
title: Text(
'Admin',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'admin' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
shape: const Border(
bottom: BorderSide(color: Color(0xffB9B3BD), width: 1)),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const AdminPage()));
},
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton(
onPressed: () {
signup();
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Sign Up',
style: TextStyle(color: Colors.white)),
),
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: () {
login();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log In',
style: TextStyle(color: Colors.white)),
),
ElevatedButton(
onPressed: () {
logout();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log Out',
style: TextStyle(color: Colors.white)),
)
]))
]).toList(),
));
}
}

Render Components Conditionally

In this section, you'll learn how to render Flutter widgets conditionally based on the status of the Auth0 Flutter SDK or the authentication status of your users.

Render the authentication buttons conditionally

The Flutter starter application features a mobile navigation experience. You'll see a menu button at the top-right corner of the page. Tapping or clicking on the menu button opens a modal that shows you the different pages that you can access in the application.

In this section, you'll expose the button components that trigger the login, sign-up, and logout events through these page navigation elements.

Next, you'll add a check in lib/widgets/app_drawer.dart to display the login and sign-up button when the user is not authenticated and the logout button if they are authenticated.

lib/widgets/app_drawer.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:helloworld/pages/admin.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/pages/profile.dart';
import 'package:helloworld/pages/protected.dart';
import 'package:helloworld/pages/public.dart';
import 'package:helloworld/services/auth_service.dart';
class AppDrawer extends StatefulWidget {
final String activePage;
const AppDrawer(this.activePage, {super.key});
State<AppDrawer> createState() => _AppDrawerState();
}
class _AppDrawerState extends State<AppDrawer> {
UserProfile? profile;
void initState() {
super.initState();
initAuth();
}
void initAuth() async {
await AuthService.instance.init();
setState(() => profile = AuthService.instance.profile);
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
void login() async {
final navigator = Navigator.of(context);
await AuthService.instance.login();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void signup() async {
final navigator = Navigator.of(context);
await AuthService.instance.signup();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void logout() async {
final navigator = Navigator.of(context);
await AuthService.instance.logout();
navigator.pop();
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const HomePage()));
}
return Drawer(
backgroundColor: Colors.black,
width: double.infinity,
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(context: context, tiles: [
Column(children: [
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 10, bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage()));
}),
const CloseButton(
color: Colors.white,
)
]),
)
]),
ListTile(
title: Text(
'Profile',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'profile' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfilePage()));
},
),
ListTile(
title: Text(
'Public',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'public' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const PublicPage()));
},
),
ListTile(
title: Text(
'Protected',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness:
widget.activePage == 'protected' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProtectedPage()));
},
),
ListTile(
title: Text(
'Admin',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'admin' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
shape: const Border(
bottom: BorderSide(color: Color(0xffB9B3BD), width: 1)),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const AdminPage()));
},
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: profile == null
? [
OutlinedButton(
onPressed: () {
signup();
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Sign Up',
style: TextStyle(color: Colors.white)),
),
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: () {
login();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log In',
style: TextStyle(color: Colors.white)),
)
]
: [
ElevatedButton(
onPressed: () {
logout();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log Out',
style: TextStyle(color: Colors.white)),
)
]))
]).toList(),
));
}
}

The profile variable which is set to Auth0's AuthService.instance.profile value returns the logged-in user's information as tracked by the Auth0 Flutter SDK. This value returns the user information if the user has been authenticated and null when not. As such, you can use the profile value to render UI elements conditionally depending on the authentication state of your users, as you did above.

Run the following command to launch the application:

COMMAND
flutter run --dart-define-from-file=env.json

We add --dart-define-from-file=env.json to our flutter run command to add the configuration from our env.json file into our application.

Go ahead and try to log in. Your Flutter application redirects you to the Auth0 Universal Login page. You can use the form to log in with a username and password or a social identity provider like Google. Notice that this login page also gives you the option to sign up.

New Auth0 Universal Login Experience Form

However, when you click the sign-up button from your application directly, Flutter takes you to the Signup page, where your users can sign up for the Flutter application. Try it out!

New Auth0 Universal Login Experience Signup Page
You can customize the appearance of New Universal Login pages. You can also override any text in the New Experience using the Text Customization API.

Notice that when you finish logging in or signing up, Auth0 redirects you to your Flutter app.

Render navigation options conditionally

There may be use cases where you want to hide user interface elements from users who have not logged in to your application. For this starter application, only authenticated users should see the navigation tabs to access the /protected and /admin pages.

To implement this use case, you'll rely once again on the profile variable created in the previous step.

Open the lib/widgets/app_drawer.dart file that defines your application drawer and update it like so:

lib/widgets/app_drawer.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:helloworld/pages/admin.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/pages/profile.dart';
import 'package:helloworld/pages/protected.dart';
import 'package:helloworld/pages/public.dart';
import 'package:helloworld/services/auth_service.dart';
class AppDrawer extends StatefulWidget {
final String activePage;
const AppDrawer(this.activePage, {super.key});
State<AppDrawer> createState() => _AppDrawerState();
}
class _AppDrawerState extends State<AppDrawer> {
UserProfile? profile;
void initState() {
super.initState();
initAuth();
}
void initAuth() async {
await AuthService.instance.init();
setState(() => profile = AuthService.instance.profile);
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
void login() async {
final navigator = Navigator.of(context);
await AuthService.instance.login();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void signup() async {
final navigator = Navigator.of(context);
await AuthService.instance.signup();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void logout() async {
final navigator = Navigator.of(context);
await AuthService.instance.logout();
navigator.pop();
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const HomePage()));
}
return Drawer(
backgroundColor: Colors.black,
width: double.infinity,
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(context: context, tiles: [
Column(children: [
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 10, bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage()));
}),
const CloseButton(
color: Colors.white,
)
]),
)
]),
ListTile(
title: Text(
'Profile',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'profile' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfilePage()));
},
),
ListTile(
title: Text(
'Public',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'public' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const PublicPage()));
},
),
if (profile != null)
ListTile(
title: Text(
'Protected',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness:
widget.activePage == 'protected' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProtectedPage()));
},
),
if (profile != null)
ListTile(
title: Text(
'Admin',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'admin' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
shape: const Border(
bottom: BorderSide(color: Color(0xffB9B3BD), width: 1)),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const AdminPage()));
},
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: profile == null
? [
OutlinedButton(
onPressed: () {
signup();
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Sign Up',
style: TextStyle(color: Colors.white)),
),
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: () {
login();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log In',
style: TextStyle(color: Colors.white)),
)
]
: [
ElevatedButton(
onPressed: () {
logout();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log Out',
style: TextStyle(color: Colors.white)),
)
]))
]).toList(),
));
}
}

Log out from your Flutter application and notice how now you can only see the tabs for the profile and public pages in the navigation modal, along with the login and sign-up buttons. Log in and then see the rest of the navigation options show up.

Modify behavior of navigation options conditionally

Since you are displaying the profile navigation option for both authenticated and unauthenticated users, we'll add triggering to the login flow for unauthenticated users and only redirect the users to the profile page if they are authenticated.

lib/widgets/app_drawer.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:helloworld/pages/admin.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/pages/profile.dart';
import 'package:helloworld/pages/protected.dart';
import 'package:helloworld/pages/public.dart';
import 'package:helloworld/services/auth_service.dart';
class AppDrawer extends StatefulWidget {
final String activePage;
const AppDrawer(this.activePage, {super.key});
State<AppDrawer> createState() => _AppDrawerState();
}
class _AppDrawerState extends State<AppDrawer> {
UserProfile? profile;
void initState() {
super.initState();
initAuth();
}
void initAuth() async {
await AuthService.instance.init();
setState(() => profile = AuthService.instance.profile);
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
void login() async {
final navigator = Navigator.of(context);
await AuthService.instance.login();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void signup() async {
final navigator = Navigator.of(context);
await AuthService.instance.signup();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
const ProfilePage()));
}
void logout() async {
final navigator = Navigator.of(context);
await AuthService.instance.logout();
navigator.pop();
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const HomePage()));
}
return Drawer(
backgroundColor: Colors.black,
width: double.infinity,
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(context: context, tiles: [
Column(children: [
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 10, bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage()));
}),
const CloseButton(
color: Colors.white,
)
]),
)
]),
ListTile(
title: Text(
'Profile',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'profile' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
if (profile != null) {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfilePage()));
} else {
login();
}
},
),
ListTile(
title: Text(
'Public',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'public' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const PublicPage()));
},
),
if (profile != null)
ListTile(
title: Text(
'Protected',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness:
widget.activePage == 'protected' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProtectedPage()));
},
),
if (profile != null)
ListTile(
title: Text(
'Admin',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'admin' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
shape: const Border(
bottom: BorderSide(color: Color(0xffB9B3BD), width: 1)),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const AdminPage()));
},
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: profile == null
? [
OutlinedButton(
onPressed: () {
signup();
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Sign Up',
style: TextStyle(color: Colors.white)),
),
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: () {
login();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log In',
style: TextStyle(color: Colors.white)),
)
]
: [
ElevatedButton(
onPressed: () {
logout();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log Out',
style: TextStyle(color: Colors.white)),
)
]))
]).toList(),
));
}
}

You can now test accessing the profile page for both authenticated and unauthenticated users. Log out and try to access the Profile page by clicking the "Profile" button in the navigation. If it works, Flutter redirects you to log in with Auth0.

Get User Profile Information in Flutter

After a user successfully logs in, Auth0 sends an ID token to your Flutter application. Authentication systems, such as Auth0, use ID Tokens in token-based authentication to cache user profile information and provide it to a client application. The caching of ID tokens can improve the performance and responsiveness of your Flutter application.

You can use the data from the ID token to personalize the user interface of your Flutter application. The Auth0 Flutter SDK decodes the ID token and stores its data in the profile variable exposed via the AuthService. Some of the ID token information includes the name, nickname, picture, and email of the logged-in user.

How can you use the ID token to create a profile page for your users?

You'll start with passing the profile information from the drawer as we navigate to the profile page

lib/widgets/app_drawer.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:helloworld/pages/admin.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/pages/profile.dart';
import 'package:helloworld/pages/protected.dart';
import 'package:helloworld/pages/public.dart';
import 'package:helloworld/services/auth_service.dart';
class AppDrawer extends StatefulWidget {
final String activePage;
const AppDrawer(this.activePage, {super.key});
State<AppDrawer> createState() => _AppDrawerState();
}
class _AppDrawerState extends State<AppDrawer> {
UserProfile? profile;
void initState() {
super.initState();
initAuth();
}
void initAuth() async {
await AuthService.instance.init();
setState(() => profile = AuthService.instance.profile);
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
void login() async {
final navigator = Navigator.of(context);
await AuthService.instance.login();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
ProfilePage(user: profile!)));
}
void signup() async {
final navigator = Navigator.of(context);
await AuthService.instance.signup();
navigator.pop();
navigator.pushReplacement(MaterialPageRoute(
builder: (context) =>
ProfilePage(user: profile!)));
}
void logout() async {
final navigator = Navigator.of(context);
await AuthService.instance.logout();
navigator.pop();
navigator.pushReplacement(
MaterialPageRoute(builder: (context) => const HomePage()));
}
return Drawer(
backgroundColor: Colors.black,
width: double.infinity,
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(context: context, tiles: [
Column(children: [
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 10, bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage()));
}),
const CloseButton(
color: Colors.white,
)
]),
)
]),
ListTile(
title: Text(
'Profile',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'profile' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
if (profile != null) {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => ProfilePage(user: profile!)));
} else {
login();
}
},
),
ListTile(
title: Text(
'Public',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'public' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const PublicPage()));
},
),
if (profile != null)
ListTile(
title: Text(
'Protected',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness:
widget.activePage == 'protected' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProtectedPage()));
},
),
if (profile != null)
ListTile(
title: Text(
'Admin',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: widget.activePage == 'admin' ? 2 : 0,
decorationColor: const Color(0xff413DA6)),
),
shape: const Border(
bottom: BorderSide(color: Color(0xffB9B3BD), width: 1)),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const AdminPage()));
},
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: profile == null
? [
OutlinedButton(
onPressed: () {
signup();
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Sign Up',
style: TextStyle(color: Colors.white)),
),
const SizedBox(
width: 16,
),
ElevatedButton(
onPressed: () {
login();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log In',
style: TextStyle(color: Colors.white)),
)
]
: [
ElevatedButton(
onPressed: () {
logout();
},
style: ElevatedButton.styleFrom(
// #635DFF
backgroundColor: const Color.fromRGBO(
99, 93, 255, 1), // Background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text('Log Out',
style: TextStyle(color: Colors.white)),
)
]))
]).toList(),
));
}
}

Next, update the profile page to display the user's profile information. Open lib/pages/profile.dart and add the following code (replacing the hardcoded placeholder user information):

lib/pages/profile.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/widgets/app_drawer.dart';
import 'dart:convert';
class ProfileData {
final String text;
const ProfileData({
required this.text,
});
factory ProfileData.fromJson(Map<String, dynamic> json) {
return ProfileData(
text: json['text'],
);
}
}
class ProfilePage extends StatelessWidget {
const ProfilePage({Key? key, required this.user}) : super(key: key);
final UserProfile user;
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
JsonEncoder encoder = const JsonEncoder.withIndent(' ');
String prettyprint = encoder.convert({
"address": user.address,
"birthdate": user.birthdate,
"email": user.email,
"name": user.name,
"givenName": user.givenName,
"middleName": user.middleName,
"familyName": user.familyName,
"profileUrl": user.profileUrl.toString(),
"customClaims": user.customClaims,
"gender": user.gender,
"isEmailVerified": user.isEmailVerified,
"isPhoneNumberVerified": user.isPhoneNumberVerified,
"pictureUrl": user.pictureUrl.toString(),
});
return Scaffold(
appBar: AppBar(
centerTitle: false,
foregroundColor: Colors.white,
title: GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const HomePage()));
}),
backgroundColor: Colors.black,
shape: const Border(bottom: BorderSide(color: Colors.white, width: 1)),
),
endDrawer: const AppDrawer('profile'),
body: Container(
color: Colors.black,
padding: const EdgeInsets.all(20),
child: ListView(children: [
const Text(
'Profile Page',
style: TextStyle(
fontSize: 24, fontWeight: FontWeight.w700, color: Colors.white),
),
const SizedBox(height: 10),
const Text('Only authenticated users can access this page.',
style: TextStyle(fontSize: 16, color: Colors.white)),
const SizedBox(height: 20),
// user information
Row(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular((25))),
child: Image.network(
user.pictureUrl.toString(),
fit: BoxFit.fill,
height: 50,
width: 50,
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user.name != null ? user.name! : '',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white),
),
Text(user.email != null ? user.email! : '',
style: const TextStyle(fontSize: 14, color: Colors.white))
],
))
],
),
const SizedBox(height: 20),
// code snippet container
Container(
padding:
const EdgeInsets.only(top: 10, right: 20, bottom: 10, left: 20),
decoration: const BoxDecoration(
color: Color.fromRGBO(189, 196, 207, 1),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10))),
child: const Text(
'User Profile Object',
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontFamily: 'RobotoMono',
fontWeight: FontWeight.bold),
),
),
Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Color.fromRGBO(42, 46, 53, 1),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Text(
prettyprint,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
fontFamily: 'RobotoMono'),
),
))
]),
),
);
}
}

What's happening within the profile page?

  • It displays three properties from the user object in the UserProfile interface: name, pictureUrl, and email.

  • It displays the full content of the decoded ID token within a code box. You can now see all the other properties available for you to use. The properties are known as "token claims".

The profile page renders user information that you could consider private or sensitive. Additionally, the user property is null if there is no logged-in user. So either way, this component should only render if Auth0 has authenticated the user.

If you are logged in to your application, go to the profile page to see your user profile details.

Integrate Flutter with an API Server

This section focuses on showing you how to get an access token in your Flutter application and how to use it to make API calls to protected API endpoints.

When you use Auth0, you delegate the authentication process to a centralized service. Auth0 provides you with functionality to log in and log out users from your Flutter application. However, your application may need to access protected resources from an API.

You can also protect an API with Auth0. There are multiple API quickstarts to help you integrate Auth0 with your backend platform.

When you use Auth0 to protect your API, you also delegate the authorization process to a centralized service that ensures only approved client applications can access protected resources on behalf of a user.

How can you make secure API calls from Flutter?

Your Flutter application authenticates the user and receives an access token from Auth0. The application can then pass that access token to your API as a credential. In turn, your API can use Auth0 libraries to verify the access token it receives from the calling application and issue a response with the desired data.

Instead of creating an API from scratch to test the authentication and authorization flow between the client and the server, you can pair this client application with an API server that matches the technology stack you use at work. The Flutter "Hello World" client application that you have been building up can interact with any of the "Hello World" API server samples from the Auth0 Developer Hub.

The "Hello World" API server samples run on http://localhost:6060 by default, which is the same origin URL and port where the mocked JSON server is running. As such, before you set up the "Hello World" API server, locate the tab where you are running the npm run api command and stop the mocked JSON server.

Pick an API code sample in your preferred backend framework and language from the list below and follow the instructions on the code sample page to set it up. Once you complete the sample API server setup, please return to this page to learn how to integrate that API server with your Flutter application.

actix-web
rust
Actix Web/Rust API:Authorization Code Sample
Code sample of a simple Actix Web server that implements token-based authorization using Auth0.
aspnet-core
csharp
ASP.NET Core Code Sample:Web API Authorization
Code sample of a simple ASP.NET Core server that implements token-based authorization using Auth0.
aspnet-core
csharp
ASP.NET Core v5 Code Sample:Web API Authorization
Code sample of a simple ASP.NET Core v5.0 server that implements token-based authorization using Auth0.
django
python
Django/Python API:Authorization Code Sample
Code sample of a simple Django server that implements token-based authorization using Auth0.
express
javascript
Express.js Code Sample:Basic API Authorization
Code sample of a simple Express.js server that implements token-based authorization using Auth0.
express
typescript
Express.js/TypeScript Code Sample:Basic API Authorization
Code sample of a simple Express.js server built with TypeScript that implements token-based authorization using Auth0.
fastapi
python
FastAPI/Python Code Sample:Basic API Authorization
Code sample of a simple FastAPI server that implements token-based authorization using Auth0.
flask
python
Flask/Python API:Authorization Code Sample
Code sample of a simple Flask server that implements token-based authorization using Auth0.
laravel
php
Laravel/PHP Code Sample:Basic API Authorization with Auth0 Laravel SDK
Code sample of a simple Laravel server that implements token-based authorization using the Auth0 Laravel SDK.
laravel
php
Laravel/PHP Code Sample:Basic API Authorization with Auth0 PHP SDK
Code sample of a simple Laravel server that implements token-based authorization using the Auth0 PHP SDK.
lumen
php
Lumen Code Sample:Basic API Authorization
Code sample of a simple Lumen server that implements token-based authorization using Auth0.
nestjs
typescript
NestJS Code Sample:Basic API Authorization
Code sample of a simple NestJS server that implements token-based authorization using Auth0.
phoenix
elixir
Phoenix/Elixir API:Authorization Code Sample
Code sample of a simple Phoenix server that implements token-based authorization using Auth0.
rails
ruby
Ruby on Rails API:Authorization Code Sample
Code sample of a simple Rails server that implements authorization using Auth0.
spring
java
Spring Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring Web API server to protect API endpoints, using Spring Security and the Okta Spring Boot Starter.
spring
java
Spring Functional Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring Web API server to protect API endpoints, following a functional approach.
spring-webflux
java
Spring WebFlux Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring WebFlux API server to protect API endpoints, using Spring Security and the Okta Spring Boot Starter.
standard-library
golang
Golang Code Sample:Basic API Authorization
Code sample of a simple Golang server that implements token-based authorization using Auth0.
symfony
php
Symfony Code Sample:Basic API Authorization
Code sample of a simple Symfony server that implements token-based authorization using Auth0.

Call a Protected API from Flutter

Once you have set up the API server code sample, you should have created an Auth0 Audience value. Store that value in the following field so that you can use it throughout the instructions presented on this page easily:

Now, update the env.json file in the root directory of your project to include the server URL and Auth0 Audience:

When running on the Android emulator. Make sure to update the API_SERVER_URL to http://10.0.2.2:6060. 10.0.2.2 is a special alias to your host loopback interface (127.0.0.1 or localhost on your development machine).

env.json
{
"API_SERVER_URL": "http://localhost:6060", # Or http://10.0.2.2:6060 depending on whether you are running the app on Android or not
"AUTH0_DOMAIN": "AUTH0-DOMAIN",
"AUTH0_CLIENT_ID": "AUTH0-CLIENT-ID",
"AUTH0_AUDIENCE":"AUTH0-AUDIENCE"
}

You are using AUTH0_AUDIENCE to add the value of your Auth0 API Audience so that your Flutter client application can request resources from the API that such audience value represents.

Let's understand better what the AUTH0_AUDIENCE and API_SERVER_URL values represent.

The API_SERVER_URL is simply the URL where your sample API server listens for requests. In production, you'll change this value to the URL of your live server.

Your Flutter application must pass an access token when it calls a target API to access protected resources. You can request an access token in a format that the API can verify by passing the audience to the Auth0 Flutter SDK.

The value of the Auth0 Audience must be the same for both the Flutter application and the API server you decided to set up.

Why is the Auth0 Audience value the same for both apps? Auth0 uses the value of the audience prop to determine which resource server (API) the user is authorizing your Flutter application to access. It's like a phone number. You want to ensure that your Flutter application "texts the right API".

Update the lib/helpers/constants.dart file to integrate these new Auth0 environment variables from env.json into your Flutter app:

lib/helpers/constants.dart
const serverUrl = String.fromEnvironment('API_SERVER_URL');
const auth0Domain = String.fromEnvironment('AUTH0_DOMAIN');
const auth0ClientId = String.fromEnvironment('AUTH0_CLIENT_ID');
const auth0Audience = String.fromEnvironment('AUTH0_AUDIENCE');

Update the login() and signup() functions in lib/services/auth_service.dart to pass the auth0Audience properties as follows:

lib/services/auth_service.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:helloworld/helpers/constants.dart';
class AuthService {
static final AuthService instance = AuthService._internal();
factory AuthService() => instance;
Auth0 auth0 = Auth0(auth0Domain, auth0ClientId);
UserProfile? profile;
AuthService._internal();
Future init() async {
final isLoggedIn = await auth0.credentialsManager.hasValidCredentials();
if (isLoggedIn) {
final credentials = await auth0.credentialsManager.credentials();
profile = credentials.user;
}
return profile;
}
Future login() async {
final credentials = await auth0.webAuthentication().login(
audience: auth0Audience,
);
profile = credentials.user;
return profile;
}
Future signup() async {
final credentials = await auth0
.webAuthentication()
.login(audience: auth0Audience, parameters: {
'screen_hint': 'signup',
});
profile = credentials.user;
return profile;
}
Future logout() async {
await auth0.webAuthentication().logout();
profile = null;
return;
}
}

Update the protected page to include an access token when making the API call to get data only accessible to authenticated users from the API. Open lib/pages/protected.dart and add the following code:

lib/pages/protected.dart
import 'package:helloworld/pages/home.dart';
import 'package:helloworld/helpers/constants.dart';
import 'package:helloworld/widgets/app_drawer.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:auth0_flutter/auth0_flutter.dart';
class ProtectedData {
final String text;
const ProtectedData({
required this.text,
});
factory ProtectedData.fromJson(Map<String, dynamic> json) {
return ProtectedData(
text: json['text'],
);
}
}
class ProtectedPage extends StatefulWidget {
const ProtectedPage({super.key});
State<ProtectedPage> createState() => _ProtectedPageState();
}
Future<ProtectedData> fetchProtectedData() async {
Auth0 auth0 = Auth0(auth0Domain, auth0ClientId);
final credentials = await auth0.credentialsManager.credentials(parameters: {'audience': auth0Audience});
final String accessToken = credentials.accessToken;
final response = await http.get(
Uri.parse('$serverUrl/api/messages/protected'),
headers: {'Authorization': 'Bearer $accessToken'},
);
if (response.statusCode == 200) {
return ProtectedData.fromJson(jsonDecode(response.body));
} else {
throw response.body;
}
}
class _ProtectedPageState extends State<ProtectedPage> {
late Future<ProtectedData> protectedData;
void initState() {
super.initState();
protectedData = fetchProtectedData();
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
return Scaffold(
appBar: AppBar(
centerTitle: false,
foregroundColor: Colors.white,
title: GestureDetector(
child: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
onTap: () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const HomePage()));
}),
backgroundColor: Colors.black,
shape: const Border(bottom: BorderSide(color: Colors.white, width: 1)),
),
endDrawer: const AppDrawer('protected'),
body: Container(
color: Colors.black,
padding: const EdgeInsets.all(20),
child: ListView(children: [
const Text(
'Protected Page',
style: TextStyle(
fontSize: 24, fontWeight: FontWeight.w700, color: Colors.white),
),
const SizedBox(height: 10),
const Text(
'This page retrieves a protected message.',
style: TextStyle(fontSize: 16, color: Colors.white),
),
const SizedBox(height: 10),
const Text('Only authenticated users should access this page.',
style: TextStyle(fontSize: 16, color: Colors.white)),
const SizedBox(height: 20),
// code snippet container
Container(
padding:
const EdgeInsets.only(top: 10, right: 20, bottom: 10, left: 20),
decoration: const BoxDecoration(
color: Color.fromRGBO(189, 196, 207, 1),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10))),
child: const Text(
'Protected Message',
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontFamily: 'RobotoMono',
fontWeight: FontWeight.bold),
),
),
Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Color.fromRGBO(42, 46, 53, 1),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))),
child: FutureBuilder<ProtectedData>(
future: protectedData,
builder: (context, snapshot) {
if (snapshot.hasData) {
JsonEncoder encoder = const JsonEncoder.withIndent(' ');
String message =
encoder.convert({'text': snapshot.data?.text});
return Text(
message,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
fontFamily: 'RobotoMono'),
);
} else {
JsonEncoder encoder = const JsonEncoder.withIndent(' ');
String message = encoder.convert({
"message":
"Http failure response for http://localhost:6060/api/messages/protected: 0 Unknown Error"
});
if (snapshot.hasError) {
message = snapshot.error.toString();
}
return Text(
message,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
fontFamily: 'RobotoMono'),
);
}
}))
]),
),
);
}
}

Lastly, update the admin page to include an access token when making the API call to get data only accessible to authenticated users with an admin role from the API. Open lib/pages/admin.dart and add the following code:

lib/pages/admin.dart
import 'package:helloworld/helpers/constants.dart';
import 'package:helloworld/widgets/app_drawer.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:auth0_flutter/auth0_flutter.dart';
class AdminData {
final String text;
const AdminData({
required this.text,
});
factory AdminData.fromJson(Map<String, dynamic> json) {
return AdminData(
text: json['text'],
);
}
}
class AdminPage extends StatefulWidget {
const AdminPage({super.key});
State<AdminPage> createState() => _AdminPageState();
}
Future<AdminData> fetchAdminData() async {
Auth0 auth0 = Auth0(auth0Domain, auth0ClientId);
final credentials = await auth0.credentialsManager.credentials(parameters: {'audience': auth0Audience});
final String accessToken = credentials.accessToken;
final response = await http.get(
Uri.parse('$serverUrl/api/messages/admin'),
headers: {'Authorization': 'Bearer $accessToken'},
);
if (response.statusCode == 200) {
return AdminData.fromJson(jsonDecode(response.body));
} else {
throw response.body;
}
}
class _AdminPageState extends State<AdminPage> {
late Future<AdminData> adminData;
void initState() {
super.initState();
adminData = fetchAdminData();
}
Widget build(BuildContext context) {
const auth0Logo =
'https://cdn.auth0.com/blog/hub/code-samples/hello-world/auth0-logo.svg';
return Scaffold(
appBar: AppBar(
centerTitle: false,
foregroundColor: Colors.white,
title: SvgPicture.network(
auth0Logo,
fit: BoxFit.fitHeight,
height: 24),
backgroundColor: Colors.black,
shape: const Border(bottom: BorderSide(color: Colors.white, width: 1)),
),
endDrawer: const AppDrawer('admin'),
body: Container(
color: Colors.black,
padding: const EdgeInsets.all(20),
child: ListView(children: [
const Text(
'Admin Page',
style: TextStyle(
fontSize: 24, fontWeight: FontWeight.w700, color: Colors.white),
),
const SizedBox(height: 10),
const Text(
'This page retrieves an admin message.',
style: TextStyle(fontSize: 16, color: Colors.white),
),
const SizedBox(height: 10),
RichText(
text: const TextSpan(
style: TextStyle(fontSize: 16, color: Colors.white),
children: <TextSpan>[
TextSpan(text: 'Only authenticated users with the '),
TextSpan(
text: 'read:admin-messages',
style: TextStyle(color: Color(0xff16A380))),
TextSpan(text: ' permission should access this page.'),
])),
const SizedBox(height: 20),
// code snippet container
Container(
padding:
const EdgeInsets.only(top: 10, right: 20, bottom: 10, left: 20),
decoration: const BoxDecoration(
color: Color.fromRGBO(189, 196, 207, 1),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10))),
child: const Text(
'Admin Message',
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontFamily: 'RobotoMono',
fontWeight: FontWeight.bold),
),
),
Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Color.fromRGBO(42, 46, 53, 1),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))),
child: FutureBuilder<AdminData>(
future: adminData,
builder: (context, snapshot) {
if (snapshot.hasData) {
JsonEncoder encoder = const JsonEncoder.withIndent(' ');
String message =
encoder.convert({'text': snapshot.data?.text});
return Text(
message,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
fontFamily: 'RobotoMono'),
);
} else {
JsonEncoder encoder = const JsonEncoder.withIndent(' ');
String message = encoder.convert({
"message":
"Http failure response for http://localhost:6060/api/messages/protected: 0 Unknown Error"
});
if (snapshot.hasError) {
message = snapshot.error.toString();
}
return Text(
message,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
fontFamily: 'RobotoMono'),
);
}
}))
]),
),
);
}
}

That's all it takes to integrate Flutter with an external API server that is also secured by Auth0 and to use an access token to consume protected server resources from your Flutter client application.

Conclusion

You have implemented user authentication in Flutter to identify your users, get user profile information, and control the content that your users can access by protecting routes and API resources.

This guide covered the most common authentication use case for a Flutter application: simple login and logout. However, Auth0 is an extensible and flexible identity platform that can help you achieve even more. If you have a more complex use case, check out the Auth0 Architecture Scenarios to learn more about the typical architecture scenarios we have identified when working with customers on implementing Auth0.

You'll cover advanced authentication patterns and tooling in future guides, such as using a pop-up instead of redirecting users to log in, adding permissions to ID tokens, using metadata to enhance user profiles, and much more.