Web Security: Authentication and Authorization

Security is important to any application and each developer should understand the basics of how to create secure applications, as you are the first line of defense against an attacker.

Creating secure applications are not a easy tasks. Applications usually require: Authentication, Authorization, Cryptography, Signatures, Security protocols and so on.

Each of these features helps to create a barrier against a possible attack, each of them solve part of the problem of secure a web application.

In this series of post, you will learn as a developer, how to build secured web application, understanding the concepts, and implementing the features using Java and Spring.

In this post, you will learn the security basics, authentication and authorization.

TRY IT YOURSELF: You can find the source code of this post here.

Web Security Series

The Problem

Why should any web application be secured? well, transferring information through the web is insecure by nature, anyone can connect to your cable and steal the information. For instance, John and Mary have a relationship, and John sends a letter to Mary as you see as follows:

Simple message from John to Mary

However, Mary’s ex-boyfriend is pretty toxic and wants to bully her new relationship, so, he captures the letter and change it by other message, as you see here:

Toxic ex-boyfriend captures and change the message

This is called a Man in the Middle attack. A Man in the middle captures information that flows from origin to destination, changing or stealing it.

NOTE: Of course, in the web you will find other attacks, however, we are going to focus on Man in the middle in this post.

The first approach to avoid a Man in the middle is adding authentication and authorization. The initial definition of those approaches are as follows:

  • Authentication: Who are you?
  • Authorization: What can you do?

In the following sections you will see them in detail.

Authentication: Who are you?

Any message has an origin, the sender. The sender wants to communicate with a receiver, the destination. The destination needs to know if the sender is who he says he is, as the receiver only accepts messages from some specific origins.

For instance, in the John and Mary example, Mary (destination) wants to know if John (origin) really sent the message:

Mary wants to know if John sent that message

Mary only accepts messages from John, so, she must validate if the message’s origin was really him. Authentication helps here, it tells you if that origin is who he said he is.

Several strategies exist to validate who you are. As an example, we are going to use a basic authentication strategy, where you have a user and password that identifies you and the destination knows which users and passwords are valid.

For example, Mary keeps the user and password for John in a bucket of boyfriends as follows:

Mary authenticates John before accepting the message

Mary asks some questions to the toxic ex-boyfriend to know who he is, however, the toxic ex-boyfriend doesn’t know John’s password, so, Mary won’t believe John sent the message Hate you.

Now, what if the ex-boyfriend don’t capture and change the message? let’s see:

Mary accepts John message

Mary asks some questions to John and he responds well, as he knows his password, so, Mary responds with Love you too.

However, what if Mary accepts the message from the real John, but, he still doesn’t have rights to do that action from Mary’s perceptive? That’s Authorization.

Authorization: What can you do?

A destination acknowledges messages sent by a valid origin, those messages usually are actions, like query data or process information. However, a valid origin doesn’t mean the origin is allowed to send messages/actions to the destination, the origin needs to be authorized first.

The allowed messages/actions usually are grouped by roles. Those roles associates a user with a set of rights.

For instance, in John and Mary relationship, Mary defines two roles:

  • BOYFRIEND: allows to say Love you to Mary.
  • CAN_MEET_PARENTS: allows to meet Mary’s parents.

At first, Mary assigns to John the role BOYFRIEND, and John wants to say Love you to Mary, so, this is what happen:

Johns wants to say Love you to Mary

Mary accepts the message from John and responds with Love you too, as John has the role BOYFRIEND.

On the other hand, what happens if John wants to know Mary’s parents?, the following diagram shows:

John asks Mary if he can meet her parents

John has the role BOYFRIEND with the rights of telling Mary Love you, however, John doesn’t have the role CAN_MEET_PARENTS, so, Mary rejects that action from John.

Authentication and authorization work together to protect the information. Usually, each language/framework has plenty of utilities to handle authentication and authorization. In the next section, you will see how Spring Boot handles them.

Authentication and Authorization using Spring Security

Let’s build a web application using Spring Security to simulate the John and Mary relationship, the following diagram shows which components it has:

John and Mary relationship using Spring Security

We have John accesing Mary’s methods through the browser. Mary uses Spring Security to secure her methods and save the boyfriends in memory.

NOTE: Spring Security has other options to save users like databases. That is out of the scope of this series.

John communicates with Mary through RESTFul services, so, the Mary’s RESTFUL controller looks like as follows:

@RestController
public class MaryDestination {
  @GetMapping("love-you")
  @RolesAllowed({"ROLE_BOYFRIEND"})
  public String loveYou() {
    return "Love you too";
  }

  @GetMapping("can-meet-your-parents")
  @PreAuthorize("hasRole('ROLE_BOYFRIEND') and " +
          "hasRole('ROLE_CAN_MEET_PARENTS')")
  public String canMeetYourParents() {
    return "You can";
  }
}

We have two endpoints:

  • /love-you: Mary responds Love you too only if who calls that API, has the role BOYFRIEND.
  • /can-meet-your-parents: Mary responds You can only if who calls that API, has the roles BOYFRIEND and CAN_MEET_PARENTS.

NOTE: See the prefix ROLE in the annotations? Spring Security requires that prefix to match the roles defined.

@RolesAllowedand @PreAuthorize specify which roles are allowed to call a method, however, @PreAuthorize includes Expression Language, giving more flexibility, in this case, you see that Mary only accepts to meet her parents if you have BOYFRIEND and CAN_MEET_PARENTS roles.

NOTE: @RolesAllowedand @PreAuthorize apply to Spring bean methods, that means, we are not securing the RESTFul API, we are securing the methods of the MaryDestination bean, therefore, you can use these annotations in other Spring beans like @Component and @Service.

Spring Security requires to specify how the security will be setup, so, you need to create a class extending from WebSecurityConfigurerAdapter. The following is the security configuration Mary has:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true)
public class CustomWebSecurityConfigurerAdapter
        extends WebSecurityConfigurerAdapter {

  @Autowired
  public void configureGlobal(
          AuthenticationManagerBuilder auth)
          throws Exception {
      
    auth.inMemoryAuthentication()
            .withUser("john")
            .roles("BOYFRIEND"/*, "CAN_MEET_PARENTS"*/)
            .password(passwordEncoder()
                    .encode("honey"));
  }

  @Override
  protected void configure(HttpSecurity http)
          throws Exception {

    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .httpBasic();
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
}

You see a lot happening there, so, check one by one:

  • Line 2 enables web security, so, the default setup is active.
  • Line 3 to 6 enables method security. This allows the use of @PreAuthorize and @RolesAllows.
  • Line 8 is required to setup the security, inheriting from WebSecurityConfigurerAdapter
  • Line 15 starts a in memory authentication, where you save the users of your application.
  • Line 17 associates roles to an user named john. See you don’t need the prefix ROLE here.
  • Line 27 tells Spring Security to authenticate any endpoint in the application.
  • And finally, line 29 tells Spring Security to use basic authentication (user and password) to authenticate every request.

That’s all you need. If you start the application and try to access the endpoint http://localhost:8080/can-meet-your-parents, you will see the following in Chrome browser:

The browser requests authentication

Spring Security tells the browser the user needs to be authenticated. If you try as username john and password 123, you will get the same authentication dialog, as that password is invalid. However if you try with the password honey, you will see the following:

If john authenticates, still, he gets forbidden

John credentials are valid, he authenticates well, however, he doesn’t have the role CAN_MEET_PARENTS, so, the authorization failed, as John cannot meet Mary’s parents yet.

Of course, you should create integration tests for the security configuration, let’s see how.

NOTE: Remember to think twice when and how to create integration test. You can read more about here.

Integration Tests using Spring Security

Spring Security brings some utilities to test the security configuration. One of them is the @WithMockUser. @WithMockUser allows the test to define which user is authenticated into the Spring Context before you apply actions against the application. The following integration tests show how to use it:

@SpringBootTest
@AutoConfigureMockMvc
class WebSecurityAuthenticationAuthorizationApplicationTests {
  @Autowired
  private MockMvc mvc;

  @Test
  void contextLoads() {
  }

  @Test
  @WithMockUser(username = "ex-boyfriend", roles = {
          "EXBOYFRIEND"})
  public void loveYou_unknownUser_reject()
          throws Exception {

    mvc.perform(get("/love-you")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isForbidden());
  }

  @Test
  @WithMockUser(username = "john", roles = {
          "BOYFRIEND"})
  public void loveYou_userJohn_accept()
          throws Exception {

    mvc.perform(get("/love-you")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(
                    content().string("Love you too"));
  }

  @Test
  @WithMockUser(username = "john", roles = {
          "BOYFRIEND"})
  public void canMeetYourParents_userJohn_cannot()
          throws Exception {

    mvc.perform(get("/can-meet-your-parents")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isForbidden());
  }

  @Test
  @WithMockUser(username = "john", roles = {
          "BOYFRIEND", "CAN_MEET_PARENTS"})
  public void canMeetYourParents_userJohnWithRoleCAN_MEET_PARENTS_cannot()
          throws Exception {

    mvc.perform(get("/can-meet-your-parents")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().string("You can"));
  }
}

@WithMockUser requires the username and the roles that user will have. Spring Security automatically injects that user into the Security Context, as a valid authenticated user.

Besides, you see 4 test cases:

  • The toxic ex-boyfriend trying to talk with Mary, telling her Love you.
  • John trying to talk with Mary, telling her Love you.
  • John asking Mary if he can meet her parents. As he is not authorized yet, she rejected.
  • John asking Mary if he can meet her parents, after Mary’s approval.

Those 4 test cases show the whole interaction between Mary, John and the ex-boyfriend. However, as the test cases automatically injects a valid user to the Spring Security context, you don’t see how basic authentication works through HTTP. You will see that in the following section.

Integration Tests using RestTemplate

Well, sending basic authentication using HTTP is not complicated. The following is how the HTTP request is setup using RestTemplate:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class WebSecurityAuthenticationAuthorizationApplicationRESTfulTests {
  private final RestTemplate restTemplate = new RestTemplate();

  @Test
  void contextLoads() {
  }

  @Test
  public void loveYou_unknownUser_reject() {
    HttpHeaders headers = new HttpHeaders();
    headers.setBasicAuth("ex-boyfriend", "123");
    HttpEntity httpEntity = new HttpEntity(
            headers);

    Assertions.assertThrows(
            HttpClientErrorException.Unauthorized.class, () -> {
              restTemplate
                      .exchange("http://localhost:8080/love-you",
                              HttpMethod.GET, httpEntity,
                              String.class);
    });
  }

  @Test
  public void loveYou_userJohn_accept() {
    HttpHeaders headers = new HttpHeaders();
    headers.setBasicAuth("john", "honey");
    HttpEntity httpEntity = new HttpEntity(
            headers);

    ResponseEntity response = restTemplate
            .exchange("http://localhost:8080/love-you",
                    HttpMethod.GET, httpEntity,
                    String.class);

    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(response.getBody()).isEqualTo("Love you too");
  }

  @Test
  public void canMeetYourParents_userJohn_cannot() {
    HttpHeaders headers = new HttpHeaders();
    headers.setBasicAuth("john", "honey");
    HttpEntity httpEntity = new HttpEntity(
            headers);

    Assertions.assertThrows(
            HttpClientErrorException.Forbidden.class, () -> {
              restTemplate
                      .exchange("http://localhost:8080/can-meet-your-parents",
                              HttpMethod.GET, httpEntity,
                              String.class);
            });
  }
}

You see in line 1 how Spring boot setups its environment to run over a defined port (8080). Besides, you see how to set the basic authentication information using RestTemplate. RestTemplate sends that information as a Authorization header in the HTTP request, in the following form:

Authorization: Basic base64(user:password)

RestTemplate joins the user and password using two points (:) character, and transform that to base64.

NOTE: You might notice the header name Authorization, should it be Authentication? maybe….. usually you use Authorization header for authentication and authorization.

NOTE2: For more information about the authentication header using basic form, you can see here.

Now, is authentication and authorization in this way, enough to protect your application? let’s discuss it.

Are Authentication and Authorization enough?

Well, not alone. Let’s see another John and Mary example:

Toxic ex-boyfriend captures the message and change it

First, Mary authenticates John well, so, she accepted the Love you message. Second, John asks if he can meet her parents, however, the toxic ex-boyfriend captures the message and changes it to Hate you. As Mary already authenticated John, she thinks the second message is from him, so, she accepts it.

The test case moves to the following question:

How does the destination (Mary) know if a message was modified after the origin (John) deliveries it?

Hashing – Digest helps.

Final Thought

Understanding how to create secured we application is a must have for any developer, as you are the first line of defense.

Authentication and authorization are two main concepts about security you learned in this post, however, they are not enough to secure an application.

In the following post, you will learn about Hashing strategies, and how they help to detect when a message changes after the origin deliveries it.

If you liked this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.

5 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s