bhubr / react-simple-oauth2-login

Simple React component for OAuth2 login - Supports Authorization Code and Implicit Grant flows.
MIT License
49 stars 31 forks source link

onSucess when crossorigin #60

Open AndreaMaestri18 opened 1 year ago

AndreaMaestri18 commented 1 year ago

Hello everyone, thanks for the library first of all!

I'm working with a cross-origin request and I need to display the code from the response.

However, when I try it doesn't show up. If I set crossorigin to false I get an error in console about not being able to read the href from because of the cross-origin request. Nevertheless the code in that case is logged.

Has anyone encountered this before?

This is what I'm doing: <OAuth2Login authorizationUrl="authUrl" responseType="code" clientId={clientId} redirectUri={redirectUri} onSuccess={(e) => console.log(e)} onFailure={(e) => console.log(e)} isCrossOrigin={true} //if false, the console.log works buttonText={"Login"} />

In addition to this, the behaviour that I have with isCrossOrigin=false is ideal, i don't have in reality any error in the behaviour, so I was wondeering why is the error message appearing in console

bhubr commented 1 year ago

Hello Andrea!

Just to let you know: the isCrossOrigin flag is a rather recent addition. It was added by a contributor who had a different setup and workflow than mine.

I'd need to know:

Cheers

AndreaMaestri18 commented 1 year ago

I don't have any control on the server unfortunately, and no the cors are not properly configured. Therefore I can't just set isCrossOrigin to false otherwise it will show me this error: DOMException: Permission denied to get property "href" on cross-origin object from PopupWindow line 120.

However, once i complete the login and i am redirected, the code is console.logged and no more error is there.

On the other hand, if isCrossOrigin=true i dont get the error, but im not redirected in the same browser window (i'm redirected in the popup) and the code is not console.logged even tho the unsuccess callback is the same.

bhubr commented 1 year ago

Sorry for the delayed response.

When you say "I don't have any control on the server", you mean the server backing your React app, not the OAuth2 server?

That said, I might have a solution. The isCrossOrigin flag is supposed to be used when the redirectUri param points to an URL on the server.

You might try setting redirectUri to your React app's URL. In this case, your React app can extract the code from the URL and send it to your backend app, which then requests a token from the OAuth2 provider, then sends it back to your frontend app. But maybe you tried this already?

AndreaMaestri18 commented 1 year ago

no i mean the Oauth2 server, I don't have control on that, so I can't set the cors in the header of the response (I'm logging in a different application than my react app). The redirectUri that i use is localhost (because I want to be redirected in my app after I log-in)

bhubr commented 1 year ago

Ok. Sure, unless it's your own OAuth2 server & infrastructure, you can't control how it behaves regarding CORS.

The thing is, you're supposed to have your own backend app if you want to use the "authorization code" flow. This app can be on your localhost while you're developing. Then there are two cases:

bhubr commented 1 year ago

I could elaborate a little more but I'm unsure as to whether you have a backend app of your own??

AndreaMaestri18 commented 1 year ago

I have also a backend app yes! But the problem is that if the redirectUri is my react app and I don't use the isCrossOrigin I get the error I mentioned before

bhubr commented 1 year ago

Is your project open source? If so, I could have a look.

Otherwise I have a few possible hints:

If you need help with the last option (webserver with or without Docker), let me know what are your OS and server stack (PHP? Node? Python? other? which framework?) and I'll try to come up with the right setup instructions & config files.

bhubr commented 1 year ago

Hi @AndreaMaestri18

Have you progressed on the issue? Let me know if you need help!

Djalalov commented 9 months ago

Hi @bhubr,

First of all, thanks for contributing for this simple yet very useful component. I am having the same problem as @AndreaMaestri18 faced

Here are my details: Tech Stack => 1) Backend: Java 2) Front: React 3) Auth server: Jira

So all in all we have 3 parties involved in auth logic here and we are trying to implement the Auth Code grant flow.

We are having 2 different ports front React: localhost:3002 back Java: localhost:8080/callback port 8080/callback is given as redirect url and when auth is initiated from port 3002, user will be taken to the Jira server and give permissions to the app and returns to 8080/callback with accessToken which is nice

