question

David S avatar image
David S asked Yatin Gera Deactivated answered

Browser Javascript OAuth2 PKCE: SDK throwing "code_verifier required"

Hi all. Newbie to RC here, so perhaps I am doing something wrong. But...

In a browser (Firefox) using javascript and the RC js SDK, I am trying to implement the OAuth2 3 legged flow using PKCE and "top frame setup" (see Top frame setup on the JS SDK page). FYI I have successfully implemented the "Popup setup" described on the same page.

In the Top frame setup, when my redirectUri landing page calls rcsdk.login(loginOptions) the SDK throws an error with message "code_verifier required". It looks to me like the SDK is not sending the code_verifier when making the authorization_code request to the RC Auth server and so the RC Auth servers response is a 400 bad request.

I am not sure how the SDK would know the value of the code_verifier as I believe it was generated on the main page when the loginUrl was created and I don't see how in this scenario the landing page SDK is going to have that info. So, perhaps PKCE is not possible using the top frame setup?

I have attached in a zip including simplified versions of the main page and redirectUri page html/js for your viewing pleasure.

rcOAuthPkce.zip

Any advice or insight would be appreciated.

Thanks. -- David

sdkoauth2
rcoauthpkce.zip (2.4 KiB)
1 comment
1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Yatin Gera avatar image Yatin Gera ♦♦ commented ·

A small suggestion.
Pushing your code on github/bitbucket/gitlab or any such platform and sharing the link would be more helpful for us instead of attaching a zip with the code

0 Likes 0 ·
David S avatar image
David S answered Yatin Gera Deactivated commented

Hi again.

Ok, so in the main page, I was able to get the code_verifier from rcsdk and save it to the browser local storage. Then in the redirect page, I was able to read the cv, add it to the loginOptions and call login(). Once login() resolved, I reloaded the main page and when I clicked the login button, it found we were already logged in and no OAuth was needed :-). Of course, you can have the redirect page reload the main page and the main page could also try to log in automagically. But I leave that as an exercise for the reader.

Updated code is here on Github as a gist.

BTW, It would be good if RC added some info about PKCE to the JS SDK page.

Thanks to Yatin for the time and comments.

1 comment
1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Yatin Gera avatar image Yatin Gera ♦♦ commented ·

Thanks for sharing the code and details
Glad to be able to help :)

0 Likes 0 ·
Yatin Gera avatar image
Yatin Gera Deactivated answered

From what I can see, you are trying to re-login from the redirect.

The long answer (with some explanation) is
The rcOauthCallbackEx.html route will be called by the ringcentral auth server (assuming you have set up the route properly in the ringcentral developer app and you are running your app as a server)
You do not need to re-login from the rcOauthCallbackEx.html page. All you need to do is to call the RingCentral.SDK.handleLoginRedirect() function which will get the verification code and pass it to the main window which initiated login

The short answer is
Replace

var rcsdk = new RingCentral.SDK({
  server: "https://platform.devtest.ringcentral.com",
  redirectUri: "YOUR_REDIRECTURI_HERE",
  clientId: "YOUR_CLIENT_ID_HERE",
});

var loginOptions = rcsdk.parseLoginRedirect(window.location.hash || window.location.search);
rcsdk.login(loginOptions)
  .then(function(ret) {
    console.log("RC Login() return:");
    console.log(ret);
  }).catch(function(e) {
     alert("Login error " + e.message);
  });

With

RingCentral.SDK.handleLoginRedirect()

in your rcOauthCallbackEx.html file and try things out once

1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

David S avatar image
David S answered David S commented

Hi Yatin.

Thank you for taking the time to look at my question and posting a reply. I tried your suggestion and the redirectUri.html page received an error that opener is null on SDK.ts 57. That is b/c the flow I am implementing (as described on the JS SDK page) is the 'Top frame setup' not the 'Popup setup'. So on the original web app page (call it Page1), instead of getting the loginUrl and then calling loginWindow() (as is done for the Popup setup) this flow gets the loginUrl and then *navigates* to the loginUrl using the same browser window. So there is no popup or new page and thus there is no window.opener since, by design, we navigated away from Page1 to the loginUrl instead of opening a popup or new tab.

