I want to create a functionality that allows a user to set a password during email verification. So when the administrator creates a user account, he gets an email with a link: http://localhost:8080/api/v1/students/set-password.html?token=d9bbd9b6-d94b-4aac-b4fe-756402eeb6ad
When the user enters the link, the email is verified and a set-password.html page should appear where the user can set their password.
Every time I try to enter this link, I am automatically redirected to http://localhost:8080/login.html.
I think something is wrong with my SecurityConfiguration, I tried to change a few things, but it didn't work. What is the problem, how can I redirect this easily or how should I do this better?
SetPassword method in StudentService class:
@Transactional
public boolean setPassword(String token, String password, String confirmPassword) {
if (!password.equals(confirmPassword)) {
throw new IllegalArgumentException("Passwords do not match.");
}
Student student = studentRepository.findByVerificationToken(UUID.fromString(token))
.orElseThrow(() -\> new IllegalArgumentException("Invalid or expired token."));
if (!student.isEmailVerified()) {
throw new IllegalStateException("Email is not verified.");
}
student.setPassword(passwordEncoder.encode(password));
student.setVerificationToken(null); // Invalidate the token after password is set
studentRepository.save(student);
return true;
}
StudentController:
package pl.studia.university.controller;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/students")
@CrossOrigin
public class StudentController {
private final StudentService studentService;
@GetMapping
public Page<StudentDto> search(@RequestParam(value = "search", required = false) String search, Pageable pageable) {
return studentService.search(search, pageable);
}
@PostMapping("/create")
@ResponseStatus(HttpStatus.CREATED)
public StudentDto create(@RequestBody CreateStudentCommand command) {
if (!PostalCodeValidator.validatePostalCode(command.getAddress().getPostalCode())) {
throw new InvalidPostalCodeFormatException("Invalid Postal code format");
}
if (!PeselValidator.validatePeselNumber(command.getPeselNumber())) {
throw new InvalidPeselFormatException("Invalid Pesel format");
}
return studentService.create(command);
}
// @GetMapping("/confirm")
// public ApiResponse confirmEmail(@RequestParam("token") String token) {
// boolean isVerified = studentService.confirmEmail(token);
// if (isVerified) {
// return new ApiResponse("The student's account has been successfully confirmed.");
// } else {
// return new ApiResponse("Invalid confirmation token.");
// }
// }
@GetMapping("/confirm")
public Object confirmEmail(@RequestParam("token") String token) {
boolean isVerified = studentService.confirmEmail(token);
if (isVerified) {
return new RedirectView("/set-password.html?token=" + token, true, true, false);
} else {
return new ApiResponse("Invalid confirmation token.");
}
}
@PostMapping("/set-password")
public ApiResponse setPassword(@RequestParam("token") String token,
@RequestParam("password") String password,
@RequestParam("confirmPassword") String confirmPassword) {
boolean success = studentService.setPassword(token, password, confirmPassword);
if (success) {
return new ApiResponse("Password set successfully");
} else {
return new ApiResponse("Failed to set password");
}
}
@DeleteMapping("/delete")
@ResponseBody
public void deleteById(@RequestParam int id) {
studentService.deleteById(id);
}
@DeleteMapping("deleteByIndexNumber")
@ResponseBody
public void deleteByIndexNumber(@RequestParam int indexNumber) {
studentService.deleteByIndexNumer(indexNumber);
}
}
and my security Configuration:
package pl.studia.university.configuration;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// // Allow access to the set-password page and the confirm endpoint without authentication
// .requestMatchers("/set-password.html", "/api/v1/students/confirm", "/style.css").permitAll()
// // Make sure other endpoints are authenticated properly
// .requestMatchers("/api/v1/students").hasRole("ADMIN")
// .requestMatchers("/api/v1/students/create").hasAuthority("ROLE_ADMIN")
// .requestMatchers("/api/v1/students/delete").hasRole("ADMIN")
// .requestMatchers("/api/v1/students/deleteByIndexNumber").hasRole("ADMIN")
// .anyRequest().authenticated()
.requestMatchers("/api/v1/students/set-password.html", "/set-password", "/api/v1/students/confirm", "/login.html", "/style.css", "/js/**", "/images/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -\> form
.loginPage("/login.html")
.loginProcessingUrl("/perform_login")
.defaultSuccessUrl("/", true)
.failureUrl("/login?error=true")
.permitAll()
)
.logout(logout -\> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout=true")
.permitAll()
)
.httpBasic(withDefaults());
return http.build();
}
@Bean
public UserDetailsService users(PasswordEncoder passwordEncoder) {
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder.encode("admin"))
.roles("ADMIN", "USER")
.build();
UserDetails user = User.withUsername("user")
.password(passwordEncoder.encode("user"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Changing requestMatchers from /api/v1/students/set-password.html to /set-password.html
.requestMatchers("/api/v1/students/set-password.html", "/set-password").permitAll()
It looks like you're incorrectly specifying the path to the restapi and html.Try changing it to
.requestMatchers("/api/v1/students/set-password", "/set-password.html").permitAll()
Hope this resolves your issue. Happy to provide more details if needed.