How to Implement Basic Access Authentication in Spring Boot
Tags: cybersecurity, java, programming, softwaredevelopment
Published: 2024-01-19 21:11:36

Using a specifically designed framework like [Spring Security](https://spring.io/projects/spring-security) to achieve your security goals, such as authorization and authentication, may seem the best solution.

> _"Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements." —_ [_Spring Security_](https://spring.io/projects/spring-security)

However, for basic needs writing a few lines of code to implement your authentication logic might be a better approach. This way, you can keep your application simple and avoid unnecessary dependencies.

The approach I am going to present will allow you to choose to protect an API with [basic access authorization](https://en.wikipedia.org/wiki/Basic_access_authentication). This method allows [HTTP user agents](https://en.wikipedia.org/wiki/User_agent) to specify a username and password when making requests. The server will authorize the request only if the credentials received are valid. It is called _basic_ because it does not require [HTTP cookies](https://en.wikipedia.org/wiki/HTTP_cookie), session identifiers, or login pages. In fact, it is the simplest technique for enforcing [access controls](https://en.wikipedia.org/wiki/Access_controls) to web resources, since it is based only on standard fields in the [HTTP header](https://en.wikipedia.org/wiki/HTTP_header).

Let's see how to achieve basic access authentication in Spring Boot in both Java and Kotlin.

## 1\. Defining a Custom Annotation

To choose what APIs you want to protect by the HTTP basic authentication system, you need a custom-defined annotation. Such an annotation will be used to mark a parameter of type `User` to define whether an API requires authentication.

As a consequence, you are assuming that every valid authentication credential pair can be associates with a particular user. So, those credentials will be retrieved from the specific HTTP header and used by authentication logic to extract a user, whose data will be automatically passed to the protected API's function body.

Let's see how a custom `Auth` annotation can be defined:

#### Java

```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Auth {}
```

#### Kotlin

```kotlin
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Auth
```

## 2\. Defining Authentication Logic

You should place authentication logic in a specific component, such as `BasicAccessAuthenticationHandler`_._ The purpose of this class is to verify if the credentials received are valid, and extract its related user.

This is what such a class looks like:

#### Java

```java
@Component
public class BasicAccessAuthenticationHandler {
    // to retrieve users associated to valid credentials
    private UserDao userDao;

    public BasicAccessAuthenticationHandler(
            @Autowired UserDao userDao
    ) {
        this.userDao = userDao;
    }

    public User getUser(
            NativeWebRequest nativeWebRequest
    ) {
        try {
            // retrieving credentials the HTTP Authorization Header
            String authorizationCredentials = nativeWebRequest
                    .getHeader(HttpHeaders.AUTHORIZATION)
                    .substring("Basic".length())
                    .trim();

            // decoding credentials
            String[] decodedCredentials = new String(
                    Base64
                            .getDecoder()
                            .decode(authorizationCredentials)
            ).split(":");

            // verifying if the credentials received are valid
            if (
                    decodedCredentials[0] == "expectedUsername" &&
                    decodedCredentials[1] == "expectedPassword"
            ) {
                // user retrieving logic
                Optional<User> userOptional = userDao.findByUsernameAndPassword(decodedCredentials[0], decodedCredentials[1]);

                userOptional.orElseThrow(
                        () -> new AuthenticationException()
                );

                return userOptional.get();
            }

            throw new AuthenticationException();
        } catch (Exception e) {
            throw new AuthenticationException();
        }
    }
}
```

#### Kotlin

```kotlin
@Component
class BasicAccessAuthenticationHandler {
    // to retrieve users associated to valid credentials
    @Autowired
    lateinit var UserDao: userDao

    fun getUser(
        nativeWebRequest : NativeWebRequest
    ) : User {
        try {
            // retrieving credentials the HTTP Authorization Header
            val authorizationCredentials = nativeWebRequest
                .getHeader(HttpHeaders.AUTHORIZATION)!!
                .substring("Basic".length)
                .trim()

            // decoding credentials
            val decodedCredentials = String(
                Base64
                    .getDecoder()
                    .decode(authorizationCredentials)
            ).split(":")

            // verifying if the credentials received are valid
            if (
                decodedCredentials[0] == "expectedUsername" &&
                decodedCredentials[1] == "expectedPassword"
            ) {
                // user retrieving logic
                val userOptional = userDao.findByUsernameAndPassword(decodedCredentials[0], decodedCredentials[1])

                userOptional.orElseThrow {
                    AuthenticationException()
                }

                return userOptional.get()
            }
            
            throw AuthenticationException()            
        } catch (e: Exception) {
            throw AuthenticationException()
        }
    }
}
```

What this component does is extract the credentials from the headers and use them to try to retrieve a user. As defined in the [RFC](https://en.wikipedia.org/wiki/RFC_%28identifier%29) [7617](https://tools.ietf.org/html/rfc7617), when dealing with basic access authentication you should expect a header field in the form of `Authorization: Basic <credentials>`, where credentials are the [Base64](https://en.wikipedia.org/wiki/Base64) encoding of _username_ and _password_ joined by a single colon `:`. This is why you need to decode first and split by `:` then.

Plus, as you can see, when credentials are missing or not valid, a custom `AuthenticationException` is thrown. In this case, the protected API should respond with the _401 Unauthorized_ status code.

_"The HTTP 401 Unauthorized client error status response code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource." —_ [_MDN web docs_](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401#)

#### Java

```java
public class AuthenticationException extends RuntimeException {    
    public AuthenticationException(
            String message
    ) {
        super(message);
    }
}
```

#### Kotlin

```kotlin
class AuthenticationException(
    message: String = DEFAULT_MESSAGE
) : RuntimeException(message)
```

To achieve this, a class marked with [`@ControllerAdvice`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html) can be used as follows:

#### Java

```java
@ControllerAdvice
public class ControllerExceptionHandler {
    // ...
  
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<String> forbiddenException(
            Exception e
    ) {
        return new ResponseEntity(
                e.getMessage(),
                HttpStatus.UNAUTHORIZED
        );
    }
}
```

#### Kotlin

```kotlin
@ControllerAdvice
class ControllerExceptionHandler {
    // ...

    @ExceptionHandler(AuthenticationException::class)
    fun forbiddenException(
        e: Exception
    ) : ResponseEntity<String?> {
        return ResponseEntity(
            e.message, 
            HttpStatus.UNAUTHORIZED
        )
    }
}
```

## 3\. Enforcing Basic Authentication

To make Spring Boot automatically look for the basic access authentication credentials when the custom `Auth` annotation is specified, you need to provide an implementation to the[`HandlerMethodArgumentResolver`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/method/support/HandlerMethodArgumentResolver.html) interface.

You can achieve this as follows:

#### Java

```java
public class BasicAccessAuthenticationResolver implements HandlerMethodArgumentResolver {    
    private BasicAccessAuthenticationHandler basicAccessAuthenticationHandler;

    public BasicAccessAuthenticationResolver(
            @Autowired BasicAccessAuthenticationHandler basicAccessAuthenticationHandler
    ) {
        this.basicAccessAuthenticationHandler = basicAccessAuthenticationHandler;
    }
    
    // to register the Auth annotation purposely defined
    @Override
    public boolean supportsParameter(
            MethodParameter methodParameter
    ) {
        return methodParameter.getParameterAnnotation(Auth.class) != null;
    }

    @Override
    public Object  resolveArgument(
            MethodParameter methodParameter,
            ModelAndViewContainer modelAndViewContainer,
            NativeWebRequest nativeWebRequest,
            WebDataBinderFactory webDataBinderFactory
    ) {
        // only if the parameter is of User type
        if (methodParameter.getParameterType() == User.class)
        return basicAccessAuthenticationHandler.getUser(nativeWebRequest);

        // default behavior
        return UNRESOLVED;
    }
}
```

#### Kotlin

```kotlin
class BasicAccessAuthenticationResolver : HandlerMethodArgumentResolver {
    @Autowired
    lateinit var basicAccessAuthenticationHandler : BasicAccessAuthenticationHandler 

    // to register the Auth annotation purposely defined
    override fun supportsParameter(
        methodParameter: MethodParameter
    ) : Boolean {
        return methodParameter.getParameterAnnotation(Auth::class.java) != null
    }

    override fun resolveArgument(
        methodParameter : MethodParameter,
        modelAndViewContainer : ModelAndViewContainer,
        nativeWebRequest : NativeWebRequest,
        webDataBinderFactory : WebDataBinderFactory
    ) : Any? {
        // only if the parameter is of User type
        if (methodParameter.parameterType == User::class.java)
            return basicAccessAuthenticationHandler.getUser(nativeWebRequest)
        
        // default behavior
        return UNRESOLVED
    }
}
```

## 4\. Configuring Spring Boot

Now, you only need to define a custom class for the configurations. This way, Spring Boot will be able to use the custom `Auth` annotation as designed.

For everything to work, you need to add the previously defined `CustomWebResolver` class to the default argument resolvers. This can be achieved by harnessing the [`WebMvcConfigurationSupport`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.html) class.

> _"\[WebMvcConfigurationSupport\] is typically imported by adding `@EnableWebMvc` to an application `@Configuration` class. An alternative more advanced option is to extend directly from this class and override methods as necessary, remembering to add `@Configuration` to the subclass and `@Bean` to overridden `@Bean` methods."_ _—_ [_Spring's official documentation_](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.html)

So, you should define a `@Configuration` annotated class that extends `WebMvcConfigurationSupport` like this:

#### Java

```java
@Configuration
class CustomConfig extends WebMvcConfigurationSupport {
    // ...

    @Bean
    public HandlerMethodArgumentResolver authWebArgumentResolverFactory() {
        return new BasicAccessAuthenticationResolver();
    }

    @Override
    public void addArgumentResolvers(
            List<HandlerMethodArgumentResolver> argumentResolvers
    ) {
        argumentResolvers.add(authWebArgumentResolverFactory())
    }
}
```

#### Kotlin

```kotlin
@Configuration
class CustomConfig : WebMvcConfigurationSupport() {
    // ...
    
    @Bean
    fun authWebArgumentResolverFactory() : HandlerMethodArgumentResolver {
        return BasicAccessAuthenticationResolver()
    }
    
    override fun addArgumentResolvers(
        argumentResolvers: MutableList<HandlerMethodArgumentResolver>
    ) {
        argumentResolvers.add(authWebArgumentResolverFactory())
    }
}
```

Please, note that when using `WebMvcConfigurationSupport`, you might have to deal with CORS configurations. Otherwise, your APIs may not be reachable as expected.

## 5\. Putting It All Together

Now, it is time to see how `Auth` annotation can be used to make an API accessible only to authenticated users. This can be easily achieved by adding a `User` type parameter marked with `Auth` annotation to the chosen Controller API function:

#### Java

```java
@GetMapping("data/{id}")
public ResponseEntity<Void> getSecuredData(
        @Auth User user,
        @PathVariable(value = "id") Int id
) {

    // NOTE: user can be used inside the method body

    // API logic

    return new ResponseEntity(HttpStatus.OK);
}
```

#### Kotlin

```kotlin
@GetMapping("data/{id}")
fun getSecuredData(
        @Auth user : User,
        @PathVariable(value = "id") id: Int
) : ResponseEntity<Void> {

    // NOTE: user can be used inside the method body

    // API logic

    return ResponseEntity(HttpStatus.OK)
}
```

Moreover, defining an API lacking protection is possible as well:

#### Java

```java
@GetMapping("data/{id}")
public ResponseEntity<Void> getData(
        @PathVariable(value = "id") Int id
) {

    // NOTE: user can be used inside the method body

    // API logic

    return new ResponseEntity(HttpStatus.OK);
}
```

#### Kotlin

```kotlin
@GetMapping("data/{id}")
fun getData(
        @PathVariable(value = "id") id: Int
) : ResponseEntity<Void> {

    // NOTE: user can be used inside the method body

    // API logic

    return ResponseEntity(HttpStatus.OK)
}
```

## Conclusion

Neglecting security can have pernicious consequences. This is exactly why you should not let all your APIs be accessible to everyone. By using the basic access authentication method you can protect your most critical APIs. As shown, implementing it and choosing which APIs to protect is not complex, and in most cases, such a simple approach is enough to secure your application.

Thanks for reading! I hope that you found this article helpful.
  
***
_The post "[How to Implement Basic Access Authentication in Spring Boot](https://writech.run/blog/how-to-implement-basic-access-authentication-in-spring-boot-eaded2e33d19/)" appeared first on [Writech](https://writech.run)._