Date

Preventing unauthenticated requests to your WPGraphQL API

Someone asked in the Slack channel how they could lock down the WPGraphQL endpoint so that only authenticated users could access it.

Provided Solution

add_action( 'do_graphql_request', 'force_graphql_api_authentication', 10, 1 );

function force_graphql_api_authentication( $query ) {
	if ( ! defined( 'GRAPHQL_HTTP_REQUEST' ) || true !== GRAPHQL_HTTP_REQUEST ) {
		return;
	}

	$introspection_query = \GraphQL\Type\Introspection::getIntrospectionQuery();
	$is_introspection_query = trim($query) === trim($introspection_query);

	if ( $is_introspection_query ) {
		return;
	}

	if ( ! get_current_user_id() ) {
		throw new \GraphQL\Error\UserError( __( 'You do not have permission to access the API', 'your-textdomain' ) );
	}
}

What this does

Below, I’ll walk through what this snippet does.

Hook into the GraphQL request lifecycle

add_action( 'do_graphql_request', 'force_graphql_api_authentication', 10, 1 );

This snippet hooks into the do_graphql_request action, which is fired when a GraphQL request is about to be executed, and fires the function force_graphql_api_authentication

The action passes 4 args to the force_graphql_api_authentication callback: $query, $operation, $variables and $params. For this particular case, we only care about the first argument, $query, which is the query string to be executed.

Determine if the request is an HTTP Request

Since WPGraphQL can be used internally within your Plugin and Theme PHP code to hydrate data for page templates, shortcodes, etc, locking down all GraphQL requests could have unintentional consequences, so we don’t want to prevent all unauthenticated requests from executing with GraphQL, we just want to prevent unauthenticated requests coming over HTTP.

So we first check:

if ( ! defined( 'GRAPHQL_HTTP_REQUEST' ) || true !== GRAPHQL_HTTP_REQUEST ) {
  return;
}

This checks to see if the request is indeed a GraphQL HTTP Request. If it’s not, it simply returns and we let GraphQL carry on as usual. That means internal GraphQL requests using the graphql() function can be processed as usual.

Ignore Introspection Queries

GraphQL has an awesome feature where the Schema itself is queryable. Tools such as WPGraphiQL, GraphQL Playground and Altair make use of the IntrospectionQuery to fetch the Schema and render Schema documentation for users.

$introspection_query = \GraphQL\Type\Introspection::getIntrospectionQuery();
$is_introspection_query = trim($query) === trim(introspection_query);

if ( $is_introspection_query ) {
  return;
}

Here we use a helper method from the underlying GraphQL PHP library which is part of WPGraphQL to get the Introspection Query.

$introspection_query = \GraphQL\Type\Introspection::getIntrospectionQuery();

Then, we compare the incoming query, which is passed through the do_graphql_request action to check if the incoming query is an IntrospectionQuery or not:

$is_introspection_query = trim($query) === trim(introspection_query);

And last, if we’ve determined that the incoming query is indeed an IntrospectionQuery, we return and allow GraphQL to execute as normal. This will allow GraphQL to execute the Introspection Query and send the Schema back to the tool requesting it.

if ( $is_introspection_query ) {
  return;
}

Throw an error if the request is not authenticated

Lastly, we check to see if the request is authenticated by checking for the ID of the current user. If the ID is “0”, then the request is not authenticated, so we want to throw an error.

if ( ! get_current_user_id() ) {
	throw new \GraphQL\Error\UserError( __( 'You do not have permission to access the API', 'your-textdomain' ) );
}

Conclusion

With this snippet, you can lock down your WPGraphQL endpoint so nothing will be executed if the request is not authenticated.

If you need to make authenticated requests, we recommend using WPGraphQL JWT Authentication, but any of the Auth plugins that work for the REST API plugin _should_ work well with WPGraphQL as well.


NOTE:

The Application Passwords plugin requires a filter to play nice with WPGraphQL:

add_filter( 'application_password_is_api_request', function( $api_request ) {
  if ( defined( 'GRAPHQL_HTTP_REQUEST' ) && true === GRAPHQL_HTTP_REQUEST ) {
     return true;
  }
  return $api_request;
});