Closed Bug 1635344 Opened 4 years ago Closed 4 years ago

Impossible to use identity.launchWebAuthFlow to authorize with Google OAuth scopes

Categories

(WebExtensions :: Compatibility, defect, P2)

75 Branch
defect

Tracking

(firefox-esr78 fixed, firefox76 wontfix, firefox77 wontfix, firefox78 wontfix, firefox79 wontfix, firefox80 wontfix, firefox84 wontfix, firefox85 fixed, firefox86 fixed)

RESOLVED FIXED
86 Branch
Tracking Status
firefox-esr78 --- fixed
firefox76 --- wontfix
firefox77 --- wontfix
firefox78 --- wontfix
firefox79 --- wontfix
firefox80 --- wontfix
firefox84 --- wontfix
firefox85 --- fixed
firefox86 --- fixed

People

(Reporter: mymindstorm, Assigned: mymindstorm)

References

(Regression)

Details

(Keywords: dev-doc-complete, regression)

Attachments

(1 file)

User Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0

Steps to reproduce:

After the new redirect_uri requirements for identity.launchWebAuthFlow introduced in Bug 1614919 (still restricted), it is now impossible to use Google OAuth flows. Google requires that you verify all websites that you redirect to. It is impossible to verify ownership of extensions.allizom.org through Google's developer console.

  1. Create a Google OAuth client id using your extension's redirect URI
  2. Try to verify OAuth consent screen

Actual results:

Cannot verify domain. Can only use extensions.allizom.org as redirect URI.

Expected results:

I should be able to use a redirect URI that I control as part of the authorization process.

This is probably not a Google-specific issue. I'm sure there are other examples of OAuth providers requiring that you verify ownership of your redirect URIs. I don't see the point of restricting the redirect_uri parameter, as it's still possible to just create a popup window and have the extension manage the redirect to work around the restrictions put on the identity API. It would be nice to at least have some insight into why this restriction was put on identity.launchWebAuthFlow.

Flags: needinfo?(mixedpuppy)

I just created an oauth app entry and was not required to do any verification. The only place I see domain verification is for webhook/push notifications. My test extension performed as expected. If you can provide a specific STR that shows otherwise please reopen.

Status: UNCONFIRMED → RESOLVED
Closed: 4 years ago
Flags: needinfo?(mixedpuppy)
Resolution: --- → INVALID

You have to verify the consent screen for public use.

https://support.google.com/cloud/answer/7454865

Apps in development: if your app is experimental or a test build, you don't need to go through verification unless you decide to launch it to the public.

https://support.google.com/cloud/answer/9110914

The process you need to complete depends on whether your app requests sensitive scopes, or restricted scopes (all apps must complete the first process, brand verification)

Under all apps section:

Verify ownership of your project’s authorized domains using the Search Console. Use an account that is either a Project Owner or a Project Editor of your Cloud Console project.

If you add a redirect URI it will be automatically added to authorized domains.

Status: RESOLVED → UNCONFIRMED
Flags: needinfo?(mixedpuppy)
Resolution: INVALID → ---
Severity: -- → S2
Flags: needinfo?(mixedpuppy)
Priority: -- → P2

S1 or S2 bugs needs an assignee - could you find someone for this bug?

Flags: needinfo?(mixedpuppy)

I can confirm there doesn't seem to be a way to go through the oauth flow for Google that will pass verification:

STR:

  1. Go to cloud console, add credentials:
    • The "Desktop App" option doesn't allow setting a redirect URL
    • The "Web Application" does allow setting a redirect URL, so this is the only path right now
  2. Add your ...extensions.allizom.org URL as a redirect URL
  3. Go to the Consent Screen options, your app requires verification
    • In the options you will see that allizom.org is automatically added to "authorized domains"
    • If you send this to Google, they will reject the verification saying you need to verify ownership of allizom.org

Expected:
The ideal approach would be to allow for the "Desktop Application" option from Google. No redirect url is selectable here. The documentation suggests a few different values that can be passed to the redirect_uri parameter:

  • custom uri scheme (used for mainly for mobile apps?)
  • loopback ip address (might cause conflicts with other software?)
  • Manual copy/paste (not great UX)
  • Programatic extraction (yay!)

