Google Sign-In using Firebase in React Native - firebase

Note the question might be long because of the need for explanation otherwise it might be very vague and lead to same old answers.
I am having problem in creating a Google Sign-In page in React Native using firebase. Based on firebase documentation:
With the updates in the 3.1.0 SDK, almost all of the JavaScript SDK’s
functionality should now work smoothly in React Native. But there are
a couple caveats:
"Headful" auth methods such as signInWithPopup(), signInWithRedirect(), linkWithPopup(), and linkWithRedirect() do not
work in React Native (or Cordova, for that matter). You can still sign
in or link with a federated provider by using signInWithCredential()
with an OAuth token from your provider of choice.
which means I cannot use following code in my React Native project:
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
Then with some googling and searching over dozens of stackoverflow, I found following way to use use Google SignIn using Firebase API
in React Native project as:
const provider = firebase.auth.GoogleAuthProvider;
const credential = provider.credential(token);
firebase.auth().signInWithCredential(credential)
.then((data) => {
console.log('SUCCESS', data);
})
.catch((error) => {
console.log('ERROR', error)
});
Now in just above code, you might have noticed token in following line:
const credential = provider.credential(token);
So based on firebase documentation, token is obtained as follows:
// `googleUser` from the onsuccess Google Sign In callback.
var token = googleUser.getAuthResponse().id_token;
So my question is how do I obtain that token using GoogleUser object or whatever it is in React Native? Or is there another way?

