DEV Community

loading...
Cover image for Secure your GraphQL API within a Spring-Boot App

Secure your GraphQL API within a Spring-Boot App

imphilippesimo profile image imphilippesimo Originally published at Medium ・6 min read

This is the third episode of the series GraphQL, from Theory to Real-World with Spring boot. The episodes are designed to be consumed independently, so feel free to start from what interest ou the most.

Overview

In the previous article, we’ve exposed a set of resources via a GraphQL API. Now we want to secure them to prevent unauthenticated and unauthorized access.

TL;DR

Check the corresponding video.

NOTE: Reading the previous article IS NOT mandatory, they are designed to be consumed independently by just checking out the codebase on GitHub.

Spring security offers us the WebSecurityConfigurerAdapter::configure method to configure our app security. It gives us a way to define the access level of some resources based on URLs. Unfortunately, this is not a mechanism that fits well with the GraphQL protocol as it does with REST APIs.

Actually, graphQL is using only one endpoint, usually /graphql. Hence we cannot secure our resources based on URLs. As we want some resources to be either accessed only by administrators, or by authenticated users or even opened to the public.

But it becomes possible if we use annotations on resolvers methods, coupled with Aspect-Oriented Programming (AOP), and that’s what we are going to do.

So, at the end of this article, you will know how to:

  • Set up an aspect with Spring AOP,

  • Use aspect-oriented programming coupled with custom annotations to secure your resources,

  • Write tests to check your GraphQL API


Project Setup

Clone the code base that we had in the previous article:

git clone -n https://github.com/zero-filtre/springboot-graphql-error-handling.git && cd springboot-graphql-error-handling/

git checkout bd7a0b5c039c610de9c8355be0d1c5f5e5484655

git switch -c my_experimental_branch

We will make the resources:
createUser(username:string, password:string) public,

getUser(username:string, password:string) available for authenticated users only, and we will create an additional resource:

deleteUser(username:string), to be available for admin users only.

Add the following dependencies to the pom.xml

...
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <scope>test</scope>
</dependency>
...

Then we add the additional deleteUser resource which matches the following resolver method in UserMutation.java:

public User deleteUser(String username) {
   return userService.deleteUser(username);
}

Don’t forget to define the UserService::delete method, which just removes the user corresponding to that username from the in-memory List<User> users and returns information about the deleted user.

Add deleteUser resource spec to the schema definition file: src/main/resources/UserQL.graphqls :

type Mutation {
    ...
    deleteUser(username: String!): User
}

Securing the resources

The strategy consists of:

  • Authorizing all requests related to GraphQL: /graphql

  • Then, thanks to custom annotations, specify, at the method level in resolvers, which methods are public, which ones should be accessed only by authenticated users, and which ones should only be accessed by users with specific roles such as ADMIN.

  • Whilst with aspects, we will check if our annotations are applied or not and throw exceptions accordingly.

Let’s create the security configuration file: SecurityConfig.java

This config tells our app to authorize all requests related to GraphQL so that we can check them at the resolver level with annotations.

We will have two annotations:
@AdminSecured appended to admin resources, and
@Unsecured appended to public resources.
All other endpoints will, by default, need authenticated users access.
It’s always better to forget to make a resource unsecured than to forget to secure it 😁

AdminSecured.java

*/**
 * This annotation will trigger Admin security check for
 * the GraphQL resolver method where it is marked.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminSecured {}

Unsecured.java

*/**
 * This annotation will disable security check for
 * the GraphQL resolver method where it is marked.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Unsecured {}

Now go on and annotate the resolver methods accordingly:

@Unsecured
public User createUser(String username, String password) {
    return userService.createUser(username, password);
}

@AdminSecured
public User deleteUser(String username) {
    return userService.deleteUser(username);
}

UserQuery::getUser will be set as secured by default.

Let’s define the aspect that will scan these annotations.
First, we need to Enable AOP in the main application class,
by adding @EnableAspectJAutoProxy annotation like so:

