Search code examples
authenticationspring-bootspring-securitygraphqlgraphql-java

authentication in spring boot using graphql


I’m working on a spring boot project with GraphQL. I'm using graphql-java-tools and graphql-spring-boot-starter. I managed to configure security and session management with spring security as you can see in the java config files below.

Now the “/graphql” path is secured (it can be accessed only sending the “basic http authentication” or a session token (x-auth-token) in a http header of the request). Authenticating with “basic http authentication” on any GraphQL operation will start a new session and send back the new session token in a header, and that token can be used further to continue that session.

How to give access to anonymous users to some GraphQL queries/mutations keeping the above behavior?

If I change antMatchers("/graphql").authenticated() to antMatchers("/graphql").permitAll() in order to allow anonymous access, then my custom AuthenticationProvider is not called anymore even when I try to authenticate with “basic http authentication”.

Thanks!

Here are my configs:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationProvider authenticationProvider;

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(authenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/graphql").authenticated()
            .and()
            .requestCache()
            .requestCache(new NullRequestCache())
            .and()
            .httpBasic()
            .and()
            .headers()
            .frameOptions().sameOrigin() // needed for H2 web console
            .and()
            .sessionManagement()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true)
            .sessionRegistry(sessionRegistry());
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 180)
public class HttpSessionConfig {

    @Bean
    public HttpSessionStrategy httpSessionStrategy() {
        return new HeaderHttpSessionStrategy();
    }

}

Solution

  • Instead of .antMatchers("/graphql").authenticated() we used .antMatchers("/graphql").permitAll(), then we removed .httpBasic() and also removed the custom AuthenticationProvider. Now the security configs look like this:

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/graphql").permitAll()
                .and()
                .requestCache()
                .requestCache(new NullRequestCache())
                .and()
                .headers()
                .frameOptions().sameOrigin() // needed for H2 web console
                .and()
                .sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .sessionRegistry(sessionRegistry());
        }
    
        @Bean
        public SessionRegistry sessionRegistry() {
            return new SessionRegistryImpl();
        }
    
        @Bean
        public HttpSessionEventPublisher httpSessionEventPublisher() {
            return new HttpSessionEventPublisher();
        }
    }
    

    Then we created a mutation for login that accepts the user's credentials and returns the session token. Here is the graphql schema:

    login(credentials: CredentialsInputDto!): String
    
    input CredentialsInputDto {
        username: String!
        password: String!
    }
    

    Basically the code we had in our custom AuthenticationProvider went into the service that is called by the login operation:

    public String login(CredentialsInputDto credentials) {
        String username = credentials.getUsername();
        String password = credentials.getPassword();
    
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
    
        ... credential checks and third party authentication ...
    
        Authentication authentication = new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        httpSession.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
        return httpSession.getId();
    }
    

    The key is that we prepared the session context with the authenticated user's authentication and then we save it (in redis) as a session attribute called "SPRING_SECURITY_CONTEXT". This is all what spring needs to be able to automatically restore the context when you make a request having the "x-auth-token" header set with the value of the session token obtained from the login operation.

    Now also anonymous calls are allowed because of .antMatchers("/graphql").permitAll() and in the service layer, on public methods we can use annotations like this: @Preauthorize("isAnonymous() OR hasRole("USER")").