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

Cookies not being sent to webview for iOS #188

Open
caralin3 opened this issue Apr 4, 2024 · 3 comments
Open

Cookies not being sent to webview for iOS #188

caralin3 opened this issue Apr 4, 2024 · 3 comments

Comments

@caralin3
Copy link

caralin3 commented Apr 4, 2024

Bug description:

Web view should be rendering a web page that has a cookie authentication. Instead it is rendering sign in page. This is happening on iOS after I upgraded to react native 0.73+. It was working properly before the upgrade. Android is working fine.

I have both thirdPartyCookiesEnabled={true} and sharedCookiesEnabled={true} enabled. Also in handleNavigationStateChange I reset the cookies for iOS:

async function resetCookies() {
    const cookies = await CookieManager.getAll(Platform.OS === 'ios');
    Object.keys(cookies).forEach(key => {
      const cookie = cookies[key];
      if (cookie.domain === domain) {
        CookieManager.clearByName(url, cookie.name, Platform.OS === 'ios');
      }
    });
  }

I also tried to sync the cookies manually with this in the onLoad and the onNavigationStateChange of <WebView />, but it still didn't work.

  const synchronizeCookiesiOS = async () => {
    if (Platform.OS === 'ios') {
      try {
        const allCookies = await CookieManager.getAll(true);
        for (const cookieKey of Object.keys(allCookies)) {
          const cookie = allCookies[cookieKey];
          if (cookie) {
            await CookieManager.set('https://' + cookie.domain, cookie);
          }
        }
      } catch (error) {
        console.error('Error synchronizing cookies:', error);
      }
    }
  };

Expected behavior:

Web view should render account page, not sign in page

Environment:

  • OS: Android, iOS
  • OS version: Android 34+, iOS 14+
  • react-native version: 0.73.6
  • react-native-webview version: 13.8.1
@karel-suchomel-ed
Copy link

karel-suchomel-ed commented May 12, 2024

One thing that could be causing this is that you are not setting the webkit boolean to true here:
await CookieManager.set('https://' + cookie.domain, cookie);
In resetCookies function you are reading those cookies from WebKit WKHTTPCookieStore storage. Also, you should turn sharedCookiesEnabled off for ios when using WebKit.

@caralin3
Copy link
Author

Thanks @karel-suchomel-ed! That fixed it for the initial Webview load, but when I leave the webview and then return to it, the cookies are not being synced and I am back to the sign in screen.

Any ideas on why re-entering the Webview, the cookies don't work?

<WebView
          style={styles.webview}
          source={{ uri: url }}
          javaScriptEnabled
          domStorageEnabled
          startInLoadingState={false}
          injectedJavaScript={script}
          onLoad={synchronizeCookiesiOS}
          onMessage={() => {
            // nothing
          }}
          onNavigationStateChange={handleNavigationStateChange}
          thirdPartyCookiesEnabled={true}
        />

@karel-suchomel-ed
Copy link

karel-suchomel-ed commented May 12, 2024

I think that for your use case you could use something similar that I used for sharing sessions between RN and WebView.
In our app we had a PHPSESSID cookie encoded in a JWT token and this worked for me (not 1:1 code):

const Example = () => {
  ...
  const [isReady, setIsReady] = useState(false)

  useFocusEffect(
    useCallback(() => {
      const getCookieHeaderString = async () => {
        const token = getUserAccessToken()
        if (token) {
          const cookie = getCookieFromToken(token)

          if (Platform.OS === 'android') {
            await CookieManager.clearAll(useWebKit)
          }

          await CookieManager.set(
            url,
            {
              name: 'PHPSESSID',
              value: cookie.sub,
              httpOnly: true,
              secure: true
            },
            useWebKit
          )
        }
        setIsReady(true)
      }

      if (customer) {
        getCookieHeaderString()
      } else {
        setIsReady(true)
      }

      return () => {
        setIsReady(false)
      }
    }, [customer])
  )

  if (isReady) {
    return null
  }

  return (
    <Webview
      ...
    />
  )
}

The key was to set the cookie before every webview mount. I didn't experience any false positives this way, even tho the documentation warns about this. If you need to persist browser history between WebView mounts, you could write your own logic and handle the URL with state. I was using this hook for managing the history stacks:

export default function useBrowserHistory(homepage: string) {
  const [backStack, setBackStack] = useState<string[]>([homepage])
  const [forwardStack, setForwardStack] = useState<string[]>([])

  const visit = useCallback(
    (url: string) => {
      if (!backStack.includes(url)) {
        setForwardStack([])
        setBackStack((prev) => [...prev, url])
      }
    },
    [backStack]
  )

  const back = useCallback(
    (steps: number = 0) => {
      let i = steps
      const tempBackStack = [...backStack]
      const tempForwardStack = [...forwardStack]
      while (tempBackStack.length > 1 && i-- > 0) {
        tempForwardStack.push(tempBackStack[tempBackStack.length - 1])
        tempBackStack.pop()
      }
      setForwardStack(tempForwardStack)
      setBackStack(tempBackStack)
      return tempBackStack[tempBackStack.length - 1]
    },
    [backStack, forwardStack]
  )

  const forward = useCallback(
    (steps: number = 0) => {
      let i = steps
      const tempBackStack = [...backStack]
      const tempForwardStack = [...forwardStack]
      while (tempForwardStack.length > 0 && i-- > 0) {
        tempBackStack.push(tempForwardStack[tempForwardStack.length - 1])
        tempForwardStack.pop()
      }
      setForwardStack(tempForwardStack)
      setBackStack(tempBackStack)
      return tempBackStack[tempBackStack.length - 1]
    },
    [backStack, forwardStack]
  )

  const resetHistory = useCallback(() => {
    setBackStack([])
    setForwardStack([])
  }, [])

  return {
    visit,
    back,
    forward,
    backStack,
    forwardStack,
    resetHistory
  }
}

I only reset the cookies for Android, because I had some issues overwriting other cookies there.

You should also store that session cookie in secure storage (MMKV for example), so you can set it before WebView load, because session cookies are wiped. I took inspiration from this: https://stackoverflow.com/questions/62057393/how-to-keep-last-web-session-active-in-react-native-webview but instead of passing the cookie through cookie header in source I set it via the CookieManager.

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