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

Async context #225

Closed
richburdon opened this issue Nov 29, 2016 · 7 comments
Closed

Async context #225

richburdon opened this issue Nov 29, 2016 · 7 comments

Comments

@richburdon
Copy link

I need to authenticate my client requests using a JWT token sent as an "Authentication" header added by my client's middleware. I'm using a function to create the resolver's context object from the request [http://dev.apollodata.com/tools/graphql-server/setup.html#options-function].

But decoding a JWT token is typically an async operation (returns a promise) and the context variable doesn't support this.

graphqlExpress(request => ({
  schema: typeDefinitionArray,
  context: () => ({
    userId: verifyIdToken(request); // DOESN'T WORK
  })
}))

The workaround is to just add the header value to the context, but then each resolver: a) has to individually resolve it; b) all resolvers now become asynchronous. Seems like supporting promises directly in the context would be useful? Bonus to allow rejection to happen here if the user is not authenticated.

@smolinari
Copy link

Granted, I am no expert. But, is accessing data really an all or nothing thing, ever? I would venture to say, in most cases, probably not. If it is a web application, there is always going to be something available to view even for an anonymous user at some point. Thus yes, resolvers should individually check on "viewable" permissions. And, in general, most resolvers should be asynchronous too, so data can be fetched in parallel saving time. So, I think what you call a workaround is the solution. 😄

Rejection of the user up front should be done in your authentication process.

This is a great article on the subject.

https://dev-blog.apollodata.com/a-guide-to-authentication-in-graphql-e002a4039d1

Scott

@richburdon
Copy link
Author

Hi @smolinari, appreciate the comments (and link) but:

1). What do you mean by rejection of the user up front? Anything can make an XMLHTTP POST call to the /graphql endpoint; you could use curl from your laptop. The endpoint needs to be securely authenticated.

2). A GraphQL query may invoke many resolvers, so doing this check for each seems expensive (the JWT decoder will cache the token, but it seems like the wrong place to do this check).

OK, so I think I figured out a better way using async/await.

function getUser(request) {
  return new Promise() { DO ASYNC HERE };
}

router.use(''graphql', graphqlExpress(async function(request) {
  let user = await getUser(request)
  return {
    context: { user }
  }
}));

@smolinari
Copy link

What do you mean by rejection of the user up front?

I meant, if a user needs to be authenticated, that step should have been done, before the request is passed on to GraphQL.

2). A GraphQL query may invoke many resolvers, so doing this check for each seems expensive (the JWT decoder will cache the token, but it seems like the wrong place to do this check).

If the user is already authenticated, then it would be a validated user passed into the resolvers. There is no more checking on the user's authenticity at the resolver level. What happens next in the resolver is business logic for authorization to answer the question, "can this particular user see the data available through this resolver?" That question most definitely needs to be answered at that point too, and for each resolver (except the case of a completely public API).

Scott

@helfer
Copy link
Contributor

helfer commented Nov 30, 2016

@richburdon you can return a promise for an options object instead of an options object.

From the docs:

Alternatively, GraphQL Server can accept a function which takes the request as input and returns a GraphQLOptions object or a promise that resolves to one

@helfer helfer closed this as completed Nov 30, 2016
@richburdon
Copy link
Author

Thanks very much @helfer I missed that.

trevor-scheer pushed a commit that referenced this issue May 6, 2020
The operation registry was failing to get `apollo-graphql` because it was
not built within this root `package-lock.json`.  This wouldn't affect users,
but it did break our repo!
@dandv
Copy link
Contributor

dandv commented May 10, 2020

@helfer: can you link to where you found that, or better yet, to today's doc? Googling for "Alternatively, GraphQL Server can accept a function which takes the request as input and returns a GraphQLOptions object or a promise that resolves to one" find this issue as the best match.

trevor-scheer pushed a commit that referenced this issue May 14, 2020
The operation registry was failing to get `apollo-graphql` because it was
not built within this root `package-lock.json`.  This wouldn't affect users,
but it did break our repo!
@zanemayo
Copy link

Here is that info from the docs: https://www.apollographql.com/docs/apollo-server/data/resolvers/#the-context-argument

Context initialization can be asynchronous, allowing database connections and other operations to complete:

context: async () => ({
  db: await client.connect(),
})

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants