Metamask-extension: Request account access ONLY if needed - need way to determine this.

Created on 1 Oct 2018  路  6Comments  路  Source: MetaMask/metamask-extension

There's a good chance this has already been discussed here or elsewhere but as of yet I've been unable to find anything that solves the following problem:

Given the breaking changes discussed here - https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8 - I have downloaded a custom (chrome) build in order to prepare my dapp for what's coming. Requesting account access via ethereum.enable() works as expected and I am prompted accordingly.

The problem is how can one determine if the user has already granted site access either on a different website or on your website at some point earlier in time? The way we init our dapp depends (for now) on redefining the users chosen provider (metamask, mobile browser, ledger etc) on every app load/refresh. Therefore if they have already granted account access and later refresh the page, MetaMask will be detected on app load and enable() will be called again as part of the init process which is redundant.

Another issue occurs in the case in which a user decides to switch providers i.e. they visit the website and use MetaMask at first but then decide to switch to their ledger later on. If for some reason they then decide to switch back to MetaMask how can we best determine if we need to request account access again or not.

Ideally, there should be a way to determine when enable() is necessary that doesn't involve local management of such state. Perhaps an additional ethereum.enabled() method returning a bool.

All 6 comments

Hi @MarcZenn, thanks for reaching out and for testing the upcoming changes.

Your understanding is correct: if a user enables a provider for a given dapp then refreshes the page, the dapp must still call provider#enable when it reloads. The difference is that the Promise returned by provider#enable will resolve immediately without showing additional user approval dialogs. This design was intentional, as it allows dapps to run a single logic path regardless of whether a user has previously enabled the provider or not in the past.

For example, instead of having code like this:

if (provider.isEnabled) {
    startDapp();
} else {
    await ethereum.enable();
    startDapp();
}

...dapps can use a single logic path:

await ethereum.enable(); // resolves immediately if user already approved in the past
startDapp();

It should also be noted that remembering past user approval is purely a MetaMask-specific feature; it's not part of the underlying EIP that requires dapps to call the enable method. There's no guarantee that other dapp browsers will cache user approval, which is another reason to always call the enable method despite past approval.

Thanks for clearing this up @bitpshr makes total sense.

I do recall a couple of times in which enable did not resolve right away but I haven't been able to reproduce since then, will post if it I come across that again.

Thanks @MarcZenn, definitely feel free to open a new ticket if you encounter any other issues.

Sorry to resurface this closed issue, but I have a few concerns: 馃槵

I understand and appreciate the decision explained above by @bitpshr and suppose that another way to detect "no need to enable" would be to simply check for any accounts. If the resulting array contains any accounts, then enable does not need to be called. However, the accounts array may also be empty if the user is not signed in to MetaMask at all.

Concern 1: no way to distinguish between needing login and needing to enable

The practice of immediately calling enable upon initial page load produces a bad user experience. As a first-time visitor, I may have never seen a privacy prompt like this before. But even if I have, I may not know anything about the specific DApp that is requesting my permission. It's akin to content sites requesting permission to show browser notifications before I can even read some article that I was linked to.

Concern 2: encouraging developers to call enable without first educating or getting buy-in from the user

Finally, calling enable in a "currently-enabled" DApp is not always returned successfully without re-engaging the user. If the user is signed out when loading a DApp, which immediately calls enable, MetaMask will prompt the use to sign in _and then display the account access prompt while the page is refreshing_. This is not good, because the user now has a call-to-action where she is reasonably likely to reject access that was already granted. If this happens, the page will refresh again and we're back to calling enable yet again. It is reasonable to expect this to happen quite often because users are not only signed out when they quit their browsers, but they are rightfully encouraged to sign out of their MetaMask accounts when not using them.

Concern 3: enable not working as intended for some signed-out users

Should I open issues for any of these three? Thanks 鉁岋笍

Hi @micahalcorn, thanks for reaching out. Since this issue was closed, certain semantics of EIP-1102 and the resulting MetaMask implementation have changed. Most importantly, MetaMask now exposes nonstandard convenience methods that can be used to achieve greater UX granularity. The methods are available are:

  • ethereum._metamask.isApproved - Determines if the dapp has a cached approval
  • ethereum._metamask.isEnabled - Determines if the dapp is currently enabled
  • ethereum._metamask.isUnlocked - Determines if the MetaMask is unlocked

This API is undocumented; we鈥檝e been hesitant to openly recommend these methods because they tie dapps to MetaMask. Not all other dapp browsers cache user approval or have a notion of locked wallets, so we intentionally put convenience methods at ethereum._metamask instead of at ethereum so developers know they鈥檙e using proprietary functionality. We鈥檙e talking with other dapp browser authors to determine if any of these methods could / should be standardized, but we鈥檙e ahead of them implementation-wise.

Concern 1: no way to distinguish between needing login and needing to enable

The convenience method ethereum._metamask.isUnlocked can be used for this. Still, dapps should never have to make this distinction. Calling ethereum.enable() when a user is logged out of MetaMask will now open a popup that allows them to log in before approval. This means that if accounts are not populated, calling ethereum.enable() is always a valid UX path to ultimately populate accounts regardless of if MetaMask is locked.

Concern 2: encouraging developers to call enable without first educating or getting buy-in from the user

Dapps should only call ethereum.enable() when they need account access. This can be on page load (bad UX) or can be immediately before some account-requiring RPC call (good UX). The example code used in this issue and throughout the EIP itself is purely for demonstration purposes. In practice, most 1102-conforming dapps have implemented this in a traditional manner, e.g. calling ethereum.enable() when a user explicitly clicks some "log in" button. The proposal itself doesn't give any indication when ethereum.enable() should be called, that's completely up to the dapp.

Concern 3: enable not working as intended for some signed-out users

I'm not sure I understand this without any context. Feel free to open a new issue with details and a way to reproduce what you're seeing.

Are there any plans to address this in a new EIP? I do not like coupling myself to MetaMask specifically, but we really need a way to check enabled without triggering a prompt. For example, if a user browses to my site and there is a section of the UI where some profile specific information is presented, I will fallback to a login button. However, if the user is already logged in (already approved MetaMask) then they should not be forced to re-login every time they open the app.

There are 3 states:

  1. User has not yet approved app, present login button.
  2. User has approved app previously, but not this session.
  3. User has approved app this session.

If a user is in state (2), I want to be able to present them with as much information as I can. I do not want my user to have to click a login button that just insta-logs in because that is unnecessary friction. However, I can't auto-login because I don't know if they are in situation (1) or (2) at the moment.

Imagine if gmail always sent you to the login page when you first opened it, but sometimes when you click the login button it just takes you straight to your inbox. As a user, this would be annoying, "Why didn't you just take me to my inbox! Clearly I was already logged in!"

Was this page helpful?
0 / 5 - 0 ratings