@SpringBootApplication
@EnableAspectJAutoProxy
public class SampleGraphqlErrorHandlingApplication {
    public static void main(String[] args) {        SpringApplication.run(SampleGraphqlErrorHandlingApplication.class, args);
    }
}

Then the aspect class… But wait! ☝, What’s an aspect? 🤔

Aspects are related to Aspect-Oriented Programming (AOP) which gives you a way to encapsulate or modularize a behavior that is transversal and repeated across several types of objects and that is, normally, not easy to encapsulate in its own module.

Usually, those behaviors (logging, caching, transaction management…) are not related to business logic and they are so-called aspects.

An aspect is a common feature that’s typically scattered across methods, classes, object hierarchies, or even entire object models [3] .

Example:
AOP provides you with programmatic control to specify that you want calls:

  • To all methods annotated @Timed to trigger a timer before executing the actual body of that code,

  • To all methods classes in the package xxx.bank.account to trigger a specific additional security check,

  • etc

Without repeating yourself 😎

We are going to do something similar with our Aspect.

Yes, you have guessed it right, our aspect is about security check based on annotations.

SecurityGraphQLAspect.java

Don’t worry, It seems complicated but it’s actually pretty simple.
Let’s go through each part of it to understand what it’s being done here.

First, As we defined this spring component as @Aspect, it becomes an interceptor and we want it to be the first to be triggered, before any other interceptor, since it is our security guarantor. That’s why we use @Order(1)

The first method doSecurityCheckis annotated with @Beforewhich is an Advice, which means, according to AOP terminology: action taken by the aspect at a specific point during the execution of the program.

The @Before advice lets the action being executed before the specified PointCuts, which helps to categorize elements of the program that the aspect will watch.

doSecurityCheckis executed before all GraphQL resolvers methods that are defined within our project and that are not annotated as unsecured. These categories are then defined with the help of @PointCut.

doSecurityCheckchecks the Spring security context to make sure the user is authenticated.

Similarly: doAdminSecurityCheck is executed before *all methods annotated as AdminSecured. *

doAdminSecurityCheckchecks the Spring security context to make sure the user has the ADMIN role.

This is the code for the referenced UnauthenticatedAccessException.java:

public class UnauthenticatedAccessException extends RuntimeException implements GraphQLError {

    public UnauthenticatedAccessException(String msg) {
        super(msg);
    }

    @Override
    public List<SourceLocation> getLocations() { return null; }

    @Override
    public ErrorClassification getErrorType() { return null; }
}

That’s it! 🏁

Now let’s start the app, launch http://locahost:8080/graphiql and:

  • Create a user:

Everything works as expected ✌🏾.

  • Delete that user:

We do not have the right to do that as expected ✌🏾.

  • Get users:

We need to log in first as expected ✌🏾.

Let’s test the remaining cases programmatically so that you can automate it in a pipeline and earn some time.

GraphQLSecurityTests.java

Notice that we restart the context after each test method with @DirtiesContext to make sure every method runs in a clean, not polluted context. But you should not abuse this, as it increases considerably your tests execution time.
Run the test and should have a 100% success.

Now our endpoints are secured 😎.


Conclusion

GraphQL APIs are great but are different in so many ways from REST APIs, and the way to handle the security is no exception.

That’s why in this article, we saw how to use aspect-oriented programming coupled with custom annotations to secure our resources. Additionally, we also wrote tests to check the whole stuff.

I hope you enjoyed it.

In the next article, we will describe a strategy and apply a strategy to write fast, bulletproof tests for your GraphQL APIs with Spring boot.

Don’t forget, Feedback is a gift 🎁!


References:

[1]https://docs.spring.io/spring/docs/2.0.x/reference/aop.html

[2]https://mi3o.com/spring-graphql-security/

[3]https://docs.jboss.org/aop/1.0/aspect-framework/userguide/en/html/what.html

Discussion (0)

pic
Editor guide