yup-oauth2 icon indicating copy to clipboard operation
yup-oauth2 copied to clipboard

Server-side oauth flow

Open finomayato opened this issue 5 years ago • 6 comments

Hi there!

There is no Server-side oauth flow implemented in this library. While implementing a custom solution for myself I wanted to ask if there were any discussions about implementing server-side flow?

If not - I can help with that. But overall library implementation won't fit for this flow (as far as I could understand from spending some time with sources of this library). If somebody is open to collaborating on it - I will be glad to contribute

finomayato avatar Sep 02 '19 21:09 finomayato

Are you sure? I only skimmed it, but it sounded a lot like the InstalledFlow. Or are there some subtle differences?

dermesser avatar Sep 03 '19 07:09 dermesser

@dermesser Basically the same. But the main difference that in the installed flow we are ok with spinning up our own server. In the server-side flow - the server is already in use by the server side application. So, spinning up one more - will add unnecessary complexity. Hence, in order to implement it in this library we need to break “flow” in 2 parts:

  1. Get auth url we redirect user to
  2. Get the code from the url and fetch tokens

The first one should return redirect uri to the caller. The second one - consume the code generated when user logged in.

And that’s the problem. I don’t know how to “divide” the flow in this library on these two parts.

finomayato avatar Sep 03 '19 09:09 finomayato

I see. I will take a look when I find some time - in the meantime you are of course welcome to try some things yourself, if it is time-sensitive :)

dermesser avatar Sep 11 '19 19:09 dermesser

Having a ServerFlowAuthenticator that can be used as component in a larger web app is of interest to me. I've started poking about in the code, and the big hurdle I see is that currently all implemented authenticators directly drive the user interface to completion, while for a server flow the driver would sit outside:

  • when a login-to-google endpoint is hit, the web-app needs to ask the ServerFlowAuthenticator for the redirect URL
    • that URL has a CSRF token that needs to be stored until the user completes the auth flow
  • after the user has authenticated with Google, their browser is redirected back to the web-app, which then can hand off the querystring to the ServerFlowAuthenticator for verification and processing:
    • load and verify the CSRF token
    • use the authorization code from the querystring to exchange it for an actual token
    • associate that token with the current session

Note that these two steps could very well happen in two separate processes, e.g. when the web-app is horizontally scaling.

The current TokenStorage implementation (of course) doesn't support storing the CSRF token, and doesn't support storing more than one user's worth of tokens.

Similarly constraining is the way flows are called, keeping the library user from interfering with the user interaction.

I've got two different ideas on how to implement this, and would love to get some guidance from @dermesser and anyone else with a stake in this to see which one is more acceptable to this project:

The neater (but more intrusive) idea is based on the observation that all current flows and the server flow do have the same "get auth code", "exchange code for token" structure. I could break apart token(scopes) -> TokenInfo method into auth_code() -> AuthCode and token(scopes, auth_code) -> TokenInfo and provide some glue to hide this from library consumers for flows that can. The server flow would then replace the auth_code() method with something like auth_redirect_url() -> RedirectUrl { url, csrf_token } and auth_code_from_redirect_result(query_string, csrf_token) -> AuthCode`. The web app then can retrieve a TokenInfo based on the auth code and required scopes.

Alternatively I could create a separate Builder stack that creates an Authenticator that already has the auth_code and initial token exchange done. This would require less changes to the existing code, but might introduce some duplication and maybe some confusion around the different ways to create authenticators.

I haven't yet fully thought through how the web-app would store and re-hydrate authenticators/TokenInfos on every request, so this might be missing something crucial.

DavidS avatar May 14 '22 10:05 DavidS

Thank you @DavidS for your ideas. I'm open to accepting such changes in any case - however, I'm somewhat busy these days, so please give me a few more days to ponder on this :) I hope it is not a very pressing issue.

With that said, it is not surprising that you are hitting the limits of this library, as it originated in a somewhat simple client app (yup = youtube uploader) and is now being used for all sorts of different use cases. I hope we can join forces in adapting it to more demanding contexts like yours.

dermesser avatar May 16 '22 03:05 dermesser

I'm certainly in no hurry, thanks for your encouragement.

I've started poking around the edges of the problem in code-form over the weekend, and the first step only needs a utility function very similar to https://github.com/dermesser/yup-oauth2/blob/29a72447f6289cd7f9c731b43d15a562cd77fba6/src/installed.rs#L26

That could be an easy wrap to expose that as fn build_authentication_request_url<T>(app_secret: &AppSecret, scopes: &[T], redirect_uri: Option<&str>) -> String

With that being so simple, a ServerFlow might just be called as

ServerFlowAuthenticator::builder(
        app_secret, 
        ServerFlowQueryArgs.from_string("query args from the redirect back into the app"))
    .build()
    .await
    .unwrap()
    .token() // for example
    .await

If I get something working I'll certainly report back.

DavidS avatar May 16 '22 21:05 DavidS