Blog post

Getting started with Flutter authentication

2023-07-18

21 minute read

Getting started with Flutter authentication

Flutter is Google’s open-source framework to develop cross-platform applications. In this article, we will take a look at how we can implement authentication using Google sign-in to secure our application using the Supabase SDK for Flutter.

We will also dive into the deep ends of Open ID Connect sign-in to better understand how third-party sign-ins are being performed. You can check out the code of the sample in this article here.

Prerequisites

This article assumes you are comfortable with writing a basic application in Flutter. No knowledge of Supabase is required.

We will use the following tools

  • Flutter - we used v3.10.5 for this article
  • Supabase - create your account here if you do not have one
  • IDE of your choosing

What is Open ID Connect?

We will implement third-party login with Google utilizing the Open ID Connect functionality of Supabase Auth. Open ID Connect, or OIDC is a protocol built on top of OAuth 2.0 that allows third-party applications to request the users to provide some personal information, such as name or profile image, in the form of an identity token along with an access token. This identity token can then be verified and decoded by the application to obtain that personal information.

Supabase auth provides signInWithIdToken method where we can sign in a user using their ID token obtained from third-party auth providers such as Google. Upon signing a user with the signInWithIdToken method, Supabase automatically populates the content of the ID token in the Supabase user metadata for easy access to the information. We will be utilizing this feature in this example to display the user profile upon the user signing in.

In today’s example, our app will make a request to Google, obtain the identity token, and we will use it to sign the user in as well as obtain basic user information.

What we will build

We will build a simple app with a login screen and a home screen. The user is first presented with the login screen, and only after they sign in, can they proceed to the home screen. The login screen presents a login button that will kick off a third-party authentication flow to complete the sign-in. The profile screen displays user information such as the profile image or their full name.

Flutter Google sign in

Setup the Flutter project

Let’s start by creating a fresh Flutter project.


_10
flutter create myauthapp

then we can install the dependencies. Change the working directory to the newly created app directory and run the following command to install our dependencies.


_10
flutter pub add supabase_flutter flutter_appauth crypto

We will use supabase_flutter to interact with our Supabase instance. flutter_appauth will be used to implement Google login, and crypto is a library that has utility functions for encryption that we will use when performing OIDC logins.

We are done installing our dependencies. Let’s set up authentication now.

Configure Google sign-in on Supabase Auth

We will obtain client IDs for iOS and Android from the Google Cloud console, and register them to our Supabase project.

First, create your Google Cloud project here if you do not have one yet. Within your Google Cloud project, follow the Configure a Google API Console project for Android guide and Get an OAuth client ID for the iOS guide to obtain client IDs for Android and iOS respectively.

Once you have the client IDs, let’s add them to our Supabase dashboard. If you don’t have a Supabase project created yet, you can create one at database.new for free. The name is just an internal name, so we can call it “Auth” for now. Database Password will not be used in this example and can be reconfigured later, so press the Generate a password button and let Supabase generate a secure random password. No need to copy it anywhere. The region should be anywhere close to where you live, or where your users live in an actual production app.

Lastly, for the pricing plan choose the free plan that allows you to connect with all major social OAuth providers and supports up to 50,000 monthly active users.

Supabase project creation

Your project should be ready in a minute or two. Once your project is ready, you can open authentication -> Providers -> Google to set up Google auth. Toggle the Enable Sign in with Google switch first. Then add the two client IDs you obtained in your Google Cloud console to Authorized Client IDs field with a comma in between the two client IDs like this: ANDROID_CLIENT_ID,IOS_CLIENT_ID.

Supabase auth Google auth provider

We also need some Android specific settings to make flutter_appauth work. Open android/app/build.gradle and find the defaultConfig. We need to set the reversed DNS form of the Android Client ID as theappAuthRedirectScheme manifest placeholder value.


_11
...
_11
android {
_11
...
_11
defaultConfig {
_11
...
_11
manifestPlaceholders += [
_11
// *account_id* will be unique for every single app
_11
'appAuthRedirectScheme': 'com.googleusercontent.apps.*account_id*'
_11
]
_11
}
_11
}