The proposed change here would be to accept urn:ietf:wg:oauth:2.0:oob:auto as a redirect uri, and then monitor the requests to wait for the token to appear. Do note however that Google considers this method deprecated (click the programmatic extraction button in the Sample authorization URLs section). Another possibility, which could be accepted in addition, would be the custom uri scheme, it uses the reverse notation of the client id, e.g. com.googleusercontent.apps.123:redirect_uri_path, which should be possible to monitor and extract as well.

As a workaround I guess one could implement their own oauth flow with browser.windows.create() and webRequest. This bug shouldn't be too difficult for a contributor to fix, I wouldn't consider it an immediate priority for the WebExtensions team.

Status: UNCONFIRMED → NEW
Ever confirmed: true

The proposed change here would be to accept urn:ietf:wg:oauth:2.0:oob:auto as a redirect uri, and then monitor the requests to wait for the token to appear. Do note however that Google considers this method deprecated (click the programmatic extraction button in the Sample authorization URLs section). Another possibility, which could be accepted in addition, would be the custom uri scheme, it uses the reverse notation of the client id, e.g. com.googleusercontent.apps.123:redirect_uri_path, which should be possible to monitor and extract as well.

I don't think you should be using a deprecated method here. Outside of the possibility of oauth breaking again, this would only solve the issue for Google, and not any other oauth provider that requires domain verification. You can't use custom uri scheme since Google requires publishing info from an app store to use a mobile client type. A loopback address would work best as extensions wouldn't have to pretend to be a website.

I have some working code for using 127.0.0.1 that I will submit, I'm still going through the patch submission docs.

As a workaround I guess one could implement their own oauth flow with browser.windows.create() and webRequest. This bug shouldn't be too difficult for a contributor to fix, I wouldn't consider it an immediate priority for the WebExtensions team.

Could Bug 1614919 be made public so that if an extension does implement their own oauth flow it doesn't create a security issue?

Assignee: nobody → mymindstorm
Status: NEW → ASSIGNED

Thank you so much for making the effort to create a patch! Bug 1614919 can likely be made public, I've asked the team to check.

Reading more about the oob:auto urn, it does look like this is Google-specific. rfc 8252 warns that using loopback might be a problem if other applications on the machine are using that port, so as long as we intercept the loopback request and don't actually execute it that seems fine to me as well.

I was interested in this for other reasons as well, so here is a proof of concept I wrote that doesn't use the identity API: https://gist.github.com/kewisch/be78a55577e58c6d88b5b39ea598365c

I think it's already cancelling the redirect from going through. I'm not too familiar with Firefox source, so I may be mistaken here.

https://searchfox.org/mozilla-central/source/toolkit/components/extensions/parent/ext-identity.js#40-47

The reason for limiting redirect_url values in bug 1614919 is to prevent extensions from receiving OAuth tokens from unrelated OAuth providers.

An (external) OAuth service is expected to only accept registered values for redirect_url, to prevent leakage of tokens to applications that aren't owned by the client that had been registered with the OAuth service.

