Google Sign-in in Flutter (without Firebase)
Google Sign-in in Flutter (without Firebase) with NodeJS Back-End
TL;DR
This week i tried to add google sign-in to my app and it was the most frustrating task i did this Q , because most google documentations was either irrelevant or too confusing , so I’m writing this to help myself and others who would find themselves in the same situation.
steps:
- Project in Google Cloud Console ⚙️
- Flutter code 💻
- Back-End APIs 🌐
Project in Google Cloud Console
steps:
- Create a Google Cloud Project 🛠️
- Configure the OAuth consent screen 🔐
- Configure Credentials 🆔
3.0. Add google_sign_in to dependencies
3.1. Server Client ID
3.2. Web app Client ID
3.3. Android Client ID
3.4. IOS Client ID - Publishing 🚀
4.1. Add domains to Google Search Console
4.2. Request to Publish in Google Cloud Console
1. Create a Google Cloud Project
Go to the Google Cloud Console (https://console.cloud.google.com/projectcreate) and create a new project.
Once the project is created, you’ll be redirected to the Google Cloud welcome page.
From the Quick access section, select “APIs & Services”:

2. Configure the OAuth consent screen:
From the “APIs & Services” page, select “OAuth consent screen”:

and now the adventure begins 🧑🚀
If you are planning to publish your app, choose the “External” option and click “CREATE”:

Enter the required information on the OAuth consent/app information page.
Note: if your Web App has a different URL, add it in the “Authorized domains” section

In this page if you don’t need any specific data, you don’t need to change anything

If you want to publish your app to the public, you don’t have to change anything

If everything is correct then just click “BACK TO DASHBOARD”
3. Configure Credentials
From the left side bar navigate to “Credentials”:

Note: I prefer to first create the server OAuth client ID and then create the client IDs for my apps. However, there is no functional difference between them.
3.0. Add google_sign_in to dependencies
Go to the google_sign_in page on the pub.dev website:

Copy the version value next to the package name.
Open the “pubspec.yaml” file in your project’s main directory and add google_sign_in to the dependencies section:
dependencies:
...
google_sign_in: ^6.1.0
...Replace “6.1.0” with the version you copied from pub.dev
Run the “flutter pub get” command in your terminal to fetch and update the dependencies
3.1. Server Client ID
Click on “CREATE CREDENTIALS”:

Pick the “OAuth client ID” option:

Select the “Web application” type:

Enter a name for the server client ID, you can leave the “Authorized JavaScript origins” and “Authorized redirect URIs” fields blank, and click on “CREATE”.

click on “DOWNLOAD JSON”. Save this file for later use in your server and Flutter app.
3.2. Web app Client ID
Click on “CREATE CREDENTIALS”:

Pick the “OAuth client ID” option:

Select the “Web application” type:

Enter a name for the web client ID, add you web app URLs to “Authorized JavaScript origins” field, and click on “CREATE”.
Note: if you want to test your code locally make sure to add a local url, example: “http://localhost:5000”
Note: if your server is using redirect urls to verify users add the URL to “Authorized redirect URIs” field.

click on “DOWNLOAD JSON”. Save this file for later use in your Flutter app.
3.3. Android Client ID
To generate a keystore file go to your terminal and then navigate to your flutter project directory, then navigate to the android directory

Enter this command (for windows check here)
keytool -genkey -v -keystore <path-to-your-project>/android/app/androidkey.jks -keyalg RSA -keysize 2048 -validity 10000 -alias keyaliasNote: replace the “<path-to-your-project>” value by your project path
it would ask for a password , just input a password and them fill in the field based on your project (you can leave them empty)
Note: This command generates a keystore file named “androidkey.jks” in the “android/app” directory of your project. You can choose a different name and alias if you prefer.
after key was generated open the “android/app/build.gradle” file and add this part:
android {...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
}
debug {
signingConfig signingConfigs.debug
}
}...signingConfigs {
debug {
keyAlias 'keyalias'
keyPassword <your-password>
storeFile file('androidkey.jks')
storePassword <your-password>
}
}
}
In the terminal, navigate to “android” directory and run the following command:
./gradlew signingReportThen scroll to the top of the response and find this part:
Starting a Gradle Daemon (subsequent builds will be faster)> Task :app:signingReport
Variant: debug
Config: debug
Store: <path-to-your-project>/android/app/androidkey.jks
Alias: keyalias
MD5: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
SHA1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
SHA-256: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
Valid until: Thursday, September 22, 2050
----------
...
Locate and copy the “SHA1” value.
Now go back to Google Cloud Console, Click on “CREATE CREDENTIALS”:

