Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't get OAuth2 login to work #2

Closed
burggraf opened this issue Jul 8, 2022 · 15 comments
Closed

Can't get OAuth2 login to work #2

burggraf opened this issue Jul 8, 2022 · 15 comments

Comments

@burggraf
Copy link

burggraf commented Jul 8, 2022

The only documentation related to OAuth login is this:

const userAuthData2 = await client.Users.authViaOAuth2("google", "CODE", "VERIFIER", "REDIRECT_URL");

But there is no indication of what "CODE" and "VERIFIER" mean, or how to use them. Can you add a more complete example of how to use this?

@ganigeorgiev
Copy link
Member

ganigeorgiev commented Jul 8, 2022

  • CODE is the code query param that is available in the url after the provider has redirected the user to your redirect url
  • VERIFIER is the PKCE challenge (aka. "code verifier") that is provided for each enabled client when calling GET /api/users/auth-methods

You could find a web example how to authenticate with OAuth2 in the "Manage Users > Auth via OAuth2" guide - https://pocketbase.io/docs/manage-users/#auth-via-oauth2

@burggraf
Copy link
Author

burggraf commented Jul 8, 2022

This guide is confusing: https://pocketbase.io/docs/manage-users/#auth-via-oauth2

The redirectUrl for both pages here is: http://localhost:8090/redirect.html
I'm confused here -- are these pages (index.html and redirect.html) supposed to be part of your client application or are these pages inside the Pocketbase binary somehow? If Pocketbase is listening on port 8090, should we be redirecting there?

@ganigeorgiev
Copy link
Member

ganigeorgiev commented Jul 8, 2022

Both pages are part of your client application (the example is for web but the OAuth2 API could be used also in mobile apps).

The prebuilt PocketBase binary allows serving static content by placing your client files inside pb_public directory.
This is where the above example pages are expected to be found. Doing so you'll have 2 cl:

For redirectUrl you have to use a url that points to your redirect handling endpoint.
For the web example we are using http://localhost:8090/redirect.html.

This url should be added in your OAuth2 provider app. This is different for each provider and some may not allow using http, localhost or deep link addresses for a redirectUrl. Google OAuth2 apps for example allows using localhost addresses only when your Google OAuth2 app is in "Test mode" (at least that was the case the last time I tested it).

In general, the OAuth2 authentication requires 2 steps:

  • auth code fetch step - this happen when you click on the "Login with..." link. After a successful authorization in the provider Sign-in/Sign-up page the user will be navigated to your app's redirectUrl address with some additional query parameters (code is the most important one).
  • code for token exchange step - this happens in the redirect handling page (aka. the page where we call client.Users.authViaOAuth2). We have to pass again the same redirect url, alongside other parameters (like the PKCE challenge) for verification purposes.

A good explanation of OAuth2 and the PKCE flow could be found in https://www.ory.sh/oauth2-for-mobile-app-spa-browser/#use-oauth2-with-pkce-instead.

I apologize. I'm not a native speaker and sometimes I've difficulties trying to properly explain things in English. If you have suggestions how to improve it, please let me know and I'll update the docs.

@burggraf
Copy link
Author

burggraf commented Jul 8, 2022

First, your English skills are excellent.
(One minor error:
Failed to authenticated
should be:
Failed to authenticate)

I've followed everything exactly but I can't get things to work. I am verifying that I get a proper code and provider.codeVerifier from the first call, but when I make the the following call to authViaOAuth2:

client.Users.authViaOAuth2(
      provider.name,
      params.get("code") || '',
      provider.codeVerifier,
      redirectUrl
  ).then((authData) => {
      console.log('authData', authData);
  }).catch((err) => {
      console.error('error', err);
  });

This throws an error:

{
  "errorDetails": "oauth2: cannot fetch token: 400 Bad Request\nResponse: {\n  \"error\": \"redirect_uri_mismatch\",\n  \"error_description\": \"Bad Request\"\n}",
  "errorMessage": "Failed to authenticated."
}

The full page URL for my auth redirect page looks like this (after going through the Google auth page):

http://localhost:8100/redirect?state=xxxx&scope=email%20profile%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/userinfo.email%20openid&authuser=0&hd=xxx.com&prompt=consent

@ganigeorgiev
Copy link
Member

ganigeorgiev commented Jul 8, 2022

For redirectUrl you have to pass the same url that you've appended to the links in your index.html.
In your case that should be just http://localhost:8100/redirect
You have to also set the same url in your Google OAuth2 app configuration.

@burggraf
Copy link
Author

burggraf commented Jul 8, 2022

That was it. The confusing part was that, inside of the redirect page (http://localhost:8100/redirect), you still have to redirect to http://localhost:8100/redirect (the same page.) I fixed that and it works!

I think it would really simplify things if you could somehow combine this into a single step. So if you could call an OAuth function in the Pocketbase binary, and just pass it the provider name (google) then have it redirect to the auth provider, that'd simplify it tremendously. The trick is getting the routing right. Let me think this through...

@ganigeorgiev
Copy link
Member

I'm glad that you've managed to make it work.

Providing twice the same url is required by the OAuth2 spec for verification purposes (theoauth2: redirect_uri_mismatch ... error is directly from the oauth2 call).

It is possible to add a redirectUrl configuration option in the admin area that could be passed seamlessly to the provider without requiring the end user to manually set it in the client.Users.authViaOAuth2 call.
But this will limit the integration options when you want to support multiple platforms at the same time.
Even for mobile, supporting both Android and iOS may require different special deep link address (aka. not starting with http/https) for each individual platform.

@burggraf
Copy link
Author

burggraf commented Jul 8, 2022

What if you have a separate redirect parameter for each provider in settings?

@ganigeorgiev
Copy link
Member

ganigeorgiev commented Jul 8, 2022

Yes, that is what I meant by "add a redirectUrl configuration option in the admin area" but that will be only for a single platform.

To mitigate this we would have to add 3 redirect fields (for web, android, ios) to each provider and I think that this will be even more confusing.

@burggraf
Copy link
Author

burggraf commented Jul 8, 2022

Can't you just send the redirectUrl parameter to the OAuth function call?

@ganigeorgiev
Copy link
Member

ganigeorgiev commented Jul 8, 2022

But we don't have the redirectUrl value. The first call (aka. when you click on the "Login with..." link) is directly to the provider's OAuth2 endpoint.
PocketBase is involved only in the code->token exchange in order to fetch the profile info and sing-in/sign-up the user in your application.

One possible workaround could be is to add a new PocketBase endpoint that will redirect to the providers OAuth2 login page and will store somewhere the user passed redirectUrl and when the user call client.Users.authViaOAuth2 PocketBase will find the redirectUrl and pass it behind the scenes. But this approach will require implementing sessions/cookies and I'd want to avoid that.

In any case, you'd have to pass somewhere the redirectUrl. In the current implementation the only drawback is that you have to set it on your own second time when making the client.Users.authViaOAuth2 call.

@burggraf
Copy link
Author

burggraf commented Jul 8, 2022

That's exactly what I was proposing, an endpoint that does everything in a single call. Pocketbase could temporarily store the redirectUrl in the database, couldn't it, and avoid implementing sessions/cookies?

It would require a unique redirect endpoint in the Pocketbase backend, but I think it's possible to make this whole thing work with a single API call.

That's the way we do it at Supabase: https://supabase.com/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers

@burggraf
Copy link
Author

burggraf commented Jul 8, 2022

You might also look into how the Supabase api works, sending both an error object and the authData at the same time. Because the way Pocketbase works now, it throws an error when you have an invalid email or password, and it doesn't handle it gracefully. There's no easy way to retrieve an error message (say, to know if it timed out or was an invalid user/password combination, etc.)

@ganigeorgiev
Copy link
Member

I don't think we can implement it the same way unless we add sessions/cookies (even if we store the redirect url in the database we'll have to link it to the previous request somehow, hence the sessions/cookies).

... sending both an error object and the authData at the same time. Because the way Pocketbase works now, it throws an error when you have an invalid email or password, and it doesn't handle it gracefully.

What do you mean by gracefully? In PocketBase I don't think it will make sense to return both error and user data because you don't have any user data when the request doesn't succeed (if we were using sessions or "guest logins" then it may make sense to have them as a fallback). The error message for email/pass login is intentionally generic to avoid (to some extend) users enumeration.

But I'll have a more detailed look at Supabase's API this weekend.

@ganigeorgiev
Copy link
Member

ganigeorgiev commented Jul 10, 2022

@burggraf I've reviewed Supabase and Appwrite's API and unfortunately I still think that we can't implement it in a single call in PocketBase due to lack of session cookies. Even if we handle the post-OAuth2 authorization action entirely server-side, on redirect we don't have a secure mechanism to return the auth data to the user.

One possible approach is to have a default redirect callback page in PocketBase that will take care to store the authorized data in the localstorage and then redirect the user to his preferred redirect endpoint (implying that they are on the same domain). But this will work only in the browser (that is actually how it's implemented in Presentator, where we have a default /auth-callback endpoint and users can register different providers by just enabling them in the configuration file).

I've opened a new issue in the main repo for better visibility with a proposal of using a persistent connections to handle even cases where redirect urls or deep links are not supported - pocketbase/pocketbase#55

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants