window.ethereum.providers undefined - allow user to select MetaMask OR Coinbase Wallet as web3 provider - wordpress

How do we allow users to choose which browser wallet / provider they use when interacting with web3 websites? This is for basic HTML / WordPress websites not using TypeScript / React, etc.
According to Coinbase Docs, the Coinbase Wallet SDK is not needed: "You can use this provider in your dapp to request users' Ethereum accounts, read on-chain data, and have the user sign messages and transactions, without using the Coinbase Wallet SDK."
https://docs.cloud.coinbase.com/wallet-sdk/docs/injected-provider
Currently, my integration works with MetaMask without issue. However, when trying to choose the Coinbase Wallet (browser extension) as my provider, there doesn't seem to be a way to do so.
If window.ethereum gets set by the Coinbase Wallet extension, there is suppose to be a window.ethereum.providers object. You can then go through each one, find MetaMask or Coinbase, and set the preferred one as the provider:
MetaMask conflicting with Coinbase wallet
However, it seems that MetaMask is has priority over this, and sets window.ethereum WITHOUT the .providers object.
I've attempted to load / find the Coinbase Wallet provider during and after Page Load, but it doesn't seem to exist. Again, I'm assuming MetaMask gets set as the provider before Coinbase injection even exists.
Is there a way to let the user set the provider / wallet that is being used using basic web3 JS?

Have had some testing with this function with coinbase and metamask extensions both installed on chrome. It will open a window for you to choose which wallet you want to connect and then use the first address in that wallet to set the account.
async function getWallet() {
try{
const accounts = await ethereum.request({ method: 'eth_requestAccounts' });
let account = accounts[0];
return account
} catch (error) {
console.log(error);
}
}

https://docs.cloud.coinbase.com/wallet-sdk/docs/injected-provider-guidance
const connectWallet = async () => {
if (typeof window.ethereum !== "undefined") {
let provider = window.ethereum;
// edge case if MM and CBW are both installed
if (window.ethereum.providers?.length) {
window.ethereum.providers.forEach(async (p) => {
if (p.isMetaMask) provider = p;
});
}
await provider.request({
method: "eth_requestAccounts",
params: [],
});
}
};
I think the way how implemented your code has some bugs. this code above is clear

Related

AppleSignIn (with Firebase & Expo) working locally but not on standalone