The identity.launchWebAuthFlow() API uses a dummy URL (https://<app-id>.chromiumapp.org/ in Chrome, extensions.allizom.org in Firefox) to have a safe redirection target that isn't under control of third parties. Instead of the result (e.g. auth tokens) being passed to the page at redirect_url, the result is passed to the callback of identity.launchWebAuthFlow(). Because of this, the browser must ensure that redirect_url is owned by the extension author:

  • Chrome chooses a well-known dummy redirect_url containing the extension ID (https://<app-id>.chromiumapp.org/). Google's OAuth service supports verification of extension ownership as seen in https://developer.chrome.com/extensions/tut_oauth . This only happens because Chrome and Google's OAuth server are from the same company.
  • Firefox also chooses a dummy redirect_url, but there is no way to prove Firefox extension ownership to providers such as Google.

The solution to this is to either allow extension developers to prove their ownership to OAuth providers, or to allow extension developers to prove their ownership of the domain to Firefox. The first is not likely to happen because of the high cost in engineering and operations. The latter can reasonably be implemented with existing primitives.

  • When an extension requests host permissions, it is granted access to any data from the website. A user consenting to the permission can be taken as a signal that the extension is allowed to see the redirect URL.
  • ( If we are concerned about overloading the meaning of host permissions, or if we want the ability to detect the use of redirect_url by static analysis, then we could require the redirect_url to be set in manifest.json. This would theoretically be more "secure", but I am not convinced that we should bother, because the alternative is that the extension implements the OAuth flow with other extension APIs as demonstrated by Philipp in comment 7, which is less user-friendly and not more secure. )

P.S.: Whitelisting loopback addresses such as 127.0.0.1 is not the ideal solution IMO, since it does not address the issue of ownership and still has the potential for credential leakage of unrelated OAuth clients. This option was only considered because Google coincidentally treats it as a special endpoint (with less rigid ownership verification requirements), and I don't view that as a good justification to whitelist an arbitrary domain.

Keywords: regression
Regressed by: CVE-2020-6823
Has Regression Range: --- → yes

because the alternative is that the extension implements the OAuth flow with other extension APIs as demonstrated by Philipp in comment 7

I also mentioned this before when I opened this issue. I really don't see the point as anyone who knows the API well enough could easily work around the restrictions making any type of verification system meaningless.

Whitelisting loopback addresses such as 127.0.0.1 is not the ideal solution IMO, since it does not address the issue of ownership and still has the potential for credential leakage of unrelated OAuth clients.

The auth flows involving loopback addresses are designed for native apps. I see this as the most reasonable solution as all native apps are public clients that inherently share this problem. Using a native flow would allow providers to be aware of this. There are already standards to help mitigate the flaws with public clients such as RFC 7636.

Firefox might also be able to mitigate this by scanning for declared client ids in code during review and auto-rejecting/flagging if someone tries to upload an extension that is not authorized to use associated with a client id.

(In reply to mymindstorm from comment #10)

because the alternative is that the extension implements the OAuth flow with other extension APIs as demonstrated by Philipp in comment 7

I also mentioned this before when I opened this issue. I really don't see the point as anyone who knows the API well enough could easily work around the restrictions making any type of verification system meaningless.

The alternatives require permissions that the user need to approve (either host permissions or "Access browser tabs"), whereas the identity API does not trigger any permission warnings.

Whitelisting loopback addresses such as 127.0.0.1 is not the ideal solution IMO, since it does not address the issue of ownership and still has the potential for credential leakage of unrelated OAuth clients.

The auth flows involving loopback addresses are designed for native apps. I see this as the most reasonable solution as all native apps are public clients that inherently share this problem.

I'm not fully onboard on this being the "most reasonable solution", because there is already a feasible alternative in the form of extension permissions. So far the only justification for relaxing the redirect_uri requirements with regards to 127.0.0.1 is because Google accepts it. I'd like some evidence of this being a more widely used practice by other major OAuth service providers before accepting 127.0.0.1 as a dummy value.

Firefox might also be able to mitigate this by scanning for declared client ids in code during review and auto-rejecting/flagging if someone tries to upload an extension that is not authorized to use associated with a client id.

This suggested alternative is far less reliable than a static value in manifest.json.

(In reply to Rob Wu [:robwu] from comment #11)

(In reply to mymindstorm from comment #10)

Whitelisting loopback addresses such as 127.0.0.1 is not the ideal solution IMO, since it does not address the issue of ownership and still has the potential for credential leakage of unrelated OAuth clients.

The auth flows involving loopback addresses are designed for native apps. I see this as the most reasonable solution as all native apps are public clients that inherently share this problem.

I'm not fully onboard on this being the "most reasonable solution", because there is already a feasible alternative in the form of extension permissions. So far the only justification for relaxing the redirect_uri requirements with regards to 127.0.0.1 is because Google accepts it. I'd like some evidence of this being a more widely used practice by other major OAuth service providers before accepting 127.0.0.1 as a dummy value.

The justification for allowing loopback addresses is the OAuth spec. The below specifically allows letting native apps use loopback addresses.

https://tools.ietf.org/html/rfc8252#section-7.3

The relevant part is:

The authorization server MUST allow any port to be specified at the
time of the request for loopback IP redirect URIs, to accommodate
clients that obtain an available ephemeral port from the operating
system at the time of the request.

This looks good enough to me, as it means that it applies to more services besides Google.
To still maintain proper isolation between native apps and extensions, let's only allow URLs in a fixed format such as http://127.0.0.1/oauth2identity/[hash].

(In reply to Rob Wu [:robwu] from comment #13)

The relevant part is:

The authorization server MUST allow any port to be specified at the
time of the request for loopback IP redirect URIs, to accommodate
clients that obtain an available ephemeral port from the operating
system at the time of the request.

This looks good enough to me, as it means that it applies to more services besides Google.

I have never said that using loopback addresses was Google specific, and I have specifically said before that this is probably not just a Google issue.

To still maintain proper isolation between native apps and extensions, let's only allow URLs in a fixed format such as http://127.0.0.1/oauth2identity/[hash].

Do you have a specific port you want me to use? 80 does not seem like the best choice. The purpose of that insecure match was to allow developers to specify a port. I was going to switch it to a regex, but if you only want one specific address that would make it simpler.

How about http://127.0.0.1:5440/mozoauth2identity/[hash] and http://::1:5440/oauth2identity/mozoauth2identity/[hash]?

Firefox will intercept the redirect to that URL; it's never going to be followed. So, we don't need to support more formats than absolutely necessary.
Just verifying that the URL starts with http://127.0.0.1/mozoauth2/[hash] should be good enough.

Pushed by dluca@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/660653e64aa0
Allow launchWebAuthFlow redirect URI to be set to loopback address. r=robwu

Clearing old needinfo (the severity has already been set in the meantime)

Flags: needinfo?(mixedpuppy)
Status: ASSIGNED → RESOLVED
Closed: 4 years ago4 years ago
Resolution: --- → FIXED
Target Milestone: --- → 86 Branch

The patch landed in nightly and beta is affected.
:mymindstorm, is this bug important enough to require an uplift?
If not please set status_beta to wontfix.

For more information, please visit auto_nag documentation.

Flags: needinfo?(mymindstorm)

Comment on attachment 9172932 [details]
Bug 1635344 - Allow launchWebAuthFlow redirect URI to be set to loopback address. r?rpl

Beta/Release Uplift Approval Request

  • User impact if declined: Google oauth flows for extensions do not work right
  • Is this code covered by automated tests?: Yes
  • Has the fix been verified in Nightly?: No
  • Needs manual test from QE?: No
  • If yes, steps to reproduce:
  • List of other uplifts needed: None
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky): This only effects one specific extension api, and only if a loopback url is used. It has automated testing.
  • String changes made/needed: n/a
Attachment #9172932 - Flags: approval-mozilla-beta?

Comment on attachment 9172932 [details]
Bug 1635344 - Allow launchWebAuthFlow redirect URI to be set to loopback address. r?rpl

approved for 85.0b5

Attachment #9172932 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
Flags: needinfo?(mymindstorm)

Is this API widely used enough (particularly for enterprise-type situations) that we should consider uplifting to ESR also? It grafts cleanly.

Flags: needinfo?(rob)

Comment on attachment 9172932 [details]
Bug 1635344 - Allow launchWebAuthFlow redirect URI to be set to loopback address. r?rpl

ESR Uplift Approval Request

  • If this is not a sec:{high,crit} bug, please state case for ESR consideration: widely used api that fails with google oauth
  • User impact if declined: unable to use google services via extensions
  • Fix Landed on Version: 86
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky): This only effects one specific extension api, and only if a loopback url is used. It has automated testing.
  • String or UUID changes made by this patch: none
Attachment #9172932 - Flags: approval-mozilla-esr78?
Flags: needinfo?(rob)

Comment on attachment 9172932 [details]
Bug 1635344 - Allow launchWebAuthFlow redirect URI to be set to loopback address. r?rpl

Approved for 78.7esr.

Attachment #9172932 - Flags: approval-mozilla-esr78? → approval-mozilla-esr78+

I'd like to put this in the enterprise release notes, but I can't come up with a good summary. Can someone that fixed this help?

Something along the lines of this?

Extensions using identity.launchWebAuthFlow may now use a specifically formatted loopback address as the redirect URL.

The release notes will need to link to some documentation with the allowed redirect format.

I've submitted a documentation change at https://github.com/mdn/content/pull/1530

What's the URL where that doc lives? I can't tell from the github pull.

It will be rendered at https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/identity

PS. even without my reply, you can find the page by copying a phrase from the source code and searching for "[phrase] mdn" on Google, DuckDuckGo, etc.

even without my reply, you can find the page by copying a phrase from the source code and searching for "[phrase] mdn" on Google, DuckDuckGo, etc.

I swear I tried that first and didn't get a hit. :)

You need to log in before you can comment on or make changes to this bug.