launchdarkly / react-client-sdk

LaunchDarkly Client-side SDK for React.js
Other
86 stars 68 forks source link

Secure mode disables streaming in the React SDK #102

Open cdcasey opened 2 years ago

cdcasey commented 2 years ago

Is this a support request? This issue tracker is maintained by LaunchDarkly SDK developers and is intended for feedback on the SDK code. If you're not sure whether the problem you are having is specifically related to the SDK, or to the LaunchDarkly service overall, it may be more appropriate to contact the LaunchDarkly support team; they can help to investigate the problem and will consult the SDK team if necessary. You can submit a support request by going here or by emailing support@launchdarkly.com.

Note that issues filed on this issue tracker are publicly accessible. Do not provide any private account information on your issues. If your problem is specific to your account, you should submit a support request as described above.

Describe the bug When I have secure mode enabled, streaming mode does not appear to work. With secure mode enabled, I have to refresh a the page in my web app before seeing the results of having turned a feature flag on or off. I have even tried adding options: { streaming: true } to the initialization call (which is supposed to be unnecessary with the React SDK), but had no luck.

To reproduce Create a project in LaunchDarkly and set it to secure mode.

Create a boolean flag.

In the entry point, initialize the app like normal:

(async () => {
    const LDProvider = await asyncWithLDProvider({
        clientSideID: <CLIENT SIDE ID>
    });

    ReactDOM.render(
        <Router>
                <LDProvider>
                    <App />
                </LDProvider>
        </Router>,
        document.getElementById("root")
    );
})();

In the main app, update the the user identity once a user is logged in:

    // user is an object that comes in from the identity provider
    // all its attributes have been confirmed and are present
    useEffect(() => {
        async function updateUserIdentity(hash: string) {
            await ldClient.identify(
                {
                    key: user.id,
                },
                hash
            );
        }

        if (isAuthenticated) {
            const userMetadata = user[metadata];

            if (userMetadata.ldHash) {
                updateUserIdentity(userMetadata.ldHash);
            }
        }
    }, [user, ldClient, isAuthenticated]);

Set some component to display conditionally depending on the presence of the flag. Then in the LaunchDarkly dashboard, toggle the flag.

Expected behavior I expect to see the component disappear or appear based on how the flag is toggled. Instead, nothing changes until I refresh the page of the web app.

SDK version 2.23.2

Language version, developer tools React 17.0.2

OS/platform This behavior has been seen in the latest versions of both Firefox and Chrome.

Additional context With the same configuration, if I turn off secure mode, the flag behaves as expected. I did try to add options: { streaming: true }, to the initialization step, but that had no effect.

eli-darkly commented 2 years ago

If you look in the network area of the browser debugging tools, are you able to see a stream request happening and receiving some kind of error response, or is there no evidence of a stream request? I would strongly suspect the former— I can't think of any code path that would make the SDK actually disable streaming based on secure mode being on; much likelier is that it is failing to send the configured hash value in the request, causing LaunchDarkly to reject the request because in secure mode all requests from browsers must have a hash value.

cdcasey commented 2 years ago

I see no evidence of a stream request. What I mean by that is that I see two requests to app.launchdarkly.com, but no request to clientstream.launchdarkly.com. When secure mode is off, the request to clientstream is there. Is there something else I should be looking for?

I will say that in both cases, there are two requests to app.launchdarkly.com, presumably one on initialization and one for identify. Without secure mode, they both return 200. With it, the first returns 400 while the second returns 200. This gave me an idea:

In the initialization step, I added a dummy user with a key and hash that can be hard coded. Then identify switches the user when the app fully loads. With this bit of configuration, things behave as expected. This does seem like a hack, tho. Is this the proper way to do things?

eli-darkly commented 2 years ago

That's pretty odd. At first glance I don't see anything wrong with the way you were trying to do it the first time, or any reason why you should have to use the workaround you just described. So we'll need to investigate.

eli-darkly commented 2 years ago

Wait— you said your idea for a workaround was to initialize with a dummy user. But I'm unclear on what you were doing before you had that idea. It looks like you're not specifying a user at all, which would mean that the SDK is creating a dummy user with a unique ID, and that will never work in secure mode because there is no way for back-end code to generate a hash for that user.

I'm not 100% sure if that explains streaming being disabled, but it is a problem. Using secure mode really requires that every user has a hash generated before that user is referenced by the SDK, and that includes whatever user you're initializing with.

cdcasey commented 2 years ago

Initially, you're correct, there was no user in the initialization step. Presumably that's why one call to app.launchdarkly.com resulted in a 400. I figured this is what broke streaming, but admittedly I'm not sure.

As for the workaround, from the docs, I gathered that initialization needed to happen before the first DOM render. I used a dummy user there because I didn't see a way to specify a regular, logged-in user at that stage.

eli-darkly commented 2 years ago

I used a dummy user there because I didn't see a way to specify a regular, logged-in user at that stage.

Do you mean that the properties for the logged-in user aren't defined/available to you yet at that point, or that you didn't see a way to pass them to the SDK?

cdcasey commented 2 years ago

I used a dummy user there because I didn't see a way to specify a regular, logged-in user at that stage.

Do you mean that the properties for the logged-in user aren't defined/available to you yet at that point, or that you didn't see a way to pass them to the SDK?

I mean that at that point, there is no available logged in user available to pass to the initialization call. Or if there is, I don't know how to access it (but I'm pretty sure there's just not one available).

maximebousquet commented 2 years ago

Hi, any news about this issue? I've recently been encountering a similar problem where I'm using secure mode + asyncWithLDProvider, but since asyncWithLDProvider executes at the entry point of the app, I never have the user's information at that point and always end up getting the following error:

[LaunchDarkly] Invalid user specified. Please see https://docs.launchdarkly.com/sdk/client-side/javascript#initializing-the-client for instructions on SDK initialization.

The SDK then works properly once the user has logged in, but the error message is annoying.

lewisblackwood commented 1 year ago

I have a similar issue to that described above.

We use asyncWithLDProvider and secure mode.

However, we always see an initial response from LaunchDarkly's API:

Environment is in secure mode, and user hash does not match.

Once the app renders we run authentication and identify the user with the secure hash. But we can't do that prior to authentication.

What's the best way to handle this scenario with the React SDK and avoid the error?