That is it for setting up our Supabase auth to prepare for Google sign-in.

Finally, we can initialize Supabase in our Flutter application with the credentials of our Supabase instance. Update your main.dart file and add Supabase.initialize() in the main function like the following. Note that you will see some errors since the home screen is set to the LoginScreen, which we will create later.


_31
import 'package:flutter/material.dart';
_31
import 'package:myauthapp/screens/login_screen.dart';
_31
import 'package:supabase_flutter/supabase_flutter.dart';
_31
_31
void main() async {
_31
/// TODO: update Supabase credentials with your own
_31
await Supabase.initialize(
_31
url: 'YOUR_SUPABASE_URL',
_31
anonKey: 'YOUR_ANON_KEY',
_31
);
_31
runApp(const MyApp());
_31
}
_31
_31
final supabase = Supabase.instance.client;
_31
_31
class MyApp extends StatelessWidget {
_31
const MyApp({super.key});
_31
_31
@override
_31
Widget build(BuildContext context) {
_31
return MaterialApp(
_31
debugShowCheckedModeBanner: false,
_31
title: 'Flutter Auth',
_31
theme: ThemeData(
_31
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
_31
useMaterial3: true,
_31
),
_31
home: const LoginScreen(),
_31
);
_31
}
_31
}

You can find your Supabase URL and Anon key in Settings -> API from your Supabase dashboard.

Supabase credentials

Create the Login Screen

We will have two screens for this app, LoginScreen and ProfileScreen. LoginScreen presents a single sign-in button for the user to perform Google sign-in. Create a lib/screens/login_screen.dart file add add the following.


_127
import 'dart:convert';
_127
import 'dart:io';
_127
import 'dart:math';
_127
_127
import 'package:crypto/crypto.dart';
_127
import 'package:flutter/material.dart';
_127
import 'package:flutter_appauth/flutter_appauth.dart';
_127
import 'package:myauthapp/main.dart';
_127
import 'package:myauthapp/screens/profile_screen.dart';
_127
import 'package:supabase_flutter/supabase_flutter.dart';
_127
_127
class LoginScreen extends StatefulWidget {
_127
const LoginScreen({super.key});
_127
_127
@override
_127
State<LoginScreen> createState() => _LoginScreenState();
_127
}
_127
_127
class _LoginScreenState extends State<LoginScreen> {
_127
@override
_127
void initState() {
_127
_setupAuthListener();
_127
super.initState();
_127
}
_127
_127
void _setupAuthListener() {
_127
supabase.auth.onAuthStateChange.listen((data) {
_127
final event = data.event;
_127
if (event == AuthChangeEvent.signedIn) {
_127
Navigator.of(context).pushReplacement(
_127
MaterialPageRoute(
_127
builder: (context) => const ProfileScreen(),
_127
),
_127
);
_127
}
_127
});
_127
}
_127
_127
/// Function to generate a random 16 character string.
_127
String _generateRandomString() {
_127
final random = Random.secure();
_127
return base64Url.encode(List<int>.generate(16, (_) => random.nextInt(256)));
_127
}
_127
_127
@override
_127
Widget build(BuildContext context) {
_127
return Scaffold(
_127
appBar: AppBar(
_127
title: const Text('Login'),
_127
),
_127
body: Center(
_127
child: ElevatedButton(
_127
onPressed: () async {
_127
const appAuth = FlutterAppAuth();
_127
_127
// Just a random string
_127
final rawNonce = _generateRandomString();
_127
final hashedNonce =
_127
sha256.convert(utf8.encode(rawNonce)).toString();
_127
_127
/// TODO: update the iOS and Android client ID with your own.
_127
///
_127
/// Client ID that you registered with Google Cloud.
_127
/// You will have two different values for iOS and Android.
_127
final clientId =
_127
Platform.isIOS ? 'IOS_CLIENT_ID' : 'ANDROID_CLIENT_ID';
_127
_127
/// Set as reversed DNS form of Google Client ID + `:/` for Google login
_127
final redirectUrl = '${clientId.split('.').reversed.join('.')}:/';
_127
_127
/// Fixed value for google login
_127
const discoveryUrl =
_127
'https://accounts.google.com/.well-known/openid-configuration';
_127
_127
// authorize the user by opening the concent page
_127
final result = await appAuth.authorize(
_127
AuthorizationRequest(
_127
clientId,
_127
redirectUrl,
_127
discoveryUrl: discoveryUrl,
_127
nonce: hashedNonce,
_127
scopes: [
_127
'openid',
_127
'email',
_127
'profile',
_127
],
_127
),
_127
);
_127
_127
if (result == null) {
_127
throw 'No result';
_127
}
_127
_127
// Request the access and id token to google
_127
final tokenResult = await appAuth.token(
_127
TokenRequest(
_127
clientId,
_127
redirectUrl,
_127
authorizationCode: result.authorizationCode,
_127
discoveryUrl: discoveryUrl,
_127
codeVerifier: result.codeVerifier,
_127
nonce: result.nonce,
_127
scopes: [
_127
'openid',
_127
'email',
_127
],
_127
),
_127
);
_127
_127
final idToken = tokenResult?.idToken;
_127
_127
if (idToken == null) {
_127
throw 'No idToken';
_127
}
_127
_127
await supabase.auth.signInWithIdToken(
_127
provider: Provider.google,
_127
idToken: idToken,
_127
nonce: rawNonce,
_127
);
_127
},
_127
child: const Text('Google login'),
_127
),
_127
),
_127
);
_127
}
_127
}

