Thanks for answering those questions.
Based on your answers, you could consider the following suggestions:
A. Storing tokens for read/proxy operations on client devices
Operations where the backend is only proxying requests to the third party API can be performed by the client application.
For that, you can initiate an Authorization Code flow with PKCE on the client application. This flow doesn’t require the client to present a secret, when exchanging the authorization code for a token.
The token can then be stored on the client’s device.
Pros:
- The backend no longer needs to gives a proxy token to the client application
- The client application can also store the refresh token, in order to refresh the access token, once it expires
Cons:
- Depending on the scopes requested and how sensitive the 3rd party API operations are, your security model might not allow you to store these tokens on the client application
- If the backend still needs to perform some transformations before delivering the result to the client application, it will still require a different token e.g. backend can run cron jobs at any time, the client application might not (phone has no network, etc.)
For more info on the second cons, please read the next section.
B. Storing tokens for long-lived/transformation operations in the backend
These type of operations might have a different security profile i.e. more sensitive, thus you probably don’t want the token on the client application.
Also, the lifecycle of these operations might differ from the typical client operation lifecycle e.g. they are longer lived.
Pros:
- The backend can be considered more difficult to compromise than client applications, thus you can trust it with more sensitive tokens.
Cons:
- If you’ve decided to also follow option A, you would have to ask twice to the end-user for consent, since you’ll have different scopes on the client tokens and the backend tokens.
C. Hybrid approach
This is similar to the approach you are using (it might actually be the same, so I apologise if I didn’t completely understood your explanation):
- The OAuth Client asking for access is the backend
- The Resource Owner (the end-user) grants access
- The backend obtains a token that allows it to perform any of the consented operations e.g. read and write
So far so good.
Now you can mint another token which allows the client application to access the backend API. This token can have a stricter scope of access e.g. read, since the client application might not need to perform all the operations initially request by the backend.
In this scenario:
- The OAuth Client asking for access is the client application
- The Resource Owner (the backend application, which hold the original token) grants access
- The client application obtains a token that allows it to perform a stricter set of operations
Pros:
- If your backend is already exposing an API to the client applications, which exposes operations other than the ones using the 3rd party API, this is probably a good way to bundle them all in one token.
- If a client application is breached, the blast radius can only big as the operations allowed by the client token (hopefully smaller than the one held by the backend)
- Revoking a client application token does not impact the tokens stored in the backend. Again, this allows you to have better control over client application breaches, without impact to the UX. (You don’t have to ask for consent to the end-user, since the backend token is intact).
Cons:
- This setup is a bit more complicated to build and understand
- Probably not worth doing, if your backend is only proxying the 3rd party API i.e. doesn’t expose any other operations related to your use case
That was a bit long, and this is not a simple topic. I hope this helps you evaluate if your solution is good enough and what tweaks can you make, in order to keep it clean.
Bear in mind that I am not, by all means, an OAuth 2.0 expert. So take this advice with a grain of salt