0

I have a scenario I am considering, and I don't quite find out what's the best solution with OAuth. Hopefully I can learn good things here.

  • We are company A and we specialize in managing secure text notes.
  • They are company B.
  • Company B has users that access Company B site. Sometimes they authenticate, sometimes they don't.
  • Company B's users need a service to create and read text notes that Company B doesn't provide.
  • Company B hires company A to provide that service to their users.
  • We, company A, provide company B with a HTML + Javascript widget they can embed on their website, so that their users can use our functionality through Company B's site as if we didn't exist. We can provide the same widget to othher companies also, so it's kind of generic.
  • The widget we provide to Company B will access our services (Resource API), so we need to ensure these requests are coming from a legit client
  • We register the widget we provide to Company B as a client in our Identity server, with a client Id and a secret, and we provide that information to Company A so that they can retrieve an access token for their widget when their site loads at any user's browser.
  • When their users interacts with the widget, the widget is sending requests to our Resource API bearing the client access token, so that we know that the request should be authorized and is coming from Company B site.
  • A user accesses Company B site, the widget loads, and the user interacts with it to retrieve his notes, if any.

enter image description here

I have several concerns about this scenario and how could we deal with it.

  1. We could keep track of Company A's users by storing their unique Id (e.g: email) and the text notes for that email. But we can only trust what Company A tells us about that user, whether is really authenticated in their system or not. To us, the Company A's user is just an email, whether legit or not.
  2. We could restrict access to our API to only allow requests from Company B borrowed widget (which is actually our widget, but embedded in their site). But we cannot really know if the widget is requesting his own text notes or someone else's text notes.

In other words, a Company A user (e.g: [email protected]) could view/steal the access_token by checking his browser's http requests and seeing the Authorization header. And they could use that access_token to view the text notes for a different user (e.g: [email protected]), wouldn't he?

How could I approach this scenario with a more secure mechanism? I feel I'm missing something important on this puzzle.

1
  • It's only HTTP, your widget could rely on B's endpoints to fetch the access token. The idea is, only B knows the user in session or the user behind every request to A. If you make B's backend to be the proxy then the problem is solved by B's backend. Not your business. Other than that, you have to make your Authentication process more sophisticated to support user credentials as well. If A has no user credentials then it can not provide Authentication. Basically, you have no OAuth, you are providing an API token in a very complicated way.
    – Laiv
    Oct 31, 2022 at 11:03

2 Answers 2

2

Your current design does seem insecure: the company B can authenticate to your services via some secret, yet this secret must be transmitted to end user devices for your design to work. You can't trust those devices. There is no guarantee that requests are generated by your widget. As a simple example, a user could enter the browser's developer tools and then make arbitrary requests to your API.

I'm also worried about how you define your user's identity. In your design, the widget running in the browser obtains an access token for a specific user identity. However, your token provider has no means to verify this identity, so the browser could request tokens for any identity, giving them access to other users.

In order to more easily compare the authentication flows, here's your current design as an ASCII-Art sequence diagram.

Client        CompanyB      TokenProvider    Resource
------        --------      -------------    --------
 | getSecret()  |                |             |
 +------------->+                |             |
 +<-------------+                |             |
 |              |                |             |
 | getToken(joe@example, secret) |             |
 +------------------------------>+             |
 +<------------------------------+             |
 |              |                |             |
 | performAction(token)          |             |
 +-------------------------------------------->+
 |              |                |             |

One possible way to fix this is to have CompanyB manage the concept of users/identities, for example by issuing JWTs. This would merge the CompanyB and TokenProvider roles. There are a number of ways to verify these tokens. For example, CompanyB could cryptographically sign the tokens, and your API could check the signature against CompanyB's public keys. Alternatively, the tokens could be encrypted with your public keys, or with a shared secret (that's not know to users).

Client             CompanyB    Resource
------             --------    --------
 | logIn(joe@example) |           |
 +------------------->+           |
 +<-------------------+           |
 |                    |           |
 | performAction(token)           |
 +------------------------------->+
 |                    |           |

Another alternative is to keep your current design, except that the CompanyB backend server obtains the user's tokens, without ever disclosing API secrets to the client.

Client        CompanyB                TokenProvider    Resource
------        --------                -------------    --------
 | logIn(joe@…)  |                         |             |
 +-------------->+ getToken(joe@…, secret) |             |
 |               +------------------------>+             |
 |               +<------------------------+             |
 +<--------------+                         |             |
 |               |                         |             |
 | performAction(token)                    |             |
 +------------------------------------------------------>+
 |               |                         |             |

Perhaps your original design is mostly appropriate, but perhaps with two changes:

  • Instead of providing the real API key to the client, they are given a time-limited nonce token that can be used to access the TokenProvider. This allows CompanyB to perform some degree of rate limiting and security checks.

  • Users aren't directly given access to resources, but have to first register an account with your service. Later, the password can be used to verify whether the client is authorized to access the resources of a particular user like Joe or Jane.

Which of these many alternatives is appropriate depends on the trust relationships between the actors. What aspects about the user's identity are CompanyB and your service supposed to know? How does billing for this service work?

For example, in my suggestions that involve a log-in to CompanyB, the CompanyB backend obtains or produces a token that the client can use to interact with your API. However, this also means that CompanyB can impersonate the client, which may not be desirable.

1
  • I like your suggestions. I think I'm gonna explore having the Company B authenticate their users and generate a token for each specific user signed with a shared secret that only Company B back-end and my Company A know. That way, indeed the widget can make requests to our endpoints accompanied by that Company B issued token, and we can trust the request comes from a browser/person who Company B considers authenticated. No need for a token provider at our end for this scenario, true. Just the shared secret and expecting requests to contain a token signed with that secret, correct?
    – diegosasw
    Nov 3, 2022 at 12:42
1

The big red flag for me here is the fact that you are using a single client access token for an entire company. If you expect one user's notes to be locked down to just that user, that user needs to have a unique access token that only works for their account and nobody else's, otherwise as you say they could just change the email and view somebody else's private data.

Your authentication system would need to accept more than just the company level credentials. You might be able to use something like JWT which can encode specific permissions in a single securely signed token. It's tricky though when you have to just trust that the email you get sent from the client really belongs to the user, since they may or may not even be authenticated with their own company site.

1
  • I agree. I wonder how do systems like Chat Widgets work and whether this is the reason many sites online seem to have separated helpdesk system and, if you want to view your tickets, you need to authenticate in the support system separately.
    – diegosasw
    Oct 31, 2022 at 10:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.