In terms of UI, this page is very simple, it just has a basic Scaffold with an AppBar, and has a button right in the middle of the body. Upon pressing the button, Google sign in flow starts. The user is presented with a Google authentication screen where they will complete the consent to allow our application to sign the user in using a Google account, as well as allow us to view some personal information.

Google sign in

Let’s break down what is going on within the onPressed callback of the sign in button.

First, we are generating a nonce-,nonce,-String%20value%20used), which is essentially just a random string. This string is later passed to Google after being hashed to verify that the ID token has not been tampered to prevent a man-in-the-middle attack.


_10
// Random string to verify the integrity of the ID Token
_10
final rawNonce = _generateRandomString();
_10
final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();

clientId and applicationId are the app-specific values. These will be used to the authentication request to Google later on.


_10
/// Client ID that you registered with Google Cloud.
_10
/// You will have two different values for iOS and Android.
_10
final clientId = Platform.isIOS ? 'IOS_CLIENT_ID' : 'ANDROID_CLIENT_ID';

redirectUrl is the URL at which the user will be redirected after a successful authentication request. For Google sign-in, we can set it to the reversed DNS form of the client ID followed by :/. discoveryUrl is a URL provided by Google that contains information about their Open ID configuration.


_10
/// Set as reversed DNS form of Google Client ID + `:/` for Google login
_10
final redirectUrl = '${clientId.split('.').reversed.join('.')}:/';
_10
_10
/// Fixed value for google login
_10
const discoveryUrl = 'https://accounts.google.com/.well-known/openid-configuration';

Then we are sending an authorization request to Google. This is where the user is taken to Google’s page to perform sign in and consent our app to obtain some personal information. Note that we are requesting three scopes here. openid and email are required by Supabase auth to complete the sign-in process. profile is not required by Supabase auth, but we are requesting it to display some profile information on the profile screen later on. We do not actually obtain the requested information in this step though. All we are doing is requesting an access token that has permission to obtain the personal information we have requested.


_14
// Authorize the user by opening the concent page
_14
final result = await appAuth.authorize(
_14
AuthorizationRequest(
_14
clientId,
_14
redirectUrl,
_14
discoveryUrl: discoveryUrl,
_14
nonce: hashedNonce,
_14
scopes: [
_14
'openid',
_14
'email',
_14
'profile',
_14
],
_14
),
_14
);

