# JWT Authentication and Role-Based Authorization with Java Spring Boot

## Introduction to JSON Web Tokens (JWTs), Authentication, and Authorization

JSON Web Tokens (JWTs) are a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret or a public/private key pair using the RSA or ECDSA algorithms.

#### Role in Authentication

Authentication is the act of validating that users are who they claim to be using passwords, biometrics, one-time passwords, and more. JWTs can be used to establish the identity of users. When a user logs in, the server generates a JWT that encodes a set of claims. The server then returns this token to the client, and the client sends the token back with each request. The server verifies the token and only allows the request to proceed if the token is valid.

#### Role in Authorization

Authorization is the process by which individual users or groups of users are granted access to a specific resource or function. JWTs also play a crucial role in authorization, as they enable servers to know what resources a user can have access to. When the JWT includes claims like user roles or permissions, the server can determine whether to grant or deny access to certain operations or resources.

#### Importance in API Security and Access Control

Securing APIs and managing access control are critical in web applications. JWTs help in securing APIs by allowing the server to process requests without having to look up too much user information since the token is self-contained. The authorization process using JWTs enables role-based access control, ensuring that users only have access to the resources and operations their role permits.

Overall, JWTs are integral to the security architecture of modern web applications, enabling reliable authentication and fine-grained authorization while also being lightweight and easily transmittable over the web.

## Project Setup

### Overview of Tools and Technologies

In this project, we are going to employ several key tools and technologies:

* **Java Spring Boot:** An open-source Java-based framework used to create a microservice. It is easy to create stand-alone, production-grade Spring based applications with minimal effort.
* **H2 Database:** A lightweight in-memory database that can be embedded in Java applications or run in the client-server mode. It's perfect for development and testing since it doesn't require any setup like a regular database.
* **JSON Web Tokens (JWT):** A standard token format used in authentication and authorizations that allows you to securely transmit data between parties as a JSON object.

### Creating Your Spring Boot Project

