question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Concurrent requests mixes up context

See original GitHub issue

Hi!

We’re having a couple of issues with apollo-server-express and apollo-rest-datasource.

We do authentication and authorization based on a header. After validating the user, we fetch some user details and set them on the context, together with the auth header (to be used by datasources).

Our issue is that during simultaneous requests that context seems to be mixed up together with other request contexts, making it so that we supply underlying services with the wrong auth header.

async updateUserDetails(args) {
    // First request made, context is for the RIGHT user
    await this.post(`v1/user-details`, args);
    // Second request made, context is SOMETIMES for the WRONG user
    return this.getProfileInformation();
}

This means that we will, now and then, return information from the wrong user. Are we missing something obvious? We’ve looked through the documentation but we’re failing to find any solution to the problem. We’re kind of guessing due to apollo-servers popularity that we’re missing something.

Our context function:

    ...
    dataSources: () => services,
    context: async ({ req }) => {
      try {
        const authHeader = req.headers.authorization.replace('Bearer ', '');
        const userToken = await services.userValidationService.getUserToken(authHeader);
        const jwt = jwtUtils.verifyToken(userToken);
        
        const expires = jwt.exp * 1000;
        const userDetails = await services.partService.getUserDetails(
          jwt.sub,
          userToken
        );

        return {
          userToken: userToken,
          expires: expires,
          userDetails: userDetails
        };
      } catch (error) {
        throw new AuthenticationError('Could not authenticate');
      }
    },
    ...

The datasource functions:

  async willSendRequest(request) {
    const {
      context: { userToken }
    } = this.context;

    if (userToken) {
      request.headers.set('authorization', `Bearer ${userToken}`);
    } 
  }

  async updateUserDetails(args) {
    try {
      // First request made, context is for the RIGHT user
      await this.post(`v1/user-details`, args);
      // Second request made, context is SOMETIMES for the WRONG user
      return this.getProfileInformation();
    } catch (e) {
      throw new Error("Could not update user details");
    }
  }

  async getProfileInformation() {
    try {
      const res = await this.get('v1/user-details');
      return contactInformationModel(res);
    } catch (e) {
      throw new Error("Could not fetch user details");
    }
  }

Our test setup:

We’ve got a curl and a small bash script that continuously hits the api with an auth poll. They’re authenticated as two different users. We start the poll script and start executing the other curl request and keep watch of the results. Every now and then we get data from the wrong user.

curl 'http://localhost:3001/graphql' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'DNT: 1' -H 'Origin: http://localhost:3001' -H 'Authorization: Bearer dd0c6de0-8fe2-404e-98eb-d2cb628dd76a' --data-binary '{"query":"mutation($input: UserDetailsInput!) {\n    updateUserDetails(input: $input) {\n      phoneNumber\n      mobileNumber\n      emailAddress\n      wantsInformation\n      notificationPreference {\n        email\n        sms\n      }\n    }\n  }","variables":{"input":{"notificationPreference":{"sms":true,"email":true},"mobileNumber":"123123123","phoneNumber":"123123123","emailAddress":"asodh@odhf.se","wantsInformation":true}}}' --compressed
#!/bin/bash
for i in {1..100}
do
  curl 'http://localhost:3001/graphql' -H 'Authorization: Bearer 2a212708-af36-43bd-a92a-5834a65f29db' -H 'content-type: application/json' --data-binary '{"operationName":null,"variables":{},"query":"{\n  auth {\n    expires {\n      at\n    }\n    profile {\n      id\n    }\n  }\n}\n"}' --compressed
done

Any help would be appreciated!

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
cupofjoakimcommented, Nov 12, 2019

@ruizmarc We found out the hard way that this isn’t actually a problem with apollo (however, they could solve it if they wanted to). Turns out this is an issue in node and express overall. The underlying request object is actually overwritten.

Our solution was to move away from relying on passing data in apollo’s context and instead use a context based on cls (continous local storage). From Continuation Local Storage for easy context passing in Node.js:

CLS is a mechanism that allows the attachment of data to the current asynchronous execution context. It uses async_hooks to keep track of asynchronous context changes and to load and unload the data associated with it.

Since we had apollo-server-express we could basically write a middleware to replace the apollo context creation, and set the user context via the npm package express-http-context that basically exposes the store. Mind that using CLS does mean a small performance hit.

Apollos own context is basically exclusively used for us to pass along datasources now.

0reactions
glassercommented, Oct 24, 2022

(To be more explicit: the problem was not that context itself was shared across requests, but that the services object above was shared across requests (which “magically” ended up on contextValue.dataSources), and that our docs didn’t make it clear enough that you shouldn’t do that.)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Two concurrent requests getting mixed up in Node.js app
I have an app written in Node.js running on nginx with MongoDB in the backend. I have an 'authenticateUser' cal which takes in...
Read more >
Synchronization of concurrent HTTP requests in NodeJS
As the follow-up to my previous post, I wrote a simple NodeJS ... Both handlers define the context variable which includes the request...
Read more >
Using axios.all to make concurrent requests - LogRocket Blog
Learn about Axios' axios.all function for HTTP requests, differentiating Promise.all and axios.all, and making concurrent API requests.
Read more >
Implementing concurrent requests limit in ASP.NET Core for ...
After adding the middleware to the pipeline I've set up a test with 30 concurrent requests and 10 as MaxConcurrentRequestsOptions.Limit .
Read more >
Dealing with many concurrent and overlapped HTTP requests
(Ideally, I'd like to be able to release the CherryPy request handling thread and pick up the request context when the Twisted "deferred"...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found