Skip to content

Commit

Permalink
Script updating archive at 2025-01-09T00:06:12Z. [ci skip]
Browse files Browse the repository at this point in the history
  • Loading branch information
ID Bot committed Jan 9, 2025
1 parent d5b9e73 commit da8a477
Showing 1 changed file with 23 additions and 2 deletions.
25 changes: 23 additions & 2 deletions archive.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"magic": "E!vIA5L86J2I",
"timestamp": "2025-01-07T00:06:25.481497+00:00",
"timestamp": "2025-01-09T00:06:07.310603+00:00",
"repo": "oauth-wg/oauth-browser-based-apps",
"labels": [
{
Expand Down Expand Up @@ -879,7 +879,7 @@
"labels": [],
"body": "[Section 6.3.4.2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-6.3.4.2.1) (Browser-Based OAuth Applications - Secure Token Storage) discourages using \"universally accessible storage areas\" such as Local Storage, and suggests a pattern of using a Web Worker to \"isolate the refresh token, and provide the application with the access token to make requests\".\r\n\r\n[Section 8.3 (Token Storage in a Web Worker) paragraphs 3 and 4](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-8.3-3) describe using Web Workers to isolate the refresh token, like what is proposed in Section 6.3.4.2.1.\r\n\r\nBut [Section 8.3 paragraph 2](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-8.3-2) notes:\r\n\r\n> The security properties of using a Web Worker are identical to using Service Workers. When tokens are exposed to the application, they become vulnerable. When tokens need to be used, the operation that relies on them has to be carried out by the Web Worker.\r\n\r\nThis would seem to *discourage* the pattern proposed in Section 6.3.4.2.1, where a Web Worker provides the access token for the application to make requests directly.\r\n\r\nSection 8.3 paragraph 2 proposes a pattern similar to that in [Section 7.4](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-7.4) (Discouraged and Deprecated Architecture Patterns - Handling the OAuth Flow in a Service Worker). [Section 7.4.1.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-7.4.1.1) (Attacking the Service Worker) notes several security shortcomings with the pattern, and that there is significant complexity to implementing, registering and maintaining a Service Worker.\r\n\r\nI believe that the shortcomings identified in Section 7.4.1.1 *also* apply to using a Web Worker to isolate the refresh token alone. Using a (shared) Web Worker is still a source of complexity \u2013 even if that worker doesn't need to do quite as much.\r\n\r\nAs a result, I don't think there is a significant *security* benefit to using Web Workers to store the refresh token over using Local Storage. An attacker who can execute arbitrary JavaScript within the context of the application (ie: via XSS) to access Local Storage can also send messages to Web Workers (to acquire a short-term access token) or [bypass them](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-5.1.3) (to acquire a new, longer-lived refresh token), and so [it's still pretty much game over for a Browser-Based OAuth Application](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-6.3.4.3).\r\n\r\nOne potential benefit to using *Shared* Web Workers for refresh tokens is that the Worker could block requests for the access token on completion of any in-flight refresh request, thus preventing the application from attempting to use a single refresh token multiple times.\r\n\r\nHowever, it'd also be possible to do that with Local Storage and the [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API).\r\n\r\n## Related issues\r\n\r\n* https://github.com/oauth-wg/oauth-browser-based-apps/issues/3\r\n* https://github.com/oauth-wg/oauth-browser-based-apps/pull/19\r\n* https://github.com/oauth-wg/oauth-browser-based-apps/pull/24 \r\n\r\n**Edit (2024-01-06)**: noted that web workers don't stop [acquiring new tokens](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-5.1.3).\r\n",
"createdAt": "2025-01-02T05:46:28Z",
"updatedAt": "2025-01-06T06:30:33Z",
"updatedAt": "2025-01-08T05:38:22Z",
"closedAt": null,
"comments": [
{
Expand All @@ -902,6 +902,27 @@
"body": "> > Since the refresh token is securely confined within the web worker, it is inaccessible to external JavaScript.\r\n> \r\n> Yes, _that_ refresh token is not accessible, but the malicious code could [do the whole Authorisation Code process itself](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-5.1.3), and get a new token. I've edited that part of my post to link to [Section 5.1.3](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-5.1.3).\r\n\r\nThis is indeed the correct observation. An attacker running malicious JS code can run a new flow to obtain an independent set of tokens. This attack vector is often overlooked and poses a major threat to frontend Oauth clients (hence the elaborate updates to the spec).\r\n\r\n\r\n> This is similar to the attack described in [Section 7.4.1.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-7.4.1.1) against Service Workers, but because Web Workers can't intercept XHR/`fetch()`, there's no need to unregister a Web Worker first.\r\n> \r\n> There are some mitigations that an IdP could do, like strictly controlling the `redirect_uri` to a specific registered handler URI (so an attacker would need XSS on the `redirect_uri`, rather than any URI on the same origin) and using a CSP to prevent embedding as an `<iframe>` (so an attacker would need some persistent XSS).\r\n\r\nThese mitigations typically do not work, for a couple of reasons. \r\n\r\nTight restrictions on the `redirect_uri` do not matter, as long as it is same-origin with the compromised application. The attacker does not need to run code at the `redirect_uri` endpoint, but merely reads the code from the `redirect_uri` loaded in the iframe. Reading URLs in iframes is restricted by the Same-Origin Policy, so running code within the same origin suffices. Similarly, some authorization servers use web messaging to send the code from the frame to main application context, which is also origin-based.\r\n\r\nAs for restricting the loading of iframes with CSP. Blocking this behavior also means that the legitimate application cannot rely on this pattern. Since frontends typically run a silent iframe-based flow upon load time, this would break silent authentication flows. And enabling this pattern also makes it available to the attacker. Additionally, I believe an attacker would be able to run a similar attack using a popup window instead of an iframe.\r\n\r\n\r\nFinally, a remark on the initial issue that started this thread. I believe the observation @micolous makes is indeed correct. There is only a very limited security benefit to using a web worker to handle refresh tokens. However, using a pattern like this makes it significantly harder to obtain refresh tokens (but not access tokens). It also forces the attacker to run a new flow to obtain one, which makes the interaction more visible. The recommendation to use a web worker is mostly aiming for \"the best you can do in a shitty situation\".",
"createdAt": "2025-01-06T06:30:32Z",
"updatedAt": "2025-01-06T06:30:32Z"
},
{
"author": "micolous",
"authorAssociation": "NONE",
"body": "> Reading URLs in iframes is restricted by the Same-Origin Policy, so running code within the same origin suffices. Similarly, some authorization servers use web messaging to send the code from the frame to main application context, which is also origin-based.\r\n\r\nAh, good point.\r\n\r\n> Additionally, I believe an attacker would be able to run a similar attack using a popup window instead of an iframe.\r\n\r\nIf the IdP is served with [COOP: `same-origin`](https://xsleaks.dev/docs/defenses/opt-in/coop/), it sounds like that would prevent code running in the application's origin from getting a `Window` handle back from a `window.open()` call to the IdP's URI. Though:\r\n\r\n* I don't think this would apply to a `window.open()` call to a URI in the application's Origin, which then redirects to the IdP\r\n* It's unclear whether that separation would continue if the pop-up navigated back to the application's Origin\r\n\r\nIf the application is served with [COOP: `noopener-allow-popups`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy), it sounds that would prevent any code running in its Origin from ever getting a `Window` handle back from `window.open()` calls to any URI. But the caveat is that [mode is still experimental, and not available in all browsers at present](https://caniuse.com/mdn-http_headers_cross-origin-opener-policy_noopener-allow-popups).\r\n\r\nAnd yes, all of this would completely break silent re-authentication flows... but the more I think about it, the more I think silent re-authentication may be a security risk anyway.\r\n\r\n> However, using a pattern like this makes it significantly harder to obtain refresh tokens (but not access tokens). It also forces the attacker to run a new flow to obtain one, which makes the interaction more visible.\r\n\r\nI agree it makes it harder for an attacker to acquire a refresh token, but both the visibility and the utility of that visibility is debatable:\r\n\r\n* If the IdP allows iframing for silent re-authentication flows, then the attacker can completely obscure the re-authentication flow.\r\n\r\n If the IdP *also* supports [`prompt=none`](https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters), then a malicious script could avoid triggering any browser UI indicative of a re-authentication flow (such as prompting for a password, security key or triggering a platform-level identity provider intent handler), and retry silently in the background.\r\n\r\n* If the IdP prevents iframing and the application has no [COOP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy), then you're relying on the user:\r\n\r\n 1. identifying that a pop-up window (or request for pop-up window) as abnormal behaviour\r\n 2. understanding that it *is* because of a successful XSS attack against their browser\r\n 3. being able to address or mitigate the issue\r\n\r\n An attacker could disguise it with some UI like \"your session has expired, allow this pop-up to log in again\", which wouldn't be out of character for many modern web apps.\r\n\r\n> The recommendation to use a web worker is mostly aiming for \"the best you can do in a shitty situation\".\r\n\r\nI agree it's a shitty situation.\r\n\r\nLike with all web apps (whether they're browser-based apps or not), you need to throw a lot of tools at them to lock them down (eg: CSP, CORP, COEP, COOP), which also limits which UI frameworks you can use and the application's UI.\r\n\r\nMy concern is that the implementation complexity of a Web Worker mostly impacts those *developing and deploying* an application, rather than someone attacking it.\r\n",
"createdAt": "2025-01-07T01:00:29Z",
"updatedAt": "2025-01-07T01:00:29Z"
},
{
"author": "philippederyck",
"authorAssociation": "CONTRIBUTOR",
"body": "In essence, it all boils down to the fact that a frontend client is a public client, with no authentication involved. The only security measure in the flow is restricting the `redirect_uri`, which is insufficient when an attacker has taken control of the origin. \r\n\r\nYou are correct that using COOP could help in preventing the popup-based scenario. However, that again would imply that legitimate popup-based scenarios might break because of this.\r\n\r\nEither way, using OAuth in the browser effectively increases the attack surface of the application (as explained in the spec), and adding additional security measures does not remediate this situation. Things like CSP etc. can definitely help and should be used, but are not a definitive defense against XSS (which is only one way malicious JS can end up in the app).\r\n\r\n\r\n> My concern is that the implementation complexity of a Web Worker mostly impacts those developing and deploying an application, rather than someone attacking it.\r\n\r\nI do want to revisit this. You have mentioned this earlier, but I don't really see the complexity issue you are referring to. Web workers are not shared across browsing contexts, and are simply a piece of code running in a separate context. The complexity is no more than running a silent flow in an iframe for example. This pattern is also actively used, for example in the Auth0 SDK (https://github.com/auth0/auth0-spa-js/blob/main/src/worker/token.worker.ts).\r\n\r\nSo instead of redeeming the authorization code in the main execution context, the application forwards it to the worker, which exchanges it for tokens. There is a one-on-one relationship between the main execution context and the worker. If the main execution context reloads, so does the worker (hence the need for the silent flow to obtain fresh tokens).\r\n\r\nAlso note that the use of a Web Worker is an implementation suggestion, not a normative requirement of the spec.",
"createdAt": "2025-01-07T07:10:00Z",
"updatedAt": "2025-01-07T07:10:00Z"
},
{
"author": "micolous",
"authorAssociation": "NONE",
"body": "> I don't really see the complexity issue you are referring to. Web workers are not shared across browsing contexts, and are simply a piece of code running in a separate context. The complexity is no more than running a silent flow in an iframe for example. \r\n\r\nThe main source of complexity is Web Workers require a separate JS module served at a separate URL, and implementing an RPC interface with JSON-serialisable objects. For a JS/TS application, that also requires that your framework's build process/bundler emitting separate modules like this. For WASM-based applications, that means setting up another build target for the authentication process, which is complicated, and crossing the JS-WASM boundary in more places.\r\n\r\nComparing to the alternative: storing the `access_token` and `refresh_token` in LocalStorage:\r\n\r\n* When the main execution context reloads, it doesn't need to spawn a `Worker` \u2013 it can acquire and refresh those values directly.\r\n\r\n* You call your OAuth2 implementation the same way as all your other code, without needing to worry about indirection, serialisation and deserialisation through an RPC interface.\r\n\r\n* There's no need for your framework, bundler or build process to support Web Workers at all. You also don't need to build a separate content security policy for the worker.\r\n\r\n* For WASM applications, token access and updates cross the JS-WASM boundary only once.\r\n\r\nFor non-shared Web Workers and Local Storage based token storage, you'd also want to have a Lock around the token to ensure that only one of them tries to mutate the token (ie: refresh it if expired) at any time, and have some mechanism to allow the user to explicitly override it (eg: \"we're trying to log you in again already from another tab, but you can log in here instead\") for non-silent flows.\r\n\r\n> This pattern is also actively used, for example in the Auth0 SDK (https://github.com/auth0/auth0-spa-js/blob/main/src/worker/token.worker.ts).\r\n> \r\n> So instead of redeeming the authorization code in the main execution context, the application forwards it to the worker, which exchanges it for tokens. There is a one-on-one relationship between the main execution context and the worker. If the main execution context reloads, so does the worker (hence the need for the silent flow to obtain fresh tokens).\r\n\r\nI already had a look at the Auth0 JS SPA SDK before making that assertion. When configured to use a Web Worker with storage in memory, it appears to roughly:\r\n\r\n1. When the `access_token` is expired, the main context messages the worker with a `response_type=refresh_token` request\r\n2. If it is a `response_type=refresh_token` request, the worker adds in any `refresh_token` from its closure (memory)\r\n3. The worker then sends the request to the IdP\r\n4. When the IdP responds, it stores the `refresh_token` in its closure (memory) and deletes it from the structure, before returning it to the main context.\r\n5. The main context stores the response in session storage or a cookie.\r\n6. When the `access_token` is missing or it can't be refreshed, the main context performs a `/authorize?response_type=code` request, and can attempt it in an iframe or with navigation (various conditions not relevant)\r\n7. When the IdP returns to the `redirect_uri` handler, the main context messages the worker with a `response_type=code` request to fetch the `access_token`.\r\n8. Worker sends the actual request to the IdP, caches the `refresh_token` from the response and strips it from the response passed back to the main context.\r\n9. Main context stores the response in session storage or a cookie.\r\n\r\nWith the Auth0 library, the main execution context is responsible for figuring out if a token has expired, and the worker is responsible for fetching any tokens, essentially proxying all (`/token`) requests.\r\n\r\nHowever, as above, there's nothing stopping a malicious script running in the main execution context from fetching the token directly, and caching it for itself. If the IdP allows silent flows (as it appears the Auth0 library may depend on), then this can all be done without the user noticing.\r\n\r\nI'm pretty sure I've found a weakness in the Auth0 worker implementation which would allow malicious code running in the main execution context to trick the worker into leaking the refresh token to a location other than the identity provider. I've emailed them privately about that. \ud83d\ude04 \r\n\r\nAs such, there's limited *benefit* to building it this way \u2013 possibly even none... and that complexity can end up introducing _more_ bugs in the process.\r\n\r\n> Also note that the use of a Web Worker is an implementation suggestion, not a normative requirement of the spec.\r\n\r\nUnderstood. I acknowledge that this is a hard problem, and it may not be possible to solve. \ud83d\ude04 \r\n",
"createdAt": "2025-01-08T04:06:19Z",
"updatedAt": "2025-01-08T05:38:22Z"
}
]
}
Expand Down

0 comments on commit da8a477

Please sign in to comment.