1. Visit the [Spring Initializr website](https://start.spring.io/).
2. Select `Maven` as your Project Type and Java as your language
3. Choose your desired version of Spring Boot. This tutorial uses 3.2.2.
4. Provide the Project Metadata for your project.
5. Choose `Jar` as the packaging option.
6. Select the version of Java you are using. This tutorial uses 21.
7. Click on "Generate" to download the project template.

Once downloaded and extracted, create the following Java Packages in your \<ArtifactId> folder (`com` in this diagram)

```
<ProjectName>
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       ├── configs
|   |   |       ├── controllers
|   |   |       ├── dto
|   |   |       ├── filters
|   |   |       ├── models
|   |   |       ├── repositories
|   |   |       ├── services
│   │   └── resources
│   └── test
│       └── java
├── .gitignore
├── mvnw
├── mvnw.cmd
├── pom.xml
└── README.md
```

### Adding Necessary Dependencies

To include the necessary dependencies for Spring Security, the H2 database, and JWT, open the `pom.xml` file and add the following dependencies:

```xml
<dependencies>
	<!-- H2 Database -->
	<dependency>
		<groupId>com.h2database</groupId>
		<artifactId>h2</artifactId>
	<version>2.2.220</version>
	</dependency>
	<dependency>
		<groupId>io.github.cdimascio</groupId>
		<artifactId>dotenv-java</artifactId>
		<version>3.0.0</version>
	</dependency>
	<dependency>
		<groupId>org.hibernate.validator</groupId>
		<artifactId>hibernate-validator</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-oauth2-client</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.30</version>
	</dependency>
	<!-- Spring Security -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
		<version>3.1.2</version>
	</dependency>
	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt-jackson</artifactId>
		<version>0.11.5</version>
	</dependency>
	<!-- JWT -->
	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt-impl</artifactId>
		<version>0.11.5</version>
	</dependency>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-lang3</artifactId>
		<version>3.12.0</version>
	</dependency>
	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt-api</artifactId>
		<version>0.11.5</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
		<version>3.1.2</version>
	</dependency>
</dependencies>
```

Make sure to consult the official documentation for up-to-date version numbers.

### Set Properties in \`application.resources\`

We will need to configure a few properties related to our H2 database, logging, JWT authentication, and authorization. Here are the settings used in our project:

```
# H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.datasource.initialize=true
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# Logging
logging.level.root=WARN
logging.level.web=INFO
logging.level.com.example=DEBUG
logging.level.org.springframework.security=DEBUG

spring.jpa.generate-ddl=true

# Generate a secret token.secret.key for our application
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
token.secret.key=<JWT Secret>

# JWT expiration is 1 hour
token.expirationms=3600000
```

## Configuring Spring Security

To secure our web application, we implement Spring Security by defining a `SecurityConfig` class. This class utilizes a combination of annotations to integrate Spring Security with our project effectively.

Below is an overview of what the `SecurityConfig` class accomplishes:

* `@EnableWebSecurity`: This annotation activates Spring Security’s web security support and provides the Spring MVC integration. It also extends the Spring Security configuration using HTTPSecurity.
* `@EnableMethodSecurity`: This enables method-level security based on annotations. It allows us to secure methods in our controllers by specifying roles and conditions for access.
* `@Configuration`: The class is marked as a configuration class, and it defines Spring Beans.
* `@RequiredArgsConstructor`: This is a Lombok annotation that generates a constructor with required dependencies (in this case, JwtAuthenticationFilter, UserService, and PasswordEncoder).

The `SecurityConfig` includes the following Beans:

* `AuthenticationProvider`: This Bean sets the custom user details service and password encoder. It is necessary for authenticating users based on username and password.
* `AuthenticationManager`: This Bean gets the authentication manager from Spring Security's AuthenticationConfiguration which in turns help manage the authentication procedure.
* `SecurityFilterChain`: This crucial Bean within the Spring Security filter chain, allows us to configure the rules and conditions for security along different paths in our application. We disable CSRF protection as it is not needed for REST APIs and define stateless session management. Furthermore, we specify URL patterns and the associated security settings, permitting everyone to access the signup and signin endpoints, whereas protecting all other endpoints. Finally, we ensure that the JWT filter is applied before the UsernamePasswordAuthenticationFilter to extract and verify the token.

Here's how we set up the `SecurityConfig`:

```java
package dev.katyella.oauth.configs;

import dev.katyella.oauth.filters.JwtAuthenticationFilter;
import dev.katyella.oauth.services.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final UserService userService;
    private final PasswordEncoder passwordEncoder;

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userService.userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder);
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(HttpMethod.POST, "/api/v1/signup", "/api/v1/signin").permitAll()
                        .requestMatchers(HttpMethod.GET, "/api/v1/test/**").permitAll()
                        .anyRequest().authenticated()
                )
                .authenticationProvider(authenticationProvider()).addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}
```

This setup is pivotal for JWT-based authentication, ensuring that each request is properly authenticated and has the correct authority to access various resources.

## Implement JWT Authentication

JWT Authentication is a key aspect in securing web applications by validating the identity of users via tokens. Let's dive into how the `JwtService` class in the provided code snippet helps in implementing JWT authentication:

The `JwtService` class performs several essential actions:

* **Token Generation**: `generateToken(UserDetails userDetails)` method generates a new token for a user based on their `UserDetails`. It includes the user's username as the subject, current time as the issue time, and sets the expiration time based on the `jwtExpirationMs` value.
* **Token Validation**: `isTokenValid(String token, UserDetails userDetails)` method checks if the token is valid by comparing the username in the token with the username in the passed `UserDetails`, and also checks if the token has expired.
* **Extract Claims**: Various methods such as `extractAllClaims`, `extractExpiration`, and `extractUserName` help in retrieving specific claims from the token like subject (username) and expiration.
* **Secret Key**: It uses the `jwtSecretKey` declared in `application.properties` to decode and sign the tokens.

Here's the entire `JwtService` code for clarity:

```java
package dev.katyella.oauth.services;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

@Service
public class JwtService {

    @Value("${token.secret.key}")
    String jwtSecretKey;

    @Value("${token.expirationms}")
    Long jwtExpirationMs;

    public String extractUserName(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public String generateToken(UserDetails userDetails) {
        return generateToken(new HashMap<>(), userDetails);
    }

    public boolean isTokenValid(String token, UserDetails userDetails) {
        final String userName = extractUserName(token);
        return (userName.equals(userDetails.getUsername())) && !isTokenExpired(token);
    }

    private <T> T extractClaim(String token, Function<Claims, T> claimsResolvers) {
        final Claims claims = extractAllClaims(token);
        return claimsResolvers.apply(claims);
    }

    private String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
        return Jwts
                .builder()
                .setClaims(extraClaims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    private Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    private Claims extractAllClaims(String token) {
        return Jwts
                .parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    private Key getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(jwtSecretKey);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}
```

## Role-Based Authorization

This code snippet is a sample controller which demonstrates how role-based access-control can be implemented using Spring Security's `@PreAuthorize` annotation. It provides access to different REST endpoints based on the authenticated user's role.

* `GET /api/v1/test/users` is secured with `@PreAuthorize("hasRole('USER')")`, restricting access to this endpoint to only allow authenticated users with the `USER` role.
* `GET /api/v1/test/admins` is annotated with `@PreAuthorize("hasRole('ADMIN')")`, restricting access to this endpoint to only allow authenticated users with the `ADMIN` role.
* `GET /api/v1/test/role_dependent` programmatically checks the user's role without using `@PreAuthorize`, offering a more dynamic approach to role-based access-control.

A more robust `BookController`that interfaces with our database will be implemented in the next step. This `BookController` will use these principles of role-based authorization to manage access to book-related resources.

```java
@RestController
@RequestMapping("/api/v1/test")
public class TestController {

    @GetMapping("/anon")
    public String anonEndPoint() {
        return "everyone can see this";
    }

    @GetMapping("/users")
    @PreAuthorize("hasRole('USER')")
    public String usersEndPoint() {
        return "ONLY users can see this";
    }

    @GetMapping("/admins")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminsEndPoint() {
        return "ONLY admins can see this";
    }

    @GetMapping("/role_dependent")
    public String roleDependentEndPoint() {
        // Get authenticated user
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        // Check if admin role
        if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
            return "Data for admins";
        } else {
            String username = authentication.getName();
            return "Data for user: " + username;
        }
    }
}
```

## Build the Books API

Now that we have set up our project, implemented JWT-based authentication and role-based authorization, let's build the Books API to manage book-related resources. We'll create a `BookController` to handle CRUD (Create, Read, Update, Delete) operations on books.

```java
@RestController
@RequestMapping("/api/v1")
@RequiredArgsConstructor
public class BookController {
    @Autowired
    private BookRepository bookRepository;

    @GetMapping("/books")
    public List<Book> getAllBooks() {
        // Get the authenticated user
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = authentication.getName();

        // If user is admin, return all books
        if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
            return bookRepository.findAll();
        } else {
            // Otherwise, return books created by the user
            return bookRepository.findByCreatedBy(username);
        }
    }

    @GetMapping("/books/{id}")
    public ResponseEntity<Book> getBookById(@PathVariable(value = "id") Long bookId) throws ConfigDataResourceNotFoundException {
        // Get the authenticated user
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = authentication.getName();

        Book book = bookRepository.findById(bookId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found for this id :: " + bookId));

        // Check if the book belongs to the authenticated user or if the user is admin
        if (book.getCreatedBy().equals(username) || authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
            return ResponseEntity.ok().body(book);
        } else {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, "You are not authorized to access this resource");
        }
    }
    
    // More endpoints for handling creation, updating, deleting...
```

## Test the Application

Now we can start our application, sign up and sign with a new user or sign in with a pre-populated one, and use the JWT returned from that request to access our various endpoints according to the role assigned to our user!&#x20;

Here's how we can sign up our user:

```http
POST http://localhost:8080/api/v1/signup
Content-Type: application/json

{
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com",
    "password": "password"
}
```

```http

POST http://localhost:8080/api/v1/signin
Content-Type: application/json

{
    "email": "john.doe@example.com",
    "password": "password"
}
```

Or log in with our admin user:

```http
POST http://localhost:8080/api/v1/signin
Content-Type: application/json

{
    "email": "admin@admin.com",
    "password": "password"
}
```

Experiment with different calls with different roles to see how our application restricts what data is available to a user based on their role. Check the `/src/test` directory in the repo to view some example API calls. You will need to provide the JWT token fetched in the `/signin` step, and remember, it expires after 1 hour!

Here's an example of accessing the user-restricted endpoint as a user role:

```http
GET http://localhost:8080/api/v1/test/users
Authorization: Bearer {{USER_AUTH_TOKEN}}
```
