Firebase Auth in a Flutter app with WebView - firebase

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};
});

Related

Getstream firebase auth react native documentation?

Not sure if anyone has any experience with getstream and react native.
I followed there tutorial to implement getstreams SDK into my existing app and its working great but I'm stuck on tokens. I've successfully set up firebase so when a new user signs up I can see there UID and information over on both firebase auth and getstream but I'm hung up on my frontend getting the user to sign in on the chat with there token. I set up firebase with there extension but still having issues. Works great with dev.tokens but just can't get past this part. Is there any examples out there or better documentation for this? Thank you!
Only documentation I can find.. not specific to react native
https://getstream.io/chat/docs/react/tokens_and_authentication/
This is currently how I initialize my user.. the user token is hard coded in my chat config file.
// useChatClient.js
import { useEffect, useState } from 'react';
import { StreamChat } from 'stream-chat';
import { chatApiKey, chatUserId, chatUserName, chatUserToken } from './chatConfig';
const user = {
id: chatUserId,
name: chatUserName,
};
const chatClient = StreamChat.getInstance(chatApiKey);
export const useChatClient = () => {
const [clientIsReady, setClientIsReady] = useState(false);
useEffect(() => {
const setupClient = async () => {
try {
chatClient.connectUser(user, chatUserToken);
setClientIsReady(true);
// connectUser is an async function. So you can choose to await for it or not depending on your use case (e.g. to show custom loading indicator)
// But in case you need the chat to load from offline storage first then you should render chat components
// immediately after calling `connectUser()`.
// BUT ITS NECESSARY TO CALL connectUser FIRST IN ANY CASE.
} catch (error) {
if (error instanceof Error) {
console.error(`An error occurred while connecting the user: ${error.message}`);
}
}
};
// If the chat client has a value in the field `userID`, a user is already connected
// and we can skip trying to connect the user again.
if (!chatClient.userID) {
setupClient();
}
}, []);
return {
clientIsReady,
};
};
The next step is to request the token from the Firebase cloud function (ext-auth-chat-getStreamUserToken), and then initialise the current user with that token.
There is a guide and video showing how to do this using the Stream Chat Flutter SDK:
https://getstream.io/chat/docs/sdk/flutter/guides/token_generation_with_firebase/
https://youtu.be/Dt_taxX98sg

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).

How do I query the Firebase authentication by phone number?

In our React 16.13.0 application, we are using Firebase. We link a user to a phone number like so
return firebase
.auth()
.currentUser.linkWithPhoneNumber(phoneNumber, recaptchaVerfier)
.then(function (confirmationResult: any) {
var code = window.prompt("Provide your SMS code");
recaptchaVerfier.clear();
return confirmationResult.confirm(code).then(() => {
callback();
});
})
I was curious how would we then go back and query the Firebase authentication table for users that have a particular phone number, assuming that phone number is used as the identifier for the user, as seen in the portal Authentication view below
. The purpose of querying is not for logging in, but rather for looking up various users.
You cannot query the Authentication database with the Client SDKs but you can with the Admin SDKs.
This means that you will need to implement this querying in your own server or in a Cloud Function.
You could for example write a Callable Cloud Function that would return the user details for a specific user.
The code would look like:
exports.getUserByPhone = functions.https.onCall(async (data, context) => {
try {
const phoneNbr = data.phoneNbr;
const userRecord = await admin.auth().getUserByPhoneNumber(phoneNbr);
return userRecord;
} catch (error) {
// See https://firebase.google.com/docs/functions/callable#handle_errors
// Also see here the error codes: https://firebase.google.com/docs/auth/admin/errors
// In particular, the auth/user-not-found code is returned if there is no existing user record corresponding to the provided identifier.
}
});
You would then call this Cloud Function from your front-end as explained here in the doc, by passing the value of the desired phoneNbr.

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.

Google Sign-In using Firebase in React Native

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.

Resources