react-native icon indicating copy to clipboard operation
react-native copied to clipboard

Cookie based authentication issues aggregation

Open Return-1 opened this issue 5 years ago • 30 comments

Environment

[skip envinfo]

Reproducible Demo

Provided in corresponding issues

Description

Issues closed while still open. Cookie based authentication is at this moment not usable. This is partially due to the following issues:

  • https://github.com/facebook/react-native/issues/23005 //TL;DR can only ever work with one cookie on Android
  • https://github.com/facebook/react-native/issues/929 //TL;DR redirect: 'manual' doesnt work

These issues have been closed even though they are still open and very relevant. There's more around cookies/fetch that i will try to hunt down in the following days. E.g one of the two platforms, i believe iOS , wont store cookies after app restart.

Conclusion

In general cookie based authentication is very problematic on multiple levels. If cookie based authentication is ~~claimed~~ implied to be supported on React Native and developers unknowingly structure their architecture around this these issues need attention. Otherwise people need to know before implementing a project using such an authentication mechanism as dozens of hours could be spend working on an architecture that is inevitably simply not supported.

This is not a matter of pointing fingers or demanding features. It is currently unfortunately misleading to leave people unaware of all these limitations as they might set out to create an architecture that's unsupported as i have.

At the very least maybe we should revise the documentation of fetch and explain how some things like "redirect:manual" dont work right now.

Return-1 avatar Jan 28 '19 09:01 Return-1

Can you run react-native info and edit your issue to include these results under the Environment section?

If you believe this information is irrelevant to the reported issue, you may write [skip envinfo] under Environment to let us know.

react-native-bot avatar Jan 28 '19 10:01 react-native-bot

I have the same issue as mentioned in #23005. Using the fetch functionality to do an authentication post returns a header with two cookies with the same name. On iOS these are concatenated. On Android only the second cookie is returned.

Example using postman:

set-cookie →token=XXXXXXXXXXXXXXXXXXXXXX; expires=Mon, 27-Jan-2020 12:45:43 GMT; Max-Age=31449600; Path=/

set-cookie →session=YYYYYYYYYYYYYYYYYYYYYYYYY; httponly; Path=/

Example RN iOS: "set-cookie": "token=XXXXXXXXXXXXXXXXXXXXXX; expires=Mon, 27-Jan-2020 12:47:58 GMT; Max-Age=31449600; Path=/, session=YYYYYYYYYYYYYYYYYYYYYYYYY; httponly; Path=/"

Example RN Android: "set-cookie": "session=YYYYYYYYYYYYYYYYYYYYYYYYY; httponly; Path=/"

React Native Environment Info: System: OS: Windows 10 CPU: (4) x64 Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz Memory: 4.83 GB / 15.68 GB Binaries: npm: 6.0.0 - C:\Program Files\nodejs\npm.CMD

cirediew avatar Jan 28 '19 13:01 cirediew

Yes, this is still a problem, and regarding your comment in other issue

means that it's a problem with the data structure used maybe?

you're absolutely right, it's a recently introduced bug in WritableNativeMap / WritableNativeArray classes. See https://github.com/facebook/react-native/issues/21795#issuecomment-430384534 https://github.com/facebook/react-native/issues/21795#issuecomment-430712904 https://github.com/facebook/react-native/issues/22064#issuecomment-435102468.

hey99xx avatar Jan 29 '19 06:01 hey99xx

If cookie based authentication is claimed to be supported on React Native

can you point out to me where this "claim" is written in the documentation?

kelset avatar Jan 29 '19 08:01 kelset

Surely. On the documentation found here: https://facebook.github.io/react-native/docs/network

React Native provides the Fetch API for your networking needs. Fetch will seem familiar if you have used XMLHttpRequest or other networking APIs before. You may refer to MDN's guide on Using Fetch for additional information.

However fetch does not support all the options as described at MDN. And since the only documentation provided on how to use it is MDN's site it follows that things like credentials:omit are supported by the networking layer.

Return-1 avatar Jan 29 '19 09:01 Return-1

Then I guess we should add in the docs, where we link to fetch, that cookies are not fully supported?

I mean, there is no direct reference to cookies in the RN docs so this phrase

cookie based authentication is claimed to be supported on React Native

is a bit passive-aggressive I feel 😅

Have you tried using third-party libraries? Do they use fetch?

kelset avatar Jan 29 '19 10:01 kelset

@kelset i apologise, implied is definitely a better word for it.

I hold immense respect both for you ( i am familiar with your contributions ) and the rest of the team and as i've already solved this issue by migrating to a token based architecture it is only for the purposes of helping others that i'm bringing this up.

How would you suggest proceeding from here? I can go ahead and edit the documentation referencing the corresponding issues but i am unaware of the processes as upon resolution documentation should be re-revisited.

Thanks so much for attending to this. Hopefully i will find some time to contribute to this issue myself.

Return-1 avatar Jan 29 '19 11:01 Return-1

I can go ahead and edit the documentation referencing the corresponding issues

As I mentioned above, probably the immediate step to be taken would be to clarify in the documentation that while we use fetch, there is not full support for cookies. And link to this issue as reference? Here's the link to the repo & file to change, if you can do a PR it would be lovely: https://github.com/facebook/react-native-website/blob/master/docs/network.md

In the meantime for now I've added the "known issues" label which should help framing this problem in the right perspective.

kelset avatar Jan 29 '19 11:01 kelset

Great, will proceed with that. There's more than cookies, for example a redirect cannot be omitted with redirect:manual. Im on it.

Return-1 avatar Jan 29 '19 11:01 Return-1

@Return-1

Hi, is this issue still being looked into? Thanks

rheng001 avatar Mar 01 '19 23:03 rheng001

@rheng001 not actively by me, i did add some documentation which is merged warning readers against usage which should at least save people a few hours.

Return-1 avatar Mar 02 '19 06:03 Return-1

@Return-1 is there a resolution plan for this issue in like 0.59? I also think this is a recent outstanding issue. There are a number of referenced issues which end up in here. It would be nice to see some progress (some commits mb) referenced from this issue.

nyilmaz avatar Mar 04 '19 06:03 nyilmaz

I agree,

It would be appreciated to see some references of this being addressed.

rheng001 avatar Mar 16 '19 00:03 rheng001

AFAIK nobody's currently working on it. We'd appreciate some movement around this issue though, so please keep the comments going and send PRs if possible.

thymikee avatar Mar 19 '19 14:03 thymikee

Hello, I had the same problem here and analysed the internal issue:

The problem is a combination of NetworkingModule.java and the usage of WritableNativeMap. The function translateHeaders maps the OKHTTP header objekt to a React Native header map. Headers with the same key was overridden because the hasKey method always return false.

https://github.com/facebook/react-native/blob/9895d011374e655bcaeb390167abafb9c01fef18/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java#L647-L661

That hasKey always return false is an implementation error in ReadableNativeMap and WritableNativeMap (which extends the readable map). This native maps supports two different modes, which can be enabled and disabled for all maps with a static boolean useNativeAccessor.

This flag defines if the ReadableNativeMap reads data from a native map or a internal (java.util.) HashMap.

But the problem was that WritableNativeMap always writes into the native map and never uses the (Readable) internal HashMap.

That was the reason why hasKey always return null if useNativeAccessor was false and only one header was returned by translateHeaders.

Like said by @hey99xx in https://github.com/facebook/react-native/issues/21795#issuecomment-430384534 and by @Return-1 https://github.com/facebook/react-native/issues/23005#issuecomment-457107736 its possible to activate the this native accessor, if you add this code to your own/project MainActivity.java:

Add this imports:

import com.facebook.react.bridge.ReadableNativeArray;
import com.facebook.react.bridge.ReadableNativeMap;

And this add the beginning of the createReactActivityDelegate method, before your application was loaded.

        ReadableNativeArray.setUseNativeAccessor(true);
        ReadableNativeMap.setUseNativeAccessor(true);

For me this works fine. But notice that this global flag may cause some other troubles.

I also started to fix this behaviour in the latest React Native 0.59.3 release, but notice than that the master version of ReadableNativeMap/WritableNativeMap was already changed.

The commits https://github.com/facebook/react-native/commit/b257e06bc6c29236a1a19f645fb46b85b2ffc4d2 and https://github.com/facebook/react-native/commit/a062b34493f07b28378de2772914d838cb28e3d8 by FB eng @sahrens changed/removed the useNativeAccessor behaviour, so I hope this issue was also fixed with the next/upcoming minor release 0.60.0. 🙌

(I couldn't test the new version yet, because running from the npm package (also with sourcecode changes) works fine for me, but the sourcecode version of RN let fail some of my 3rd party libraries. I'm looking forward for a RC of 0.60.0 and maybe will update this text here then.)

jerolimov avatar Apr 04 '19 09:04 jerolimov

Can confirm the duplicate-header-disappearance issue hasn't been fixed yet, and the workaround doesn't work anymore either. @sahrens

pinstripe-potatohead avatar Aug 22 '19 21:08 pinstripe-potatohead

I am using cookie based authentication. On RN 0.59.8 I manually set Cookie: connect.sid=${ sessionId }, because on iOS cookie was not persisted by system. Now I am updating from RN 0.59.8 to 0.60.0 and see on dev server that connect.sid cookie is being doubled like Cookie: connect.sid=${ sessionId }, connect.sid=${ sessionId }. That is breaking authentication, because ',' used as a delimeter. Is it expected behavior?