Using the authorization token obtained in the previous step, we make the final request to Google’s auth server to obtain the personal information we asked for earlier. We get an ID token in return, which contains the personal information.


_16
// Request the access and id token to google
_16
final tokenResult = await appAuth.token(
_16
TokenRequest(
_16
clientId,
_16
redirectUrl,
_16
authorizationCode: result.authorizationCode,
_16
discoveryUrl: discoveryUrl,
_16
codeVerifier: result.codeVerifier,
_16
nonce: result.nonce,
_16
scopes: [
_16
'openid',
_16
'email',
_16
'profile',
_16
],
_16
),
_16
);

And lastly, we pass the ID token we obtained from Google to Supabase to complete the sign-in on Supabase auth. Once the user is signed in, the auth state listener in the initState fires and takes the user to the ProfileScreen.


_10
await supabase.auth.signInWithIdToken(
_10
provider: Provider.google,
_10
idToken: idToken,
_10
nonce: rawNonce,
_10
);

Create the Profile Screen

The ProfileScreen will be just a simple UI presenting some of the information we obtained in the LoginPage. We can access the user data with supabase.auth.currentUser, where Supabase has saved the personal information in a property called userMetadata. In this example, we are displaying the avatar_url and full_name to display a basic profile page. Create a lib/screens/profile_screen.dart file and add the following.


_54
import 'package:flutter/material.dart';
_54
import 'package:myauthapp/main.dart';
_54
import 'package:myauthapp/screens/login_screen.dart';
_54
_54
class ProfileScreen extends StatelessWidget {
_54
const ProfileScreen({super.key});
_54
_54
@override
_54
Widget build(BuildContext context) {
_54
final user = supabase.auth.currentUser;
_54
final profileImageUrl = user?.userMetadata?['avatar_url'];
_54
final fullName = user?.userMetadata?['full_name'];
_54
return Scaffold(
_54
appBar: AppBar(
_54
title: const Text('Profile'),
_54
actions: [
_54
TextButton(
_54
onPressed: () async {
_54
await supabase.auth.signOut();
_54
if (context.mounted) {
_54
Navigator.of(context).pushReplacement(
_54
MaterialPageRoute(builder: (context) => const LoginScreen()),
_54
);
_54
}
_54
},
_54
child: const Text('Sign out'),
_54
)
_54
],
_54
),
_54
body: Center(
_54
child: Column(
_54
mainAxisSize: MainAxisSize.min,
_54
children: [
_54
if (profileImageUrl != null)
_54
ClipOval(
_54
child: Image.network(
_54
profileImageUrl,
_54
width: 100,
_54
height: 100,
_54
fit: BoxFit.cover,
_54
),
_54
),
_54
const SizedBox(height: 16),
_54
Text(
_54
fullName ?? '',
_54
style: Theme.of(context).textTheme.headlineMedium,
_54
),
_54
const SizedBox(height: 32),
_54
],
_54
),
_54
),
_54
);
_54
}
_54
}

And with that, we now have a basic working personalized application that utilizes Google sign-in.

Flutter Google sign in app

Conclusion

In this post, we learned how to implement authentication in a Flutter application using Google sign-in and the Supabase SDK for Flutter. We also delved into the Open ID Connect functionality, which allows third-party sign-ins and the retrieval of personal information through identity tokens.

You can also check out the Flutter reference documents to see how you can use supabase-flutter to implement a Postgres database, Storage, Realtime, and more.

More Flutter and Supabase resources

Share this article

Last post

Supabase Launch Week 8 Hackathon

25 July 2023

Next post

pgvector 0.4.0 performance

13 July 2023

Related articles

Offline-first React Native Apps with Expo, WatermelonDB, and Supabase

Supabase Beta September 2023

Dynamic Table Partitioning in Postgres

Supabase Beta August 2023

pgvector v0.5.0: Faster semantic search with HNSW indexes

Build in a weekend, scale to millions