5

I have an endpoint in my kotlin app that looks like this:

    either.eager<String, Unit> {
      val sessionAndCookieUser = commonAuth.decryptCookieGetUser(getCookie(context), ::userTransform).bind()
      val user = sessionAndCookieUser.session.user
      val ctx = Ctx(ds, SystemSession, conf)
      val dbUser = getUserEither(ctx, user.id).bind()

      val signatureAlgorithm = SignatureAlgorithm.HS256
      val signingKey = SecretKeySpec(conf.get(ZendeskJWTSecret).toByteArray(), signatureAlgorithm.jcaName)

      val iat = Date(System.currentTimeMillis())
      val exp = Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)

      val token = Jwts.builder()
          .claim("name", dbUser.name)
          .claim("email", dbUser.email)
          .setIssuer(conf.get(StreamAppName))
          .setIssuedAt(iat)
          .setExpiration(exp)
          .signWith(signingKey, signatureAlgorithm)
          .compact()

      context.setResponseCode(StatusCode.OK)
          .setResponseType("application/json")
          .send(jsonObject("token" to token).toString())
    }.mapLeft {
      context.setResponseCode(StatusCode.UNAUTHORIZED)
    }

I am setting a response where I should send a jsonObject if a user is authenticated or UNAUTHORIZED if the user is not authenticated. When I am testing this endpoint in a browser I just get status unknown for that request - when I was debugging the backend, otherwise I get 200 with no response data. If I test it in postman I get json as a response. I see that token is being built and everything looks good on the backend side, but then response is not being loaded in the browser.

I am fetching it like this from react:

export const fetchGet = (uriPath: string) => 
  fetch(fullUrl(uriPath), {
    method: 'GET',
    credentials: 'include'
})

useEffect(() => {
    console.log('got here')
    fetchGet('/auth/token')
      .then(res => {
        console.log('res ', res)
       return res.json()
      })
      .then(res => {
        console.log('res.json ', res)
        return res.ok ? setJwtToken(res.token) : Promise.reject(res.statusText)
      })
      .catch(error => {
        console.log('err ', error)
        setError(error.toString())
      })
  }, [])

In the console I can only see 'got here' being logged, nothing else, and frontend crushed with an error:

DevTools failed to load source map: Could not load content for data:application/json;charset=utf-8;base64, longTokenString...: Load canceled due to reload of inspected page

What am I doing wrong here?

Updated

I found an issue here, I had 2 more useEffect functions, and they were redirecting before I had a result. I am not sure why was the useEffect function where I am passing the error state variable running when there was no change from initial state?

Here is the full code:

const [jwtToken, setJwtToken] = useState(null)
const [error, setError] = useState(null)

useEffect(() => {
    fetchGet('/auth/token')
      .then(async res => {
        const data = await res.json()
        if (!res.ok) {
          const error = data?.message || res.statusText
          return Promise.reject(error)
        }
        return data
      })
      .then(({token}) => setJwtToken(token))
      .catch(err => {
        console.log('err ', err)
        setError(err.toString())
      })
  }, [])

  useEffect(() => {
    if (jwtToken) {
      // window.location.href = `/mypage.com?access/jwt?jwt=${jwtToken}&return_to=`
      console.log(jwtToken)
    }
  }, [jwtToken])
  useEffect(() => {
    console.log(error)
    //window.location.href = '/login'
  }, [error])

Update nr. 2:

const [jwtToken, setJwtToken] = useState('')
  const { search } = useLocation()

  useEffect(() => {
    fetchGet('/auth/token')
      .then(async res => {
        const data = await res.json()
        if (!res.ok) {
          const error = data?.message || res.statusText
          return Promise.reject(error)
        }
        return data
      })
      .then(({token}) => setJwtToken(token))
      .catch(() => window.location.href = '/login')
  }, [])

  useEffect(() => {
    const params = new URLSearchParams(search)
    const returnTo = params.get('return_to') ? `&return_to=${params.get('return_to')}` : ''
    jwtToken !== '' ? window.location.href = `${url}/jwt?jwt=${jwtToken}${returnTo}` : null
  }, [jwtToken])

  return <p>Authenticating ...</p>

I have removed unnecessary error useEffect function, but now I get:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

I get this warning and it is also not redirecting after the token is fetched. What am I doing wrong this time around?

Leff
  • 1,968
  • 24
  • 97
  • 201
  • Can you `console.log(fullUrl(uriPath))` and report the result ... and/or show `fullUrl`'s code? – machineghost Oct 21 '21 at 01:37
  • What are you seeing in the console and network tabs after commenting out the redirects from your other useEffects? – JBallin Oct 21 '21 at 02:34
  • Please notice `error` is an object and that will make code inside `useEffect` loop forever because it uses the shallow comparison. – ShinaBR2 Oct 21 '21 at 10:00
  • how can I avoid that? @ShinaBR2 – Leff Oct 21 '21 at 10:02
  • @Leff you should remove that `useEffect`, and should handle redirect in your `.catch(err => {})` instead. – ShinaBR2 Oct 21 '21 at 10:06
  • @ShinaBR2 I have done some changes based on your suggestion, you can see it under new update in the question, but now I get a new warning and it fails to redirect after it fetches token – Leff Oct 21 '21 at 11:37
  • What library are you using to fetch? I remember trying to use the fetch API and `catch` not working, however don't quote me on that. I would try to check if something has errored in `then` instead. – lpetrucci Oct 21 '21 at 11:41

2 Answers2

1

Every useEffect callback will be invoked on first mount. You should include a simple if statement to ensure an error is set before running your error handling logic.

  useEffect(() => {
    if(error) {
      console.log(error)
      //window.location.href = '/login'
    }
  }, [error])

There is likely an issue with the CORS configuration of your API.

Access-Control-Allow-Origin response header must be set to the origin of your react app (it cannot be * for credentialed requests) and Access-Control-Allow-Credentials must be true. Failing to include them will result in an opaque response.

https://fetch.spec.whatwg.org/#cors-protocol-and-credentials

Andrew Gillis
  • 3,250
  • 2
  • 13
  • 15
0

Here is my completed answer. The main problem here is using useEffect incorrectly, especially with objects in the dependency array.

Let's talk about this code first

useEffect(() => {
  // TODO something with error
}, [error]);

Because error is an object and React useEffect use shallow comparison as you can see in this question. It will make the code inside that useEffect will run forever.

Next part, you get warnings because your use of redirect is not in the right way. Just remove that useEffect and it should work.

The reason why is, when we have an error, your code in your catch should run. Beside that, jwtToken will be changed at that time too. It will make your app redirected before the rendering process is completed.

ShinaBR2
  • 2,519
  • 2
  • 14
  • 26
  • I get that error useEffect was wrong, but why is the other where we set the token? If everything goes well when fetching, shouldn't that useEffect redirect normally? – Leff Oct 21 '21 at 12:04
  • @Leff because your logic to redirect and your logic inside `catch` are the same. You only need to handle only one time. – ShinaBR2 Oct 21 '21 at 12:23
  • Catch happens only if there is an error and redirects to a different place, while useEffect with token should happen only if token is changed and redirect to another place, so I am not sure what do you mean by same logic? – Leff Oct 21 '21 at 12:34
  • Your `useEffect` will change whenever `jwtToken` changed is correct. But the thing is, you handle redirect in that `useEffect` also, that will make the app redirect **before** it finish re-render. That cause the warnings you get. The same logic I mean is "redirect whenever we get error". You can choose handle it either inside `catch` or inside `useEffect`. – ShinaBR2 Oct 21 '21 at 12:37