guliash avatar Sep 26 '19 08:09 guliash

I am using cookie based authentication. On RN 0.59.8 I manually set Cookie: connect.sid=${ sessionId }, because on iOS cookie was not persisted by system. Now I am updating from RN 0.59.8 to 0.60.0 and see on dev server that connect.sid cookie is being doubled like Cookie: connect.sid=${ sessionId }, connect.sid=${ sessionId }. That is breaking authentication, because ',' used as a delimeter. Is it expected behavior?

credentials: 'omit' solved my issue. But it was not needed before 0.60.0 on iOS.

guliash avatar Sep 30 '19 06:09 guliash

I am using cookie based authentication. On RN 0.59.8 I manually set Cookie: connect.sid=${ sessionId }, because on iOS cookie was not persisted by system. Now I am updating from RN 0.59.8 to 0.60.0 and see on dev server that connect.sid cookie is being doubled like Cookie: connect.sid=${ sessionId }, connect.sid=${ sessionId }. That is breaking authentication, because ',' used as a delimeter. Is it expected behavior?

credentials: 'omit' solved my issue. But it was not needed before 0.60.0 on iOS.

It's also fix the issue for me.

dorcyv avatar Oct 07 '19 16:10 dorcyv

@guliash and @dorcyv can you check if any of the cookies set by the server contain quotation marks?

I noticed a smiliar issue with cookies with quotation marks after upgrading to 0.60. I haven't had the time to report it properly yet and this information will help me figure out if the issue is also occurring for other people.

Jyrno42 avatar Oct 07 '19 18:10 Jyrno42

@Jyrno42 no quotation marks.

guliash avatar Oct 07 '19 19:10 guliash

This is what happens for me (Android):

  • I'm loading an image from the endpoint with authorization
  • I'm using Cookie header to pass session information using a custom cookie
  • Server responds with Set-Cookie header, containing standard JSESSIONID cookie
  • After first such response custom Cookie header is no longer sent by Image component - it's always the value from Set-Cookie

xfyre avatar Oct 10 '19 13:10 xfyre

@grabbou Thank you for fixing this issue. It is an important step in unlocking cookie based authentication for React Native which at this point is only partially usable and might lead up to teams having issues with setting up their architecture as i unforutnately faced in the past. I will attempt to re-update the documentation with this.

If the issue below is also resolved, cookie-based authentication might be fully support from this point onwards. The issue is referenced on the original post and can be found in the following:

https://github.com/facebook/react-native/issues/929 https://github.com/facebook/react-native/issues/26311

Since at the time cookie based authentication was not a reality my team has migrated to token based however, solving the above issue might make it operational again and it could be claimed on the next release that it can be safely used from developers from this point onwards so i think it's an important thing to highlight.

Thank you for the awesome work

Return-1 avatar Nov 28 '19 11:11 Return-1

A new cookie based issue that is still valid in v0.62.0: Android's CookieJar (JavaNetCookieJar) makes it impossible to use cookies during development

rishabhpoddar avatar Mar 30 '20 22:03 rishabhpoddar

A new cookie based issue that is still valid in v0.62.0: Android's CookieJar (JavaNetCookieJar) makes it impossible to use cookies during development

Can concur this still exists and is an issue

stephanoparaskeva avatar Jun 14 '20 22:06 stephanoparaskeva

if this issue still exists, Can we use the below implementation? https://build.affinity.co/persisting-sessions-with-react-native-4c46af3bfd83

miladr0 avatar Sep 07 '20 19:09 miladr0

Can concur this still exits and is an issue:

What I did:

  1. Fetch using React Native Fetch API. I created a SPA chat for Browser to prove it sends the Set-Cookie, the repository
fetch("localhost:8080", {
  method: "GET",
});

I assume that Set-Cookie should store the cookie in React Native. I use React Native Debugger to see check the cookies in Application > Cookies but it's empty, I assume that's not where you can find the Cookies?

  1. Establish a WebSocket connection
const stompConfig = {
  brokerURL,
  onConnectCallback, // subscribe
  debugCallback
}
const stompClient = new StompJs.Client(stompConfig);
stompClient.activate();

My expected result is: The WebSocket handshake should pass.

My actual result is: The WebSocket handshake got denied (AcessDeniedException). It turns out that the WebSocket handshake does not contain Cookie header.

Are React Native going to support cookie-based authentication soon? I need not to use JWT (for functional purposes, I need to be able to invalidate session, etc).