In the Popup setup (as I am sure you know), the only thing handleLoginRedirect() does is perform a postMessage() to the window.opener passing back the query params so that the call to loginWindow() resolves and Page1 can call login() passing the returned loginOptions. Login() will add the code_verifier (if set) to the token request. As I mentioned, I have implemented the Popup setup successfully, but for various reasons thought the Top frame setup might be more useful in my situation.

In the 'Top frame setup', if we weren't using PKCE, I think the redirectUri should do a login and then reload Page1 which will likely try to login to RC again and be successful (I think) since the access and refresh tokens will be stored in the browser local storage. But with PKCE we created the code verifier and code challenge in Page1 and so the redirectUri.html does not have it.

Hmm, so I am thinking that Page1, after creating the code verifier and code challenge must save the verifier to the browsers local storage. Then, in the redirectUri page, we must read the verifier and pass that to login (looks like it can be passed). Once the login is resolved, we can redirect to Page1 which will try to login to RC and find we are logged in - I hope.

But, unfortunately, I do not have access to the code_verifier (do I?) as it is embedded in the SDK.

Thanks again for your suggestion, it helped me dig deeper into things.

-- David

4 comments
1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Yatin Gera avatar image Yatin Gera ♦♦ commented ·

Hi @David S
This is interesting.
Sorry for missing out on the use case.
I now understand what you are trying to do.
Let me try to run this locally and see if I can find the cause and the solution to this.
I am glad you went through the SDK and saw how we post internally over the socket.

I will get back on this once I try this out
Please revert with the solution if you are able to find that in the meanwhile so that it can help others looking for a similar thing :)

0 Likes 0 ·
David S avatar image David S Yatin Gera ♦♦ commented ·

Hi Yatin. No worries.

I was just able to do what I was thinking -- get the code_verifier from the rcsdk after the loginUrl is created and save it to the browsers local storage. Then in the redirectUri read the cv from localstorage, save it into the loginOptions object and pass to login(). That had a clean result. Then when I reloaded Page1 it found we were already logged in and did not need to do any OAuth. :-)

I will post the code (once cleaned up) - to github as you suggested.

0 Likes 0 ·
Yatin Gera avatar image Yatin Gera ♦♦ David S commented ·

Perfect!
To summarize,

1. You are able to generate the login URL
2. Reach the ringcentral auth service on the top window
3. Log yourself in
4. Get a code (since you are not using implicit flow) generated on the redirect url route

Where things fail is when the SDK uses this code to make grant_type: authorization_code request to the token endpoint, it fails since code_verifier is not attached and sent for this login call

I am looking into the SDK to see if there is a bug there and maybe fix it

Like you said, the way to make to work right now will be something like

rcsdk.login({...loginOptions, code_verifier: SAVED_CODE_VERIFIER_FROM_THE_TOP_WINDOW})
0 Likes 0 ·
Show more comments
Yatin Gera avatar image
Yatin Gera Deactivated answered

On further analysis, I found the root cause
This is happening because you are creating a new instance of the SDK on the redirect page.
If you can share the same instance of SDK from the index.html and the redirect page, things will work
The SDK will save code_verifier when making the login call
can be seen here
https://github.com/ringcentral/ringcentral-js/blob/2bc7829c17fe196bfe7fb38d3752e8163dff67fc/sdk/src/platform/Platform.ts#L289

but because on the redirect page, a new SDK instance is used, it does not know what was the code_verifier

Hope this helps

1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Yatin Gera avatar image
Yatin Gera Deactivated answered

This is part of the SDK readme now
https://github.com/ringcentral/ringcentral-js/tree/master/sdk#top-frame-with-pkce-setup


Thanks, @David S for helping us identify this

1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Developer sandbox tools

Using the RingCentral Phone for Desktop, you can dial or receive test calls, send and receive test SMS or Fax messages in your sandbox environment.

Download RingCentral Phone for Desktop:

Tip: switch to the "sandbox mode" before logging in the app:

  • On MacOS: press "fn + command + f2" keys
  • On Windows: press "Ctrl + F2" keys