Quick and Dirty way to add User authentication in Spring Boot Back-end

Yo Han Joo
3 min readJul 7, 2021

So I’ve been always fascinated with user authentication in web applications and it also seems like the bread and butter of apps. Even the use of jwt seems to be a requirement. In this tutorial, I will show you a quick and dirty way to add a jwt user authentication to spring boot.

First off, let’s list all of the required dependencies to our project’s pom.xml:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

Next, let’s add a User model:

@Document
public class User {

@Id
private String id;

private String username;

private String email;

private int role;

@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;

public User() {
}

public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.role = 0;
this.password = password;
}

... getters, setters, toString
}

The password has JsonProperty WRITE_ONLY as we don’t want to display the field to the user if they request details!

Let’s add the repository interface:

@Repository
public interface UserRepository extends MongoRepository<User, String> {
Optional<User> findUserByUsername(String username);
Optional<User> findUserByEmail(String email);
}

This is the repository we will use. There is not much explanation to be done here so let’s continue, which is the service side:

@Service
public class UserService {

@Value("${SECRET_KEY}")
private String secret;
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;

/**
* Constructor
*/
@Autowired
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}

/**
* User registration
*/
public String createUser(User user) {
if(userRepository.findUserByEmail(user.getEmail()).isPresent())
throw new IllegalStateException("email taken");
if(userRepository.findUserByUsername(user.getUsername()).isPresent())
throw new IllegalStateException("username taken");

user.setPassword(passwordEncoder.encode(user.getPassword()));

userRepository.save(user);
return "success";
}

/**
* User Login
*/
public String loginUser(User requestUser) {
User user = userRepository.findUserByUsername(requestUser.getUsername()).orElseThrow(() -> new IllegalStateException("User with that username does not exist"));

if(passwordEncoder.matches(requestUser.getPassword(), user.getPassword())) {
return Jwts.builder()
.setSubject(user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 1800000))
.signWith(SignatureAlgorithm.HS256, secret.getBytes(StandardCharsets.UTF_8))
.compact();
} else {
throw new IllegalStateException("invalid password");
}
}

/**
* Returns username if jwt is valid
*/
public String validateUser(String jwt) {
Claims claims = Jwts.parser()
.setSigningKey(secret.getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(jwt).getBody();

return claims.getSubject();
}
}

Ok, there is much to talk about here. Let’s start with the variables first:

We will need to import a secret key from our application.properties. We will also need our user repository and the password encoder (for jwt).

Next let’s go to the registerUser function:

public String createUser(User user) {
if(userRepository.findUserByEmail(user.getEmail()).isPresent())
throw new IllegalStateException("email taken");
if(userRepository.findUserByUsername(user.getUsername()).isPresent())
throw new IllegalStateException("username taken");

user.setPassword(passwordEncoder.encode(user.getPassword()));

userRepository.save(user);
return "success";
}

The registration function first checks if the user email or username is already taken, otherwise it hashes the password. The login user is pretty self explanatory.

public String loginUser(User requestUser) {
User user = userRepository.findUserByUsername(requestUser.getUsername()).orElseThrow(() -> new IllegalStateException("User with that username does not exist"));

if(passwordEncoder.matches(requestUser.getPassword(), user.getPassword())) {
return Jwts.builder()
.setSubject(user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 1800000))
.signWith(SignatureAlgorithm.HS256, secret.getBytes(StandardCharsets.UTF_8))
.compact();
} else {
throw new IllegalStateException("invalid password");
}
}

In this function, it first searches if the user exists. then the passwordEncoder compares the requested password and the password in the database. if it matches, the function will return a Jwt.

public String validateUser(String jwt) {
Claims claims = Jwts.parser()
.setSigningKey(secret.getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(jwt).getBody();

return claims.getSubject();
}

The validate user checks the jwt that is sent from the user later on.

Now the only thing that’s left is our controller.

@RestController
@RequestMapping(path = "/api/v1/users")
public class UserController {

private final UserService userService;

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

@PostMapping("/register")
public String createUser(@RequestBody User user) {
return userService.createUser(user);
}

@PostMapping("/login")
public String loginUser(@RequestBody User user) {
return userService.loginUser(user);
}

@GetMapping("/validate")
public String validateUser(@RequestHeader("Bearer") String jwt) {
return userService.validateUser(jwt);
}
}

Because userService handles everything for us, the controller is pretty lightweight.

Now we are done! Check out your application and it should be up and running :)

--

--