Problem: We can not get this access token in port 3002

bhubr commented 9 months ago

Hi @Djalalov

Thanks! I guess you had a question in mind?

Djalalov commented 9 months ago

Screenshot from 2023-11-29 14-02-45

We have the exact scenario described here but we dont understand what it means for the step 2. It says set the auth url in backend and it should return something. Can you please elaborate on this ?

bhubr commented 9 months ago

OK I kind of see,

I need to see some digging: the isCrossOrigin stuff had been added by a contributor which had this specific use case.

I had started a big overhaul of the docs but never got through it, and I definitely should get back to it.

But first, I'm gonna try and retrieve the code samples I think I have somewhere. I have some time right now so I'm gonna get right to it. Stay tuned, I'll keep you posted!

Djalalov commented 9 months ago

image

Here is the screenshot This is situtaion when we have crossOrigin = true

bhubr commented 9 months ago

[EDIT] SOLVED! See comment below!

Might I ask you some clarification please?

When you "authorize" your OAuth app in the popup window, are you directly redirected to http://localhost:8080/callback with the access token? That's what your screenshot seems to indicate...

What I mean is, don't you first get a "code" that you then "exchange" for an access token by sending a POST request to a specific endpoint on Jira?

bhubr commented 9 months ago

OK I made it work. I'll show you an excerpt adapted from the example in example/server (it's Node.js code but I'll comment it).

It's a bit specific to GitHub but I guess you don't have to worry about it, since your screenshot suggests that you already got the access token part right!

To summarize:

  1. You get back the code from Jira,
  2. You send the code to Jira with other params (secrets etc.), and it responds with an access token
  3. You wrap your final response to the client in a script that will send a message to the "parent" window of the popup, that will include the data you got back from Jira.
  4. That data will be passed to the onSuccess callback of the React component.

It is crucial that you set isCrossOrigin on the component, but you already got that part right too, don't you 😉 ?

Last thing: here I had to JSON-encode the parsed data that I got from GitHub. But seeing that you get JSON data back from Jira, I guess you can inject it in the script "as is", in lieu of ${JSON.stringify(data)}.

Let me know if that helped!

// Endpoint that handles the `/callback` URL
app.get('/callback', async (req, res) => {
  // Get the `code` parameter from the query string
  const { code } = req.query;
  // Those are parameters that I got from environment variables
  const { tokenUrl, clientId, clientSecret, redirectUri } = oauth;
  // GitHub wants everything in an url-encoded body
  const payload = qs.stringify({
    code,
    client_id: clientId,
    client_secret: clientSecret,
    redirect_uri: redirectUri,
    grant_type: 'authorization_code',
  });
  try {
    // Send the token request to GitHub (details may vary for other providers)
    const resp = await axios.post(tokenUrl, payload, {
      headers: {
        'content-type': 'application/x-www-form-urlencoded;charset=utf-8',
      },
    });
    // Since GitHub gives as back the result in url-encoded form, we need to parse it
    const data = qs.parse(resp.data);

    // **************************************************************************
    // 👇👇👇 The interesting part: sending the result back to the client 👇👇👇
    // **************************************************************************
    res.send(`<script>
      window.addEventListener("message", function (event) {
        if (event.data.message === "requestResult") {
          event.source.postMessage({"message": "deliverResult", result: ${JSON.stringify(
            data
          )} }, "*");
        }
      });
    </script>`);
  } catch (err) {
    console.error('Error while requesting a token', err.response.data);
    res.status(500).json({
      error: err.message,
    });
  }
});
Djalalov commented 9 months ago

[EDIT] SOLVED! See comment below!

Might I ask you some clarification please?

When you "authorize" your OAuth app in the popup window, are you directly redirected to http://localhost:8080/callback with the access token? That's what your screenshot seems to indicate...

What I mean is, don't you first get a "code" that you then "exchange" for an access token by sending a POST request to a specific endpoint on Jira?

