How to build a Google sign-in in Flutter without Firebase

Raw Text

Florian Abel 路 Follow

Published in CodeX 路 7 min read 路 Jul 25, 2022

--

3

Listen

Share

Let your Flutter App users authenticate with GoogleOAuth2, using Flutter AppAuth , and connect with your backend. Not limited to Firebase.

TL;DR: The GitHub repo for this project can be found here .

freestocks

Unsplash

Introduction

This article is a step-by-step guide to implementing social authentication with GoogleOAuth2 in your Flutter Application, using Flutter AppAuth . After successful authentication, you will receive an access token to gain access to your backend. I have written another article explaining how to handle the backend side with a Django Application. However, you are free to use every backend you feel most comfortable with.

Authentication with OAuth2

Authentication with OAuth2 is based on different flows, depending on the specific setup you are using for your application. The basics and which flow to choose for your project are covered well in these two articles from Aaron Parecki and Auth0 .

Authorization Code Flow with Proof Key (PKCE)

In this example, we are using the Authorization Code Flow with Proof Key for Code Exchange (PKCE) , which is the recommended way when public clients, like Single-Page and Mobile Applications, request an access token from an OAuth2 provider. The authentication process with the provider happens on the client. After successful authentication, the client exchanges the received access token for an authentication token from the backend. With this token, the client can then use restricted endpoints provided by the backend.

Further details about this flow are well explained in this Auth0 article . The exception in our case is, that we are connecting directly to GoogleOAuth2, instead of Auth0.

Registering your Application with GoogleOAuth2

In order to let use Google鈥檚 social login functionality, your application needs to be registered at the Google Cloud Console .

Create a new project

Define your OAuth Consent Screen

Create credentials

Creating the credentials

Note: We need to create two sets of credentials, one for iOS and one for Android.

We are starting at the Credentials-Page :

Select + Create Credentials at the top of the Page

Choose OAuth client ID

iOS or Android as Application type

Name it as you wish

Fill in the Bundle ID (iOS) or Package Name (Android)

Create and fill in the SHA-1 certificate fingerprint (Only for Android)

Copy and save the Client ID

Bundle ID and Package Name The Bundle ID and Package Name are unique identifiers, chosen by you. They can never be the same for two applications inside the Apple App Store or Google Play Store, respectively. A common format is com.company.appname , e.g. com.socialauthexample.mobile_app .

Note: Do not use hyphens (-) or underscores (_), or you might run into errors.

SHA-1 certificate fingerprint The SHA-1 certificate fingerprint is a unique signing key, that differs in development and production. To obtain it for your development environment you can follow these steps. Check this article for more information and instructions on getting the fingerprint for production.

For Mac:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

For Windows:

keytool -list -v -keystore C:\Users\\.android\debug.keystore -alias androiddebugkey

Note: Make sure to run Android Studio at least once before, otherwise you might receive an error.

Set up your Application

Create base application

flutter create --org com.socialauthexample mobile_app

Install required dependencies

Three packages are required for our social authentication solution and secure storage of tokens.

flutter_appauth Well-maintained Flutter package, wrapping around AppAuth , providing authentication and authorization for users.

http Future-based library to perform HTTP requests, developed by the Dart team.

flutter_secure_storage A Flutter plugin to store data in secure storage.

flutter pub add flutter_appauth && flutter pub add http && flutter pub add flutter_secure_storage

Providing credentials to our application

In order to successfully authenticate our users, we need to provide client ids (previously obtained) and redirect URLs to our application.

Redirect URL Format

The redirect or callback URL is the address where the OAuth2 server will send the user after successful authentication. Google has specific requirements on the format. The format we are using in this example is based on your client ids: com.googleusercontent.apps.:redirect_uri_path

iOS

com.googleusercontent.apps.:/oauthredirect

Android

com.googleusercontent.apps.:/oauthredirect

Using variables in your codebase We are using a helper file lib/helper/constants.dart to store all constants for our application and to pass on the correct credentials, depending on the platform the application is running on.