I implemented the signinWithApple button on my Expo app, and it's working perfectly locally when i use the host.exp.Exponent on Services ID in Firebase authentification tab for Apple Sign in.
But when I create a standalone app, and I test it with TestFlight, it doesn't work anymore whether I use host.exp.Exponent, nothing, or my specific app service ID on Services ID.
What am I missing here?
MY CODE :
handleApple = async () => {
const csrf = Math.random().toString(36).substring(2, 15);
const nonce = Math.random().toString(36).substring(2, 10);
try {
const appleCredential = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL
],
state: csrf,
nonce: nonce
});
const {
identityToken,
fullName,
email
} = appleCredential;
if (identityToken) {
// login with credential
const provider = new firebase.auth.OAuthProvider("apple.com");
const credential = provider.credential({
idToken: identityToken,
rawNonce: nonce,
});
await firebase.auth().signInWithCredential(credential).then(user => {
...
EDIT :
I managed to make it work by using my bundle identifier (which is also my app id) on the Service ID field in firebase.
Error code :
Error: The audience in ID Token [##.app-videos] does not match the expected audience ##.signin.
But now the sign in with Apple on my website breaks.
I manage to make it work when I change the Service Id field to my specific app service ID (found in Identifiers > Services IDs).
So i'm stuck with an app that requires something and a website that requires an other. Why is that?
Should I do something specific when I rebuild my app so that the changes that I made to mu identifiers are taken into account?
I'm using this, is it not enough?
expo build:ios --clear-provisioning-profile
I had the same problem and should solve it in the following way.
You must re-create GoogleService-info.plist in the Firebase console with the same bundleId as in the project in Xcode.

Firebase service account to generate authentication token for client-side use with Google Apps Script

I am having difficulty using the FirebaseApp (a 3rd party API) to generate an authentication token that can be passed to a sidebar and used by the client to login and access my Firebase Database client-side.
I'm trying to use this tutorial but cannot get it working without using a database secret (which is being depreciated) in makeToken(). I'd prefer to use a service account as reflected in this tutorial. When I look at the difference between the tokens generated, the first 2 pieces separated by a '.' are identical, the last piece after the final '.' is different. The lengths are the same as well. eg:
//Example Generated by Database Secret: TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBv.ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=.dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2U=
//Example Generated by Service Account: TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBv.ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=.IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbml=
I can generate the OAuth access token, pass it to FirebaseApp and generate an authentication token, but when it is passed client-side and I attempt to authenticate I get an error: Login Failed! Error: INVALID_TOKEN: Failed to validate MAC.
It seems like there is a lot of misinformation and conflicting information on how this should be done.
I have a getFirebaseService() function server-side that uses Apps Script OAuth2 Library to get an access token.
function getFirebaseService() {
return OAuth2.createService('Firebase')
// Set the endpoint URL.
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the private key and issuer.
.setPrivateKey(fb_PRIVATE_KEY) //Service account private key
.setIssuer(fb_SERVICE_EMAIL) //Service account email
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scopes.
.setScope('https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/firebase.database');
}
I have a makeToken() function server-side that gets an authentication token from Firebase using the OAuth access token. I am able to use the service.getAccessToken() OAuth token server-side to access and store data. So that works, I guess my issue is creating a client auth token that's more restrictive.
function makeToken(){
var service = getFirebaseService();
if (service.hasAccess()) {
return FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()) //Database Secret Works: "AAslhfi3MYACCESSTOKEN2930hf03ah4th8" but is being depreciated.
.createAuthToken(Session.getActiveUser().getEmail());
} else {
Logger.log("makeToken: " + service.getLastError());
}
}
Then client-side, from the sidebar, I try to authenticate with a custom auth token retrieved server-side from makeToken().
var userAuthToken;
google.script.run.withSuccessHandler(function (requestAuthToken) {
userAuthToken = authenticateClient(requestAuthToken)
}).makeToken();
function authenticateClient(userRequestToken) {
var ref = new Firebase(fb_URL);
ref.authWithCustomToken(userRequestToken, function (error, authData) {
if (error) {
console.log("FB Login Failed!", error); //Error below come from here.
}
else {
console.log("FB Login Succeeded!", authData);
}
});
return ref.authData.auth;
}
This results in Login Failed! Error: INVALID_TOKEN: Failed to validate MAC..
Edit: Is it possible FirebaseApp is incorrectly generating the JWT Authentication Token?
Edit2: I think the above edit is unlikely as I attempted to use the GSApp library and had the same issue. It only seems to want the depreciated database secret, not a service account OAuth.
Alright, so after a very long day I figured it out. I'm going to lay out what I ended up using for libraries and what the issue was (see the third library). The main problem was essentially that the tutorial was outdated and no a lot of people use Firebase in apps script.
OAuth2 (Server-side)
Link
I didn't have to change anything here! It was working fine and never an issue.
FirebaseApp (Server-side)
Link
This is a nice library and I stuck with it because it worked well (once I got it there). I had to make a change to my original code that came from the tutorial I mentioned. My code ended up like this and worked:
if (service.hasAccess()) {
return FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()) //get OAuth Token
.createAuthToken(Session.getEffectiveUser().getEmail(), null, serviceAccount.client_email, serviceAccount.private_key);
//... Added the null, private key, and service email parameters.
Firebase (Client-side)
Link
Alright, so this is where my main issue was -- The tutorial I followed for client-side setup was old. I had to upgrade the code on my own to use the new 3.x version:
<script src="https://www.gstatic.com/firebasejs/5.8.2/firebase.js"></script>
// Initialize Firebase
var config = {
apiKey: "<Web API Key>",
authDomain: "<Project ID>.firebaseapp.com",
databaseURL: "https://<DB URL>.firebaseio.com/"
};
firebase.initializeApp(config);
With this firebase instance I was able to update my original authenticateClient() method:
function authenticateClient(userRequestToken) {
firebase.auth().signInWithCustomToken(userRequestToken).catch(function(error) {
// Handle Errors here.
console.error("authClient: ", error.code, error.message);
});
return {
uid: firebase.auth().currentUser.uid,
metadata: {
lastSignInTime: firebase.auth().currentUser.lastSignInTime
}
};
}
That's it! I now have a firebase instance with a signed in user via JWT Custom Token! I came across a few people with similar issues an I hope this helps.

Firebase Web Admin

First of all, I am using nodejs for the backend. I use firebase hosting and firebase functions to deploy an express() app.
What I am trying to achieve is to make an admin website, which is connected to Firebase. so I have a route /admin/ like this:
adminApp.get("/", (request, response) => {
return response.redirect("/admin/login");
});
Here I basically want to check if a current user is logged in - or not.
I know firebase supports client side authentication using:
firebase.auth().onAuthStateChanged(user => {
if (user) {
} else {
}
});
And using
function login() {
var userEmail = document.getElementById("email").value;
var userPass = document.getElementById("password").value;
firebase.auth().signInWithEmailAndPassword(userEmail, userPass).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (error) {
document.getElementById('loginError').innerHTML = `Error signing in to firebase`;
}
});
}
However image this case:
Someone (not an admin) is visiting /admin/some_secret_website/ which he obviously does not have access to.
If I rely on client side authentication, it first loads the entire website and the scripts and then notices - hey I am not authenticated, let me redirect to /login. By then however anyone knows the source code of an admin page.
I'd rather have something like:
adminApp.get("/admin/some_secret_website", (request, response) => {
if (request.user) {
// user is authenticated we can check if the user is an admin and give access to the admin page
}
});
I know that you can get the user's token and validate that token using the AdminSDK, but the token must be send by the client code, meaning the website was already loaded.
I came across Authorized HTTPS Endpoint by firebase, but it only allows a middleware when using a bearer token.
Does anybody know how I can maintain a server side user object to not even return admin html to the browser but only allow access to admins?
Like Doug indicated, the way your admin website/webapp would function with Firebase Cloud Functions (which is effectively a Nodejs server) is that you get the request, then use the headers token to authenticate them against Firebase Auth. See this answer for a code snippet on this.
In your case, I'm thinking you would create a custom claim for an "administrator" group and use that to determine whether to send a pug templated page as a response upon authentication. As far as Authorization, your db rules will determine what said user can CRUD.

How to change the language of the message sent in Firebase auth

I need to change the language of the message sent in accordance with the browser's language when I want to reset the password in my app. This is my onSubmit func, which i call on submit form to send message. I take value from state and put it into languageCode. I use redux
onSubmit = () => {
let error = {}
if (!this.state.email)
error.email = <FormattedMessage id='common.error.empty' />
if (Object.keys(error).length) {
this.setState({error})
return
}
this.props.languageCode(this.props.locale)
this.props.doPasswordReset(this.state.email).then(() => {
this.setState({openDialog: true})
}).catch(error => {
this.setState({
error: {
...this.state.error,
email: error.message,
},
})
})
}
thanks for #Frank van Puffelen, #Miyo AlpĂ­zar #Frederiko Cesar for thier anser in this question & this question
Firebase's error message are targeted at application developers, so are in English only. While we'd love to provide them in the same languages as we provide our documentation in, that will never cover all the languages of your users.
So you will have to detect the error in your code, log the error to a central system where you can inspect the problem and then show a localized error message to your user.
As far as I know there is no standardized way of doing that in Angular. But if there is, it'll be unrelated to Firebase.
For Javascript - version 9
import { getAuth } from "firebase/auth"
const auth = getAuth()
auth.useDeviceLanguage() // detects language from user's device
or you can set it manually
auth.languageCode = 'sk' // change messages to Slovak language
https://firebase.google.com/docs/reference/js/auth.auth.md#authlanguagecode
Use the languageCode property of Auth to specify a language for the user. Here is the entry for that in JS, but it's also available on iOS and Android.

Firebase Auth successful, but Database Ref set and update does not happen in electron app version 1.4.2

I'm able to authorize the Firebase app from my existing Electron app using firebase.auth().signInWithCustomToken. The promise for this method resolves and I'm able to obtain the current authorized user with firebase.auth().currentUser.uid.
At this point I must technically be able to write to /users/<currentUser>. However calling the userRef.set() and userRef.update() methods does not update the database reference and fails silently (both the callback and the promise from these methods do not resolve and there is no error thrown).
What is strange is that the exact same code works in a different, newly created Electron app. My code looks like below:
const writeToFirebase = (customToken) => {
syncApp.auth().signInWithCustomToken(customToken).then(user => {
const userId = firebase.auth().currentUser.uid; // this is successfull
const userRef = firebase.database().ref("/users/" + userId);
userRef.set({data: data}, () => { //callback does not trigger });
userRef.update({data: data})
.then(() => {//promise does not resolve})
.catch(err) => {// promise is not rejected either! }
});
}
Any pointers on how to go about debugging this would be helpful.
I discovered the problem. It's unlikely anybody else would have the same issue, but if you do, take a look at the userAgent value in your browserWindow.loadURL in Electron.
Mine was set to an Android mobile device & Firebase was not setting/updating due to this reason. I presume the Firebase server reacts differently when it sees a mobile userAgent and I was using the Firebase JS SDK and not the Android SDK which caused the issue.

Resources