I am going to assume you've added GoogleSignin to your project. If not, you can find a pretty good instruction here
The callback that Google provides has an item, called idToken, which can be used to login via google into your firebase. Once you have returned that object from Google Signin, EG
GoogleSignin.signIn()
.then((user) => { this.loginWithGoogle(user) });
You can simply use this user object's idToken as a credential,
loginWithGoogle = (user) => {
var credential = firebase.auth.GoogleAuthProvider.credential(user.idToken);
and then log into firebase using that credential
firebase.auth().signInWithCredential(credential).then(u => {
//blah blah bleep
}
Hope this helps.

Related

How do you send a Firebase verification email (so that a user can verify their email) to a user in React Native?

I am making a React Native app where you can create accounts via Firebase authentication. I am try to set up a way to verify your email. I thought the code below would work, but it doesn't work. Anyone know the solution to this:
Code:
async function handleSignUp() {
await createUserWithEmailAndPassword(auth, email, password)
.then((userCredentials) => {
let user = auth.currentUser;
const actionCodeSettings = {
url: `${configData.BASE_URL}/sign-in/?email=${user.email}`,
};
auth.currentUser.sendEmailVerification(actionCodeSettings).then(() => {
alert("Verification link has been sent to your email");
});
})
.catch((error) => alert(error.message));
}
You're using the v9 or later SDK, which uses a modular syntax like you see here: await createUserWithEmailAndPassword(auth, email, password).
Similar to createUserWithEmailAndPassword, sendEmailVerification is a top-level function too in this API, so the syntax is:
sendEmailVerification(auth.currentUser, actionCodeSettings).then(() => {
alert("Verification link has been sent to your email");
});
This API is quite well covered in the Firebase documentation on sending an email verification link, so I recommend keeping that handy while coding.

What Sign in method to use best?

We are having a flutter app (ios, android, web), where users are signed in via username & password.
We are also using google firebase since its powerful and easy to integrate.
The username and password mainly belongs to the website where we are gathering data at. (As example - If they use the website without the app, and they change the password, after that he wont be able to login to the app)
Now the mentionned websites host is giving us API access, login via OpenId to get the access token for the API. Because we are a safety risk since we store the passwort of the users too!
For the API access we dont really need to store Username and password of the user, since they are redundant anyway. But if we want to add a feature (for example message sending or further data storage) we need to have the user signed in into firebase.
Upt to now we are using for (first) signin the following snippet:
firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
and for already signed in users :
firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
Notice that similar credentials are also using to login on the API. (Since the user is there already registered)
How can we login on firebase with said information without asking twice for a password ond username (once for us, and once for the API) ?
We already tried :
await firebaseAuth.signInWithCustomToken(token)
with the jwl token from the OpenId, of course it did not work because the token did not contain the uid reference.
SOLUTION
Create a Firebase Cloud Function just like described in Firebase Cloud Functions.
Be aware that if you want to create a customtoken, the cloud functions need rights. On initializeApp(..)
admin.initializeApp({
serviceAccountId: '{App_Name}#appspot.gserviceaccount.com',
});
So the correct service account has to be selected, you also have to give him the rights to generate tokens. (See => Stackoverflow Question
The Cloud Function does then look the following way :
export const functionName= functions.https.onRequest(async (request, response) => {
const id = request.query.id;
const passcode = request.query.passcode; // not really needed
// add other passcodes for different authentications
if (passcode == "{COMPARE SOMETHING}") {
await admin.auth().createCustomToken(id).then((customToken) => {
response.status(200).send({
'id': id,
'customToken': customToken
});
}).catch((error) => {
response.status(500).send({
'ErrorMessage': "No token could be generated",
"Error": error
});
});
}
else {
response.status(500).send({
'ErrorMessage': "Passcode wrong"
});
}
});
On the other hand we have the code on the mobile app :
// Get JWT Token
Map<String, dynamic> jwtpayload =
Jwt.parseJwt(response_decoded['id_token']); // use import 'package:jwt_decode/jwt_decode.dart';
final queryParameters = {
'id': jwtpayload ['sub'],
'passcode': 'APassCode',
};
final uri = Uri.https('us-central1-{yourApp}.cloudfunctions.net',
'/{functionName}', queryParameters);
final cloud_function_api_call = await client.post(uri);
var decoded_cloud_function_api_call =
jsonDecode(cloud_function_api_call.body);
And at the end :
await firebaseAuth.signInWithCustomToken(
decoded_cloud_function_api_call['customToken']);
I hope it helps others facing a similar issue.

How to sign in with Apple and Google credentials in react-native and supabase

I have been trying to implement sign in with google and apple using the following libraries and supabase in bare react-native projects.
react-native-apple-authentication
react-native-google-signin
They work very well with firebase as described here using firebase sdk.
The concept is fairly simple, the native sdk for both apple and google sign return user credential after a successful sign in . Here is an example from https://rnfirebase.io/auth/social-auth
With google credential
import auth from '#react-native-firebase/auth';
import { GoogleSignin } from '#react-native-google-signin/google-signin';
async function onGoogleButtonPress() {
// Get the users ID token
const { idToken } = await GoogleSignin.signIn();
// Create a Google credential with the token
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
// Sign-in the user with the credential
return auth().signInWithCredential(googleCredential);
}
With apple credential
import auth from '#react-native-firebase/auth';
import { appleAuth } from '#invertase/react-native-apple-authentication';
async function onAppleButtonPress() {
// Start the sign-in request
const appleAuthRequestResponse = await appleAuth.performRequest({
requestedOperation: appleAuth.Operation.LOGIN,
requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
});
// Ensure Apple returned a user identityToken
if (!appleAuthRequestResponse.identityToken) {
throw 'Apple Sign-In failed - no identify token returned';
}
// Create a Firebase credential from the response
const { identityToken, nonce } = appleAuthRequestResponse;
const appleCredential = auth.AppleAuthProvider.credential(identityToken, nonce);
// Sign the user in with the credential
return auth().signInWithCredential(appleCredential);
}
I have gone through the auth provided by supabase here supabase-js auth
The closest thing I can find related to the implementation above is the use of a refresh-token as shown here
Sign in using a refresh token
// An example using Expo's `AuthSession`
const redirectUri = AuthSession.makeRedirectUri({ useProxy: false });
const provider = 'google';
AuthSession.startAsync({
authUrl: `https://MYSUPABASEAPP.supabase.co/auth/v1/authorize?provider=${provider}&redirect_to=${redirectUri}`,
returnUrl: redirectUri,
}).then(async (response: any) => {
if (!response) return;
const { user, session, error } = await supabase.auth.signIn({
refreshToken: response.params?.refresh_token,
});
});
The example above uses a refreshToken parameter auth.sign() to create a session for a user who is already registered.
Correct me if am wrong. According to the auth documentation there is no method that allows one to create a user using an auth credential. This is because of how gotrue social auth is implemented. The only solution I can think of as now is to move the entire auth logic to the backend and handle it on the web. This is not elegant at all since it will have to involve ugly deep-linking or webview implementations to use supabase session and access token.
Given the scenario above, is there way of using native social sdk in react-native to create a new user account in supabase using a user credential without having to force them use an email link, password or web ? Any ideas , answers or corrections are welcome.
At the time of writing, Signing in with third party credentials is not implemented in gotrue(Module used for auth in supabase) yet. If you have a native app, flutter app and react-native app that needs social auth using credentials follow up on this WIP:PR. For further information refer to Proof Key for Code Exchange (PKCE).

Firebase Auth in a Flutter app with WebView

I'm developing a Flutter app that uses Firebase Auth to handle authentication. However, some sections of the app use a WebView that shows content from the web version (which also uses Firebase Auth). My question is to how ensure that users that have signed in to the app are also signed in within the WebView.
There's nothing built into Firebase to automatically synchronize the authentication state from native code into a web view that is opened from this native code.
It should be possible to pass the ID token from the native code to the web view and use it there, but I've never tried that myself.
Some relevant links that I found:
How to pass Firebase Auth token to webView and register for notifications on Android (describes the same problem, but then with Android - and unfortunately without an answer)
Is there a way to keep the user signed in between native code and a WebView using Firebase Auth on Android? (unfortunately also without an answer)
Webviews and social authentication with React Native (blog post describing a workaround for this type of problem with Facebook login and react native)
How to do Authentication on native and pass to webView? (also with React Native, but this answer looks promising)
capacitor-firebase-auth npm module (plugin for Capacitor framework that propagates the token from native code to web view)
None of these are pre-built solutions for Flutter + WebView, but I hope that combined they allow you to build something yourself. If you do: please share it! :)
Here is solution for Firebase Auth with WebView in React Native:
import React from 'react'
import WebView from 'react-native-webview'
export default function HomeScreen(props) {
// props.user represents firebase user
const apiKey = props.user.toJSON().apiKey
const authJS = `
if (!("indexedDB" in window)) {
alert("This browser doesn't support IndexedDB")
} else {
let indexdb = window.indexedDB.open('firebaseLocalStorageDb', 1)
indexdb.onsuccess = function() {
let db = indexdb.result
let transaction = db.transaction('firebaseLocalStorage', 'readwrite')
let storage = transaction.objectStore('firebaseLocalStorage')
const request = storage.put({
fbase_key: "firebase:authUser:${apiKey}:[DEFAULT]",
value: ${JSON.stringify(props.user.toJSON())}
});
}
}
`
return <WebView
injectedJavaScriptBeforeContentLoaded={authJS}
source={{
uri: 'http://192.168.1.102:3000',
baseUrl: 'http://192.168.1.102:3000',
}}
/>
}
Similar logic might be required in Flutter (JS injection).
High Level
From Flutter mobile client, sign in to Firebase
Generate a unique Firestore document for the logged in user, setting whatever auth data you need to lookup via calls from the webView - eg, uid, email, etc
Pass that doc.id to the webView, and use that token value as a parameter for cloud functions being called from the webView, that require the logged-in user data
Code
Implementation requires 5 small JS blocks between Firebase cloud and the browser:
From Flutter mobile client, call cloud function to give you a unique token, where token will be a doc ID and data will have Auth User uid:
exports.getWebAppUserToken = functions.https.onCall(async (data, context) => {
let docRef = await firestore.collection('webTokens')
.add({uid : context.auth['uid']});
return {'webToken' : docRef.id};
});
Pass the token into the url called to open the webview, eg: http://app.com/appPage/<token>, and then extract token in browser:
getValidationToken() {
let href = window.location.href;
let lastIdx = href.lastIndexOf('/');
return href.substr(lastIdx + 1).trim();
}
Now from the browser you can call a cloud function using the token:
const authFuncCalledFromWeb =
firebase.functions().httpsCallable('authFuncCalledFromWeb');
const result = await authFuncCalledFromWeb(uiValidationToken);
Cloud function that uses the webToken to get uid for the request:
exports.authFuncCalledFromWeb = functions.https.onCall(async (data, context) => {
let webToken = data;
let uid = await getWebTokenUid(webToken);
// >>> do stuff that requires uid
});
Helper to lookup webToken:
getWebTokenUid = async function (webToken) {
let webTokenDoc = await firestore.collection(appData.Collctn.webTokens)
.doc(webToken).get();
let webTokenDocData = webTokenDoc.data();
return webTokenDocData['uid'];
}
=================
Here's a variation if you want to consider expiring the token:
<!-- begin snippet: js hide: true -->
let EXPIRES_INTERVAL = 1000 * 60 * 20;
exports.getWebAppUserToken = functions.https.onCall(async (data, context) => {
logr.enter(`getWebAppUserToken`);
const uid = appConfig.getLoggedInUid(context);
logr.i(`uid: ${uid}`);
// For Field.expires, consider that webToken will not be
// looked up until user clicks HTML submit action.
// So whatever interval we give, we should check in client
// On the other hand, user can only get this token through
// the app in a cloud func, so expires may not be nec.
let expiresTimestamp = dateUtil.getNowNumericTimestamp() + EXPIRES_INTERVAL;
let webTokenProfile = {
[Field._created] : dateUtil.getNowReadableTimestampPST(),
[Field.expires] : expiresTimestamp,
[Field.uid] : uid,
}
let docRef = await firestore.collection('webTokens')
.add(webTokenProfile);
let webToken = docRef.id;
return {'data' : webToken};
});

Firebase signInWithCredentials from Microsoft Azure

Normally, I'd use the firebase.auth.OAuthProvider('microsoft.com') and pass it to firebase.auth().signInWithPopup() but in my case I need to log into Microsoft first using their library and pass the credentials back to firebase. (why? because I need their accessToken/refreshToken handling which isn't done by firebase).
Anyway, I can't seem to find a way to take the microsoft-supplied credentials and pass them successfully back to firebase...
here's what's not working for me:
export async function msLogin() {
const userAgent = new MSAL.UserAgentApplication({ auth: { clientId: CLIENT_ID, } });
const auth = await userAgent.loginPopup({ scopes: ['profile'] });
const provider = new firebase.auth.OAuthProvider('microsoft.com');
const idToken = auth.idToken.rawIdToken;
const credential = provider.credential(idToken);
await firebase.auth().signInWithCredential(credential); // **** THIS FAILS
// ... with "Invalid IdP response/credential: http://localhost?id_token=eyJ0eXAiO...0e9BeTObQ&providerId=microsoft.com"
The microsoft popup part works great and I'm logged in successfully there and able to issue API calls. But I can't get firebase to accept those credentials to log in on firebase. I've enabled the Microsoft OAuth provider in the Firebase Authentication panel and I'm sure the CLIENT_ID's match...
Maybe I'm constructing the OAuthCredential wrong?
Help greatly appreciated!
ps: this is a continuation of this SO post.

Resources