Note: For security reasons, credentials should never be part of your source code. The most secure way to provide them is to use environment variables, with e.g. flutter_config , but this goes beyond the scope of this article.

Setting up URL Schemes

Besides providing the redirect URLs for Flutter AppAuth, we need to let our Applications know about the required URL schemes.

CFBundleURLSchemes (iOS) To configure the URL scheme for iOS, we are adding a new dict tag CFBundleURLTypes to the /ios/Runner/Info.plist file.

appAuthRedirectScheme (Android) For Android, we need to adjust the following entries in the /android/app/build.gradle file.

Note: Make sure that the URL schemes and redirect URLs match, otherwise you might not get redirected after authentication.

Building the Application

Our application will show a login screen for unauthenticated users that gets replaced with our main screen after successful authentication. Furthermore, previously logged-in users stay logged in after restarting the Application.

We are creating a user interface with some basic logic in our lib/main.dart file, three additional screens, and a service to handle authentication, lib/services/authentication_service.dart .

Application Interface

main.dart Our main.dart holds the base structure for our Application. Two flags, isLoading and isLoggedIn , represent the current authentication status and if some action is currently loading. Their state is handled by three methods ( setLoadingState , setAuthenticatedState and setUnauthenticatedState ) and the builder provides the correct screen, LoadingScreen MainScreen , AuthenticationScreen , accordingly.

On Application initialization, our app checks if a user is currently authenticated and sets the state accordingly (Details on how that happens will be discussed further below). If a user is already authenticated, the MainScreen is provided, otherwise, our user gets to see the AuthenticationScreen . Furthermore, we are passing the appropriate functions as callbacks to change the state later on.

loading_screen.dart Our LoadingScreen contains a CircularProgressIndicator , which continues spinning while our app waits for any actions to finish.

authentication_screen.dart Inside our AuthenticationScreen we provide a Button to initiate the authentication process, which sets the app in a loading state and calls our authentication service's login method. On success our app switches to the authenticated state, presenting the MainScreen , otherwise, it goes back into the unauthenticated state, showing the AuthenticationScreen , again.

main_screen.dart Our main screen currently only has one function, logging the user out again. When clicking the Button our logout method is called, which in turn calls the authentication service and on success, changes the state of the Application to unauthenticated.

Authentication Service

The true magic is within our authentication service. It checks for an already existing refresh token in our secure storage, performs the authentication and refresh processes, and saves and deletes our obtained tokens.

The basics The authentication service is built as a Singleton, meaning that only one instance of it can exist at all times. The constructor is private and can only be called from within the class. In order to access the service, we always have to call the instance AuthService.instance.someMethod() . The service provides three public methods and one private for internal use.

Initialization The first method, called by the Application in main.dart is initAuth() . It checks for an existing refresh token and tries to obtain a fresh access token with it. If that works, we have a logged-in user and our Application goes into the authenticated state. If not, either no user has logged in yet, or the token expired. In both cases, our App goes into an unauthenticated state.

Login Once a user clicks the login button, our login() method is called and starts the authentication process. A window for a Google sign-in is presented and the user can authenticate. If everything goes smoothly, a result with an access and a refresh token is returned.

Logout Upon logout, the saved refresh token is deleted and our Application switches back to an unauthenticated state.

Handling authentication results _handleAuthResult is the only private method in our service. It is responsible to take care of the responses we are getting from Google upon authentication or when trying to obtain a fresh access token with the help of an existing refresh token. If the result is valid we update the refresh token. Afterward, we are exchanging the obtained access token for an authentication token from our own backend and save it for further use.

Note: Since this goes way beyond the scope of this article I laid out a quick example for you in the uncommented section of the method. You can implement it with your own backend as you need. For this scenario, we are just assuming that the backend returned a valid token that we can use.

Summary

Now you have a basic social authentication schema to use Google sign-in. It can easily be changed to different providers like Facebook or Twitter, by using different credentials. Afterward, your backend will handle the obtained access tokens and provide you with a fresh authentication token. For a Django backend example, check my other article .

Feel free to reach out in the comments or follow me on Twitter ( @florian_abel_ ). I鈥檇 be happy to hear your thoughts, questions, and experiences.

