Learn how to handle authentication and authorization of a GraphQL server using Node.js and JWTs.
Let us know if you have any questions in the comments below!
Hi,
Thanks for this very interesting tutorial. I add the same authentication mechanism but I noticed slow performance on each authorized request.
I suspect the HTTP call make by the jwks client to add the network over trip.
What could be the solution to avoid that.
I used the cache option and it is better. But as I want to deploy this on a serverless platform I will meet some problem.
Is it safe to keep the jwks file or store the content into an environment variables ? To avoid the network trip ?
Thanks again for this great post
Hi Olivier! Glad you enjoyed the tutorial
There are multiple ways to solve this issue, like the cache one you tried. Another thing you could do is move the authentication to the context instead of the resolvers to verify the token. That way the token will only be verified once, but this takes away the certainty that you would always have a fresh token for the user.
Regarding this, I’ve also made a small change to the code for this tutorial which can be found here: Royderks/changes 1 by royderks · Pull Request #3 · auth0-blog/auth0-graphql-server · GitHub. With the current setup on every request to a resolver the database
variable will be recreated, this PR will solve that issue.
Let me know if this solves your problem!
Thanks for the post! Two questions about validate.js
:
-
Should the promise reject
if (error)
, rather than resolve? -
What do you think about abstracting away the whole
validate.js
code into one Auth0 SDK method that the developer can call?That is what Firebase Auth does, and validating JWTs is a one liner. With an extra line, you fetch the user.
if (req.headers.authorization) try { const idToken = req.headers.authorization.match(/^Bearer:\s+(.*)/)[1]; const verifiedToken = await firebase.auth().verifyIdToken(idToken); user = await firebase.auth().getUser(verifiedToken.uid); } catch (error) { console.error('Error in GraphQL authorization', error); }
Thank you for your feedback. We are in the process of revamping the story that we want to tell about identity and GraphQL. Thank you for sharing a model of what you think would be a good developer experience
@royderks what do you think about the first question, rejecting the Promise? Thanks!
@dan-auth0 Thanks, good to hear! I think my suggestion re. encapsulating the decoding and validation of the JWT in a server-side library call pertains to the server-side story in general. To me that code looks mostly like security/cryptography magic. And if Auth0 improves it in the future, those who’ve copy/pasted that snippet will be left behind.
However, I’m just starting to find my way among the vast documentation around Auth0, and I might have been spoiled by the simplicity of Firebase Auth, so if there’s a much simpler way of decoding the JWT on the server, feel free to point me in that direction. Thanks!
Hi @Civility! Unfortunately we cannot take full advantage of the reject method of the Promise, as GraphQL will throw an error at the “highest” level if we do so.
To break this down: Whether or not you send a valid token, we want the query to return a list of events. If the token is valid you’ll also be able to query the attendants, if the token is invalid the fields attendants will always be null
. Would the isTokenValid
return a rejected Promise, GraphQL will throw an error return null
for data instead. So every request with an invalid token won’t return any data.
Let me know if this makes sense to you
That can be avoided by making queries return types that can be nullable.
But I have a more serious problem with src/validate.js
: the client.getSigningKey
error callback received this error
:
Error: getaddrinfo ENOTFOUND undefined
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26)
The error doesn’t say what it was trying to fetch, but debugging shows that error.config.baseURL
is https://undefined/.well-known/jwks.json
, so presumably the jwksClient failed. However, process.env.AUTH0_DOMAIN
is set to civility-app.auth0.com
, so I can’t tell what’s wrong.
I solved the problem above by assigning the environment variables to module-scoped variables, but it would be helpful if the error were more precise (probably out of scope though).
Different question: how should the client handle the case when isTokenValid
returns that the JWT had expired?
Let me raise that internally with our GraphQL peeps