Authenticating GraphQL requests

The problem is that we are not using the authentication everywhere at the moment. We verify that the user is who they say they are, but we do not recheck this when the requests for chats or messages come in. To accomplish this, we have to send the JWT token, which we generated specifically for this case, with every Apollo request. On the back end, we have to specify which request requires authentication, read the JWT from the HTTP authorization header, and verify it.

Open the index.js file from the apollo folder for the client-side code. Our ApolloClient is currently configured as explained in Chapter 4, Integrating React into the Back end with Apollo. Before sending any request, we have to read the JWT from the localStorage and add it as an HTTP authorization header. Inside the link property, we have specified the links for our ApolloClient processes. Before the configuration of the HTTP link, we insert a third preprocessing hook as follows:

const AuthLink = (operation, next) => {
const token = localStorage.getItem('jwt');
if(token) {
operation.setContext(context => ({
...context,
headers: {
...context.headers,
Authorization: `Bearer ${token}`,
},
}));
}
return next(operation);
};

Here, we have called the new link AuthLink, because it allows us to authenticate the client on the server. You can copy the AuthLink approach to other situations in which you need to customize the header of your Apollo requests. Here, we just read the JWT from the localStorage and, if it is found, we construct the header using the spread operator and adding our token to the Authorization field as a Bearer token. It is everything that needs to be done on the client-side.

To clarify things, take a look at the following link property to see how to use this new preprocessor. There is no initialization required; it is merely a function that is called every time a request is made. Copy the link configuration to our Apollo Client setup:

link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.log(`[GraphQL error]: Message: ${message}, Location:
${locations}, Path: ${path}`));
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
}
}),
AuthLink,
new HttpLink({
uri: 'http://localhost:8000/graphql',
credentials: 'same-origin',
}),
]),

For our back end, we need a pretty complex solution. Create a new file called auth.js inside the graphql services folder. We want to be able to mark specific GraphQL requests in our schema with a so-called directive. If we add this directive to our GraphQL schema, we execute a function whenever the marked GraphQL action is requested. In this function, we can verify whether the user is logged in or not. Have a look at the following function and save it right to the auth.js file:

import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express';

class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function(...args) {
const ctx = args[2];
if (ctx.user) {
return await resolve.apply(this, args);
} else {
throw new AuthenticationError("You need to be logged in.");
}
};
}
}

export default AuthDirective;

Starting from the top, we import the SchemaDirectiveVisitor class from the apollo-server-express package. This class allows us to handle all requests that have the AuthDirective attached. We extend the SchemaDirectiveVisitor class and override the visitFieldDefinition method. Within the method, we resolve the current context of the request with the field.resolve function. If the context has a user attached, we can be sure that the authorization header has been checked before and the identified user has been added to the request context. If not, we throw an error. The AuthError we are throwing gives us the opportunity to implement certain behaviors when an UNAUTHENTICATED error is sent to the client.

We have to load the new AuthDirective class in the graphql index.js file, which sets up the whole Apollo Server:

import auth from './auth';

While using the makeExecutableSchema function to combine the schema and the resolvers, we can add a further property to handle all schema directives, as follows:

const executableSchema = makeExecutableSchema({
typeDefs: Schema,
resolvers: Resolvers.call(utils),
schemaDirectives: {
auth: auth
},
});

Here, we combine the schema, the resolvers, and the directives into one big object. It is important to note that the auth index inside the schemaDirectives object is the mark that we have to set at every GraphQL request in our schema that requires authentication. To verify what we have just done, go to the GraphQL schema and edit postsFeed RootQuery by adding @auth at the end of the line like this:

postsFeed(page: Int, limit: Int): PostFeed @auth

Because we are using a new directive, we also must define it in our GraphQL schema so that our server knows about it. Copy the following code directly to the top of the schema:

directive @auth on QUERY | FIELD_DEFINITION | FIELD

This tiny snippet tells the Apollo Server that the @auth directive is usable with queries, fields, and field definitions so that we can use it everywhere.

If you reload the page and manually set the loggedIn state variable to true via the React Developer Tools, you will see the following error message:

As we have implemented the error component earlier, we are now correctly receiving an unauthenticated error for the postsFeed query if the user is not logged in. How can we use the JWT to identify the user and add it into the request context?

Schema directives are a complex topic as there are many important things to bear in mind to do with Apollo and GraphQL. I recommend that you read up on directives in detail in the official Apollo documentation: https://www.apollographql.com/docs/graphql-tools/schema-directives.html.

In Chapter 2, Setting Up GraphQL with Express.js, we set up the Apollo Server by providing the executable schema and the context, which has been the request object until now. We have to check if the JWT is inside the request. If this is the case, we need to verify it and query the user to see if the token is valid. Let's start by verifying the authorization header. Before doing so, import the new dependencies in the Graphql index.js file:

import JWT from 'jsonwebtoken';
const { JWT_SECRET } = process.env;

The context field of the ApolloServer initialization has to look as follows:

const server = new ApolloServer({
schema: executableSchema,
context: async ({ req }) => {
const authorization = req.headers.authorization;
if(typeof authorization !== typeof undefined) {
var search = "Bearer";
var regEx = new RegExp(search, "ig");
const token = authorization.replace(regEx, '').trim();
return jwt.verify(token, JWT_SECRET, function(err, result) {
return req;
});
} else {
return req;
}
},
});

We have extended the context property of the ApolloServer class to a full-featured function. We read the auth token from the headers of the requests. If the auth token exists, we need to strip out the bearer string, because it is not part of the original token that was created by our back end. The bearer token is the best method of JWT authentication.

There are other authentication methods like basic authentication, but the bearer method is the best to follow. You can find a detailed explanation under RFC6750 by the IETF at this link: https://tools.ietf.org/html/rfc6750.

Afterwards, we use the jwt.verify function to check if the token matches the signature generated by the secret from the environment variables. The next step is to retrieve the user after successful verification. Replace the content of the verify callback with the following code:

if(err) {
return req;
} else {
return utils.db.models.User.findById(result.id).then((user) => {
return Object.assign({}, req, { user });
});
}

If the err object in the previous code has been filled, we can only return the ordinary request object, which triggers an error when it reaches the AuthDirective class, since there is no user attached. If there are no errors, we can use the utils object we are already passing to the Apollo Server setup to access the database. If you need a reminder, take a look at Chapter 2, Setting Up GraphQL with Express.js. After querying the user, we add them to the request object and return the merged user and request object as the context. This leads to a successful response from our authorizing directive.

You can now test this behavior. Start the front end with npm run client and the back end using npm run server. Don't forget that all Postman requests now have to include a valid JWT if the auth directive is used in the GraphQL query. You can run the login mutation and copy it over to the authorization header to run any query. We are now able to mark any query or mutation with the authorization flag and, as a result, require the user to be logged in.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.113.197