Pick the “OAuth client ID” option:

Select the “Android” type:

Past the SHA1 value and name the Client ID
To obtain the “Package name” value, open “android/app/src/main/AndroidManifest.xml”, in the first line copy the “package” value and past it in the “Package name” field and click “CREATE”:

click on “DOWNLOAD JSON”. Save this file for later use in your Flutter app.
3.4. IOS Client ID
…i will finish this later… 🫠
Publishing
4.1. Add domains to Google Search Console
Navigate to Google Search Console:

Select “Domain” and enter you domain in the field and click “CONTINUE”

Add this TXT record in your DNS configuration and wait for couple of minutes.
Note: if you use “cloudflare.com” to manage your DNS, this part would be way much easier and faster.
Note: if you use web hosting services (such as WHC) there is a hight chance that TXT doesn’t work for you, and remember to add the record in the cPanel and not in the web hosting DNS manager.
If everything works, you will see this Pop-up:

For Cloudflare users, this page would look like this:

Click “START VERIFICATION” and will be redirected to the Cloudflare website

Click “Authorize” and Cloudflare will configure the records automatically.
If everything works, you will see this Pop-up:

Note: Repeat this process for all of your domains.
4.2. Request to Publish in Google Cloud Console
In Google Cloud navigate to “OAuth consent screen” and click “PUBLISH APP”:

Click “CONFIRM” and wait for the Google’s response Email.
Note: if you clicked “CONFIRM” before verifying your domains or you forgot to add a domain and got an Email telling you the verification failed, after adding the domains, send this email to Google’s Trust and Safety team:
Preferably send this as a reply to an Email with this Subject:
OAuth Verification Request for Project <project-name>If you couldn’t find it use this Subject:
Request for Google Cloud Project Verification and PublicationReply with this Email:
Hi,
Dear Google Trust & Safety Team,
I am writing to inform you that I have successfully completed the domain verification process in Google Search Console. As a result, I would like to request the verification and publication of my Google Cloud Project associated with the domains.
I kindly request your assistance in verifying and publishing my Google Cloud Project. I understand the importance of adhering to Google's Trust & Safety policies, and I assure you that my project complies with all the necessary guidelines and regulations.
If there are any specific steps or additional information required from my end, please let me know, and I will promptly provide all the necessary details.
Best regards,
<your-teams-name>Flutter code
Add this to your “constants.dart” file:
import 'package:flutter/foundation.dart' show kIsWeb;
...
class AppKey{
static const String googleServerClientId = '<your-server-clientID>.apps.googleusercontent.com';
}
const GOOGLE_CLIENT_ID_WEB = '<your-web-clientID>.apps.googleusercontent.com';
String GoogleClientID() {
if (kIsWeb) {
// running on the web!
return GOOGLE_CLIENT_ID_WEB
}
return '';
}Note: replace these values “<your-server-clientID>” and “<your-web-clientID>”
import 'package:get/get.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:<project-name>/core/constants.dart';
class GoogleController extends GetxController {
Rx<pageState> status = pageState.initial.obs;
late GoogleSignIn _googleSignIn;
void initAuth() {
final String googleClientId = GoogleClientID();
// this is because we only need to config the `clientId` for web app,
// android and ios are configured in their specific config files
if (googleClientId == '') {
_googleSignIn = GoogleSignIn(serverClientId: AppKey.googleServerClientId);
} else {
_googleSignIn = GoogleSignIn(
serverClientId: AppKey.googleServerClientId,
clientId: googleClientId,
);
}
}
void authWithGoogle({
required bool isSignIn,
}) async {
status(pageState.loading);
try {
/*
TODO: remove this after testing was finished,
this lets you pick your account in every login attempt
*/
await _googleSignIn.signOut();
GoogleSignInAccount? googleSignInAccount = await _googleSignIn.signIn();
GoogleSignInAuthentication? _googleAuth = await googleSignInAccount!.authentication;
if (googleSignInAccount != null) {
if (googleSignInAccount.serverAuthCode != null && googleSignInAccount.serverAuthCode!.isNotEmpty) {
print(_googleAuth.idToken.toString());
print(googleSignInAccount.email);
print(googleSignInAccount.displayName);
// TODO: here you can send the login request to your server's API
// NOTE: you only need to send the `_googleAuth.idToken.toString()`
await _googleSignIn.signOut();
status(pageState.loaded);
} else {
status(pageState.error);
}
} else {
status(pageState.error);
}
} catch (e) {
status(pageState.error);
}
}
}Note: replace the value of “<project-name>”
Back-End APIs
I wrote my auth service in nodeJS. However, there isn’t a big difference between different languages. Refer to google documentation for you specific language here:
Add the required dependency with:
npm install google-auth-libraryAdd these APIs to your user routes:
var express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const { OAuth2Client } = require('google-auth-library');
var config = require('../config/config.json');
var app = module.exports = express.Router();
// adding Helmet to enhance your Rest API's security
app.use(helmet());
app.use(
cors({
origin: config.allowedOrigin,
methods: ["GET", "POST"]
})
);
app.post(config.apiVersionPrefix + '/users/signup-google-auth', async function(req, res) {
if (!req.body.tokenId) {
return res.status(400).send({
message: "Please fill in all the fields",
});
}
const client = new OAuth2Client(config.googleAuth.ServerClientID);
const ticket = await client.verifyIdToken({
idToken: req.body.tokenId,
});
const response = ticket.getPayload();
if (response.email_verified !== true) {
return res.status(400).send({ message: 'Bad Request' });
}
if (response.iss !== 'https://accounts.google.com' && response.aud !== config.googleAuth.ServerClientID) {
return res.status(400).send({ message: 'Bad Request' });
}
// check if user already exists
var mongoRes = await usersMongoClient.findByEmail(response.email)
if (mongoRes !== null) {
return res.status(400).send({
message: "User already exists",
});
}
// TODO: check if `response.sub` is already a taken username, if `true` add a rnadom number to the end and then check again
// TODO: download the image and upload it to our server's S3 storgae and save the saved object ID instead
const newUser = new UserDomain();
newUser.Email = response.email;
newUser.Username = response.sub;
newUser.Images = [response.picture];
newUser.CreatedAt = new Date();
newUser.Fullname = response.given_name;
newUser.IsEmailVerified = true;
// save user in database
await usersMongoClient.addUser(newUser);
var mongoRes = await usersMongoClient.findByEmail(response.email)
return res.status(201).send({
id_token: authUtils.createIdToken(mongoRes.username),
access_token: authUtils.createAccessToken(mongoRes.username, mongoRes._id)
});
});
app.post(config.apiVersionPrefix + '/users/login-google-auth', async function(req, res) {
if (!req.body.tokenId) {
return res.status(400).send({
message: "Please fill in all the fields",
});
}
const client = new OAuth2Client(config.googleAuth.ServerClientID);
const ticket = await client.verifyIdToken({
idToken: req.body.tokenId,
});
const response = ticket.getPayload();
if (response.email_verified !== true) {
return res.status(400).send({ message: 'Bad Request' });
}
if (response.iss !== 'https://accounts.google.com' && response.aud !== config.googleAuth.ServerClientID) {
return res.status(400).send({ message: 'Bad Request' });
}
// check if user exists
var mongoRes = await usersMongoClient.findByEmail(response.email)
if (mongoRes === null) {
return res.status(400).send({
message: "User doesn't exists",
});
}
return res.status(201).send({
id_token: authUtils.createIdToken(mongoRes.username),
access_token: authUtils.createAccessToken(mongoRes.username, mongoRes._id)
});
});Your config file should look like this:
{
...
"googleAuth": {
"ServerClientID":"<your-server-clientID>.apps.googleusercontent.com"
}
}Resources:

IOS :