What I've found:

  1. credentials: 'omit' actually get the Set-Cookie header in Response, while credentials: 'include', credentials: 'same-origin' or without the credentials key did not receive the Set-Cookie header in Response.

    This is the response.headers with credentials: 'omit'

    [Tue Jan 26 2021 19:54:03.771]  LOG      {"map": {"cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-length": "1406", "content-type": "text/html;charset=UTF-8", "date": "Tue, 26 Jan 2021 12:54:03 GMT", "expires": "0", "keep-alive": "timeout=60", "pragma": "no-cache", "set-cookie": "XSRF-TOKEN=e4a9c264-b887-4869-a55b-64fac6d0549d; Path=/", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-xss-protection": "1; mode=block"}}
    

    This is the response from curl -v localhost:8080

    * Rebuilt URL to: localhost:8080/
    *   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET / HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.55.1
    > Accept: */*
    >
    < HTTP/1.1 302
    < Set-Cookie: XSRF-TOKEN=bf141a5d-9d25-4945-99b9-8d7b96e08237; Path=/
    < Set-Cookie: JSESSIONID=877F42328F07BDCA550D7C447CE8C603; Path=/; HttpOnly
    < X-Content-Type-Options: nosniff
    < X-XSS-Protection: 1; mode=block
    < Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    < Pragma: no-cache
    < Expires: 0
    < X-Frame-Options: DENY
    < Location: http://localhost:8080/login
    < Content-Length: 0
    < Date: Tue, 26 Jan 2021 13:05:15 GMT
    <
    * Connection #0 to host localhost left intact
    

Question:

  1. Where can I see the cookies? In Browser you can open Developer tools > Application > Cookies. I have tried to look in React Native Debugger > Application > Cookies but it always return empty.

jasonrichdarmawan avatar Jan 26 '21 10:01 jasonrichdarmawan

My team has also run into this issue, and it was extremely challenging to debug. We were using axios with React Native 0.64.3 along with cookie based authentication.

Our function looks like this:

axios.get('https://some-url-to-our-service.com', {
  headers: {
    'Content-Type': 'application/json',
    Cookie: `session_token=${tokenVariable}; userID=${userIDVariable};`,
  },
})
  .then((response) => resolve(response))
  .catch((error) => reject(error));

This code worked in all of our Jest specs, including integration specs that actually called the production server.

We also have a Postman set up that we use to test, debug, and otherwise hack on our backend services. Using the same tokenVariable and userIDVariable with that URL was working.

But in the app, we were getting a bunch of 401 authentication errors. The server wasn't recognizing our cookies.

I tested this using plain fetch, and it still wasn't working.

Then I found this thread and specifically https://github.com/facebook/react-native/issues/23185#issuecomment-536420223. With fetch omitting the credentials, everything started to work.

I haven't gotten around to patching our Axios call yet, but based on this issue, I think the Axios equivalent of credentials: omit is withCredentials: false.

If you're here and you're using Axios in React Native with cookie based authentication, you might want to try something that looks like:

axios.get('https://some-url-to-our-service.com', {
  headers: {
    'Content-Type': 'application/json',
    Cookie: `session_token=${tokenVariable}; userID=${userIDVariable};`,
  },
  withCredentials: false,
})
  .then((response) => resolve(response))
  .catch((error) => reject(error));

I haven't tested that yet, but if I do, I will try to follow up here and confirm the fix.

I don't know what else RN should or can do here. I'm glad that I was able to find this thread from the link in the fetch docs, but it definitely burned a day for me, which is too bad. At the end of the day, I get that headers and cookies can be hard (I am no stranger to CORS). I just kind of wish this had been a smoother experience.

Thanks to everyone in this thread, I hope my comment can help someone out in the future.

EDIT 1: I did a hotfix on my local machine and withCredentials: false seems to be getting 200 responses with the data I would expect.

coolsoftwaretyler avatar Jun 07 '22 02:06 coolsoftwaretyler

@coolsoftwaretyler, hey Tyler, is this issue still a thing on the latest version of RN? With v2 of our API, we were planning to remove Authentication header-based JWT authentication, since storing the token in the cookie using the httpOnly param is better for security (at least in the browsers)

Kiura avatar Jan 14 '23 23:01 Kiura

@Kiura - I'm on React Native 0.69.7, so I can't speak to any versions more recent than that, but I will say I know that my comment is still relevant. A week or two ago, I ran into the same issue as I was making a refactor somewhere that didn't have appropriate test coverage, and I had accidentally removed my workaround.

It also looks like the RN docs for 0.71 have a note about cookie based authentication, so while I can't speak to it specifically, it seems reasonable that the issue is still there.

coolsoftwaretyler avatar Jan 14 '23 23:01 coolsoftwaretyler