Single Line Text

Florian Abel 路 Follow. Published in CodeX 路 7 min read 路 Jul 25, 2022. -- 3. Listen. Share. Let your Flutter App users authenticate with GoogleOAuth2, using Flutter AppAuth , and connect with your backend. Not limited to Firebase. TL;DR: The GitHub repo for this project can be found here . freestocks. Unsplash. Introduction. This article is a step-by-step guide to implementing social authentication with GoogleOAuth2 in your Flutter Application, using Flutter AppAuth . After successful authentication, you will receive an access token to gain access to your backend. I have written another article explaining how to handle the backend side with a Django Application. However, you are free to use every backend you feel most comfortable with. Authentication with OAuth2. Authentication with OAuth2 is based on different flows, depending on the specific setup you are using for your application. The basics and which flow to choose for your project are covered well in these two articles from Aaron Parecki and Auth0 . Authorization Code Flow with Proof Key (PKCE) In this example, we are using the Authorization Code Flow with Proof Key for Code Exchange (PKCE) , which is the recommended way when public clients, like Single-Page and Mobile Applications, request an access token from an OAuth2 provider. The authentication process with the provider happens on the client. After successful authentication, the client exchanges the received access token for an authentication token from the backend. With this token, the client can then use restricted endpoints provided by the backend. Further details about this flow are well explained in this Auth0 article . The exception in our case is, that we are connecting directly to GoogleOAuth2, instead of Auth0. Registering your Application with GoogleOAuth2. In order to let use Google鈥檚 social login functionality, your application needs to be registered at the Google Cloud Console . Create a new project. Define your OAuth Consent Screen. Create credentials. Creating the credentials. Note: We need to create two sets of credentials, one for iOS and one for Android. We are starting at the Credentials-Page : Select + Create Credentials at the top of the Page. Choose OAuth client ID. iOS or Android as Application type. Name it as you wish. Fill in the Bundle ID (iOS) or Package Name (Android) Create and fill in the SHA-1 certificate fingerprint (Only for Android) Copy and save the Client ID. Bundle ID and Package Name The Bundle ID and Package Name are unique identifiers, chosen by you. They can never be the same for two applications inside the Apple App Store or Google Play Store, respectively. A common format is com.company.appname , e.g. com.socialauthexample.mobile_app . Note: Do not use hyphens (-) or underscores (_), or you might run into errors. SHA-1 certificate fingerprint The SHA-1 certificate fingerprint is a unique signing key, that differs in development and production. To obtain it for your development environment you can follow these steps. Check this article for more information and instructions on getting the fingerprint for production. For Mac: keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android. For Windows: keytool -list -v -keystore C:\Users\\.android\debug.keystore -alias androiddebugkey. Note: Make sure to run Android Studio at least once before, otherwise you might receive an error. Set up your Application. Create base application. flutter create --org com.socialauthexample mobile_app. Install required dependencies. Three packages are required for our social authentication solution and secure storage of tokens. flutter_appauth Well-maintained Flutter package, wrapping around AppAuth , providing authentication and authorization for users. http Future-based library to perform HTTP requests, developed by the Dart team. flutter_secure_storage A Flutter plugin to store data in secure storage. flutter pub add flutter_appauth && flutter pub add http && flutter pub add flutter_secure_storage. Providing credentials to our application. In order to successfully authenticate our users, we need to provide client ids (previously obtained) and redirect URLs to our application. Redirect URL Format. The redirect or callback URL is the address where the OAuth2 server will send the user after successful authentication. Google has specific requirements on the format. The format we are using in this example is based on your client ids: com.googleusercontent.apps.:redirect_uri_path. iOS. com.googleusercontent.apps.:/oauthredirect. Android. com.googleusercontent.apps.:/oauthredirect. Using variables in your codebase We are using a helper file lib/helper/constants.dart to store all constants for our application and to pass on the correct credentials, depending on the platform the application is running on. Note: For security reasons, credentials should never be part of your source code. The most secure way to provide them is to use environment variables, with e.g. flutter_config , but this goes beyond the scope of this article. Setting up URL Schemes. Besides providing the redirect URLs for Flutter AppAuth, we need to let our Applications know about the required URL schemes. CFBundleURLSchemes (iOS) To configure the URL scheme for iOS, we are adding a new dict tag CFBundleURLTypes to the /ios/Runner/Info.plist file. appAuthRedirectScheme (Android) For Android, we need to adjust the following entries in the /android/app/build.gradle file. Note: Make sure that the URL schemes and redirect URLs match, otherwise you might not get redirected after authentication. Building the Application. Our application will show a login screen for unauthenticated users that gets replaced with our main screen after successful authentication. Furthermore, previously logged-in users stay logged in after restarting the Application. We are creating a user interface with some basic logic in our lib/main.dart file, three additional screens, and a service to handle authentication, lib/services/authentication_service.dart . Application Interface. main.dart Our main.dart holds the base structure for our Application. Two flags, isLoading and isLoggedIn , represent the current authentication status and if some action is currently loading. Their state is handled by three methods ( setLoadingState , setAuthenticatedState and setUnauthenticatedState ) and the builder provides the correct screen, LoadingScreen MainScreen , AuthenticationScreen , accordingly. On Application initialization, our app checks if a user is currently authenticated and sets the state accordingly (Details on how that happens will be discussed further below). If a user is already authenticated, the MainScreen is provided, otherwise, our user gets to see the AuthenticationScreen . Furthermore, we are passing the appropriate functions as callbacks to change the state later on. loading_screen.dart Our LoadingScreen contains a CircularProgressIndicator , which continues spinning while our app waits for any actions to finish. authentication_screen.dart Inside our AuthenticationScreen we provide a Button to initiate the authentication process, which sets the app in a loading state and calls our authentication service's login method. On success our app switches to the authenticated state, presenting the MainScreen , otherwise, it goes back into the unauthenticated state, showing the AuthenticationScreen , again. main_screen.dart Our main screen currently only has one function, logging the user out again. When clicking the Button our logout method is called, which in turn calls the authentication service and on success, changes the state of the Application to unauthenticated. Authentication Service. The true magic is within our authentication service. It checks for an already existing refresh token in our secure storage, performs the authentication and refresh processes, and saves and deletes our obtained tokens. The basics The authentication service is built as a Singleton, meaning that only one instance of it can exist at all times. The constructor is private and can only be called from within the class. In order to access the service, we always have to call the instance AuthService.instance.someMethod() . The service provides three public methods and one private for internal use. Initialization The first method, called by the Application in main.dart is initAuth() . It checks for an existing refresh token and tries to obtain a fresh access token with it. If that works, we have a logged-in user and our Application goes into the authenticated state. If not, either no user has logged in yet, or the token expired. In both cases, our App goes into an unauthenticated state. Login Once a user clicks the login button, our login() method is called and starts the authentication process. A window for a Google sign-in is presented and the user can authenticate. If everything goes smoothly, a result with an access and a refresh token is returned. Logout Upon logout, the saved refresh token is deleted and our Application switches back to an unauthenticated state. Handling authentication results _handleAuthResult is the only private method in our service. It is responsible to take care of the responses we are getting from Google upon authentication or when trying to obtain a fresh access token with the help of an existing refresh token. If the result is valid we update the refresh token. Afterward, we are exchanging the obtained access token for an authentication token from our own backend and save it for further use. Note: Since this goes way beyond the scope of this article I laid out a quick example for you in the uncommented section of the method. You can implement it with your own backend as you need. For this scenario, we are just assuming that the backend returned a valid token that we can use. Summary. Now you have a basic social authentication schema to use Google sign-in. It can easily be changed to different providers like Facebook or Twitter, by using different credentials. Afterward, your backend will handle the obtained access tokens and provide you with a fresh authentication token. For a Django backend example, check my other article . Feel free to reach out in the comments or follow me on Twitter ( @florian_abel_ ). I鈥檇 be happy to hear your thoughts, questions, and experiences.