How to mock JWT authenticaiton in a Spring Boot Unit Test?

We Are Going To Discuss About How to mock JWT authenticaiton in a Spring Boot Unit Test?. So lets Start this Java Article.

How to mock JWT authenticaiton in a Spring Boot Unit Test?

  1. How to mock JWT authenticaiton in a Spring Boot Unit Test?

    If I understand correctly your case there is one of the solutions.
    In most cases, JwtDecoder bean performs token parsing and validation if the token exists in the request headers.
    Example from your configuration:
    @Bean JwtDecoder jwtDecoder() {

  2. mock JWT authenticaiton in a Spring Boot Unit Test

    If I understand correctly your case there is one of the solutions.
    In most cases, JwtDecoder bean performs token parsing and validation if the token exists in the request headers.
    Example from your configuration:
    @Bean JwtDecoder jwtDecoder() {

Solution 1

If I understand correctly your case there is one of the solutions.

In most cases, JwtDecoder bean performs token parsing and validation if the token exists in the request headers.

Example from your configuration:

    @Bean
    JwtDecoder jwtDecoder() {
        /*
        By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
        indeed intended for our app. Adding our own validator is easy to do:
        */

        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
            JwtDecoders.fromOidcIssuerLocation(issuer);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }

So for the tests, you need to add stub of this bean and also for replacing this bean in spring context, you need the test configuration with it.

It can be some things like this:

@TestConfiguration
public class TestSecurityConfig {

  static final String AUTH0_TOKEN = "token";
  static final String SUB = "sub";
  static final String AUTH0ID = "sms|12345678";

  public JwtDecoder jwtDecoder() {
    // This anonymous class needs for the possibility of using SpyBean in test methods
    // Lambda cannot be a spy with spring @SpyBean annotation
    return new JwtDecoder() {
      @Override
      public Jwt decode(String token) {
        return jwt();
      }
    };
  }

  public Jwt jwt() {

    // This is a place to add general and maybe custom claims which should be available after parsing token in the live system
    Map<String, Object> claims = Map.of(
        SUB, USER_AUTH0ID
    );

    //This is an object that represents contents of jwt token after parsing
    return new Jwt(
        AUTH0_TOKEN,
        Instant.now(),
        Instant.now().plusSeconds(30),
        Map.of("alg", "none"),
        claims
    );
  }

}

For using this configuration in tests just pick up this test security config:

@SpringBootTest(classes = TestSecurityConfig.class)

Also in the test request should be authorization header with a token like Bearer .. something.

Here is an example regarding your configuration:

    public static RequestBuilder getAllRoundsByUserId(String userId) {

        return MockMvcRequestBuilders
            .get("/users/" + userId + "/rounds/")
            .accept(MediaType.APPLICATION_JSON)
            .header(HttpHeaders.AUTHORIZATION, "Bearer token"))
            .contentType(MediaType.APPLICATION_JSON);
    }

Original Author Of This Content

Solution 2

try with @WithMockUser

    @Test
    @WithMockUser(username="ahmed",roles={"ADMIN"})
    public void shouldGetAllRoundsByUserId() throws Exception {

Original Author Of This Content

Solution 3

For others like me, who after gathering information from what seems like a gazillion StackOverlow answers on how to do this, here is the summary of what ultimately worked for me (using Kotlin syntax, but it is applicable to Java as well):

Step 1 – Define a custom JWT decoder to be used in tests

Notice the JwtClaimNames.SUB entry – this is the user name which will ultimately be accessible via authentication.getName() field.

val jwtDecoder = JwtDecoder {
        Jwt(
                "token",
                Instant.now(),
                Instant.MAX,
                mapOf(
                        "alg" to "none"
                ),
                mapOf(
                        JwtClaimNames.SUB to "testUser"
                )
        )
}

Step 2 – Define a TestConfiguration

This class goes in your test folder. We do this to replace real implementation with a stub one which always treats the user as authenticated.

Note that we are not done yet, check Step 3 as well.

@TestConfiguration
class TestAppConfiguration {

    @Bean // important
    fun jwtDecoder() {
        // Initialize JWT decoder as described in step 1
        // ...

        return jwtDecoder
    }

}

Step 3 – Update your primary configuration to avoid bean conflict

Without this change your test and production beans would clash, resulting in a conflict. Adding this line delays the resolution of the bean and lets Spring prioritise test bean over production one.

There is a caveat, however, as this change effectively removes bean conflict protection in production builds for JwtDecoder instances.

@Configuration
class AppConfiguration {

    @Bean
    @ConditionalOnMissingBean // important
    fun jwtDecoder() {
        // Provide decoder as you would usually do
    }

}

Step 4 – Import TestAppConfiguration in your test

This makes sure that your test actually takes TestConfiguration into account.

@SpringBootTest
@Import(TestAppConfiguration::class)
class MyTest {

    // Your tests

}

Step 5 – Add @WithMockUser annotation to your test

You do not really need to provide any arguments to the annotation.

@Test
@WithMockUser
fun myTest() {
    // Test body
}

Step 6 – Provide Authentication header during the test

mockMvc
    .perform(
        post("/endpointUnderTest")
            .header(HttpHeaders.AUTHORIZATION, "Bearer token") // important
    )
    .andExpect(status().isOk)

Original Author Of This Content

Solution 4

SecurityConfig bean can be loaded conditionally as,

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  @Profile("!test")
  public WebSecurityConfigurerAdapter securityEnabled() {

    return new WebSecurityConfigurerAdapter() {

      @Override
      protected void configure(HttpSecurity http) throws Exception {
        // your code goes here
      }

    };
  }

  @Bean
  @Profile("test")
  public WebSecurityConfigurerAdapter securityDisabled() {

    return new WebSecurityConfigurerAdapter() {

      @Override
      protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll();
      }
    };
  }

}

So this bean won’t be initialized in case of test profile. It means now security is disabled and all endpoints are accessible without any authorization header.

Now “test” profile needs to be active in case of running the tests, this can be done as,

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@WebMvcTest(UserRoundsController.class)
public class UserRoundsControllerTest extends AbstractUnitTests {

// your code goes here

}

Now this test is going to run with profile “test”.
Further if you want to have any properties related to this test, that can be put under src/test/resources/application-test.properties.

Hope this helps! please let me know otherwise.

Update:
Basic idea is to disable security for test profile. In previous code, even after having profile specific bean, default security was getting enabled.

Original Author Of This Content

Conclusion

So This is all About This Tutorial. Hope This Tutorial Helped You. Thank You.

Also Read,

Siddharth

I am an Information Technology Engineer. I have Completed my MCA And I have 4 Year Plus Experience, I am a web developer with knowledge of multiple back-end platforms Like PHP, Node.js, Python and frontend JavaScript frameworks Like Angular, React, and Vue.

Leave a Comment