Yes, its where things get complicated. What you are saying is the standerd flow and this was our plan at first IDEALLY. But that didnt work since we have CORS issue. We tried move the logic of exchanging the code to token in the backend right after when Jira hits 8080/callback

Djalalov commented 9 months ago

OK I made it work. I'll show you an excerpt adapted from the example in example/server (it's Node.js code but I'll comment it).

It's a bit specific to GitHub but I guess you don't have to worry about it, since your screenshot suggests that you already got the access token part right!

To summarize:

1. You get back the `code` from Jira,

2. You send the code to Jira with other params (secrets etc.), and it responds with an access token

3. You wrap your final response to the client in a script that will send a message to the "parent" window of the popup, that will include the data you got back from Jira.

4. That data will be passed to the `onSuccess` callback of the React component.

It is crucial that you set isCrossOrigin on the component, but you already got that part right too, don't you 😉 ?

Last thing: here I had to JSON-encode the parsed data that I got from GitHub. But seeing that you get JSON data back from Jira, I guess you can inject it in the script "as is", in lieu of ${JSON.stringify(data)}.

Let me know if that helped!

// Endpoint that handles the `/callback` URL
app.get('/callback', async (req, res) => {
  // Get the `code` parameter from the query string
  const { code } = req.query;
  // Those are parameters that I got from environment variables
  const { tokenUrl, clientId, clientSecret, redirectUri } = oauth;
  // GitHub wants everything in an url-encoded body
  const payload = qs.stringify({
    code,
    client_id: clientId,
    client_secret: clientSecret,
    redirect_uri: redirectUri,
    grant_type: 'authorization_code',
  });
  try {
    // Send the token request to GitHub (details may vary for other providers)
    const resp = await axios.post(tokenUrl, payload, {
      headers: {
        'content-type': 'application/x-www-form-urlencoded;charset=utf-8',
      },
    });
    // Since GitHub gives as back the result in url-encoded form, we need to parse it
    const data = qs.parse(resp.data);

    // **************************************************************************
    // 👇👇👇 The interesting part: sending the result back to the client 👇👇👇
    // **************************************************************************
    res.send(`<script>
      window.addEventListener("message", function (event) {
        if (event.data.message === "requestResult") {
          event.source.postMessage({"message": "deliverResult", result: ${JSON.stringify(
            data
          )} }, "*");
        }
      });
    </script>`);
  } catch (err) {
    console.error('Error while requesting a token', err.response.data);
    res.status(500).json({
      error: err.message,
    });
  }
});

Oh thanks a lot. Let me try.

bhubr commented 9 months ago

[EDIT] SOLVED! See comment below! Might I ask you some clarification please? When you "authorize" your OAuth app in the popup window, are you directly redirected to http://localhost:8080/callback with the access token? That's what your screenshot seems to indicate... What I mean is, don't you first get a "code" that you then "exchange" for an access token by sending a POST request to a specific endpoint on Jira?

Yes, its where things get complicated. What you are saying is the standerd flow and this was our plan at first IDEALLY. But that didnt work since we have CORS issue. We tried move the logic of exchanging the code to token in the backend right after when Jira hits 8080/callback

Interesting. Do you mean that you initially intend to redirect to localhost:3002/callback, then send the code to your backend, have it request the token and send it back to your React app?

I guess you already tried configuring CORS on the backend? I don't know how that would be done with Java (Spring Boot maybe?) but it's also a common issue when dealing with requests from a React app on a Node.js backend.

But then, there are ways to fix it without necessarily doing cross-origin requests:

bhubr commented 9 months ago

@Djalalov Did you eventually succeed?

Djalalov commented 9 months ago

@Djalalov Did you eventually succeed?

Hi @bhubr, yes.

After several attempts, we have just achieved desired result.

image

Just for the reference: This snippet is the key. Just because after Jira redirect user to the endpoint 8080/callback with code and state, backend was literally catching that response from Jira, exchanging code and state to access_token and not doing anything else. You snippet of code was actually the code we needed to send the data to the front.

I really appreaciate your help. Thanks

bhubr commented 9 months ago

@Djalalov Glad it helped!