> For the complete documentation index, see [llms.txt](https://wisdom.gitbook.io/gyan/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wisdom.gitbook.io/gyan/security/passwordless-login-with-spring-security-6/spring-security-onetimetoken/one-time-token-with-custom-configuration.md).

# One Time Token with custom configuration

This is the custom one time token implementation with some own configurations.

1. **AppUser**-&#x20;
   1. Entity that stores the user basic information.
2. **OneTimeToken**-&#x20;
   1. Entity that stores one time token information, like name, expiration etc.
3. **CustomUserDetailsService-**&#x20;
   1. UserDetailsService implementation that fetches users from that database and returns a **UserDetails (I)** which has the implementation AppUser.
4. **SecurityConfig-**&#x20;
   1. The default submit page url and token generation urls are the same, but after token validation we are directing to /login/ott/success that returns the secure page.
5. **JDBCTokenGeneratorService-**&#x20;
   1. This is the custom token generation service, which is responsible for generating tokens and validating them.&#x20;
   2. It is inspired from original JDBCTokenGeneratorService, just the expiry time has been increased from 5 to 15 minutes.
   3. It also has the cron jobs that clears token automatically.
6. **OneTimeTokenSuccessHandler-**&#x20;
   1. In this class, after token generation token are being sent to username, for that first user is being fetched from the database using a custom user details service

### pom.xml

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte</artifactId>
    <version>3.1.12</version>
</dependency>
<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte-spring-boot-starter-3</artifactId>
    <version>3.1.12</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.5</version>
</dependency>
```

### application.properties

```properties
spring.application.name=spring-one-time-token
logging.level.root=info
logging.level.org.springframework.security=DEBUG
logging.level.gg.jte=DEBUG


spring.sendgrid.api-key=API_KEY
spring.sendgrid.from-email=EMAIL

spring.profiles.active=rest

spring.datasource.url=jdbc:postgresql://localhost:5432/DB_NAME
spring.datasource.username=USERNAME
spring.datasource.password=PASSWORD

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false

gg.jte.developmentMode=true
gg.jte.templateLocation=src/main/jte
```

### AppUser entity

```java
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "app_users",indexes = {
        @Index(name = "idx_username", columnList = "username", unique = true),
        @Index(name = "idx_email", columnList = "email", unique = true)
})
public class AppUser implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(unique = true)
    private String email;

    @Column(nullable = false)
    private String role;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("ROLE_" + this.role));
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    public String getFullName() {
        return this.firstName + " " + this.lastName;
    }
}
```

### OneTimeToken entity

```java
@Getter
@Table(name = "one_time_tokens")
public class OneTimeToken extends DefaultOneTimeToken {

    @Column(name = "username")
    private String userName;

    @Column(name = "token_value")
    private String tokenValue;

    @Column(name = "expires_at")
    private Instant expiresAt;

    public OneTimeToken(String token, String username, Instant expireAt) {
        super(token, username, expireAt);
    }
}
```

### Security Configuration

{% code fullWidth="true" %}

```java
@Profile("custom")
@Configuration
@EnableWebSecurity
@Log4j2
@RequiredArgsConstructor
public class SecurityConfig {

    private final JdbcOperations jdbcOperations;

    private final String [] permitUrl = {"/error","/exp","/public","/ott/sent","/login/ott"};

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

    @Bean
    public SecurityFilterChain configure(HttpSecurity security) throws Exception {

        return security.authorizeHttpRequests(request ->
                        request
                                .requestMatchers(permitUrl).permitAll()
                                .anyRequest().authenticated()
                )
                .formLogin(Customizer.withDefaults()) // default form login
                .oneTimeTokenLogin(ott->
                        ott
                                // default is fine, uses SimpleUrlAuthenticationFailureHandler , that checks token and prepare authentication object
                                // by default only gets username but we can configure Authentication Manger and UserDetailsService to get more details
//                                .authenticationProvider() // with different or custom authentication provider for ott verification

                                // default login page for ott, can be changed to custom one but need to handle csrf
                                .defaultSubmitPageUrl("/login/ott") // token submit page , default is "/login/ott" can be changed to custom one
//                                .showDefaultSubmitPage(true)  // sets the value to show submit page or not

                                // default is fine , no need to change but can be changed to custom one
                                .tokenGeneratingUrl("/ott/generate") // url that specifies how token will get generated with , default one time token service - [InMemory or Jdbc]

                                .loginProcessingUrl("/login/ott/success") // specifies the url that process login request

                                // taking jdbc token service instead of in memory
                                .tokenService(jbdcTokenService()) // used to specify TokenGenerationService - InMemory Or JdbcTokenService , default is {@link InMemoryOneTimeTokenService} with default expiry of 5 minutes
//                                .authenticationFailureHandler() // specifies the things when token is not valid, defaults to /login?error - SimpleUrlAuthenticationFailureHandler

                                // configures the success handler overrides from default that redirect to previous page, it always redirect to /home
                                .authenticationSuccessHandler(successHandler()) // specifies the success handler - default SavedRequestAwareAuthenticationSuccessHandler
                        )
//                .csrf(AbstractHttpConfigurer::disable) // csrf needed as spring uses form login for ott
                .build();

    }

    // authentication manager that uses DaoAuthenticationProvider to get user info from database
    @Bean
    public AuthenticationManager authenticationManager(CustomUserDetailsService customUserDetailsService) throws Exception {
        var provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder());
        provider.setUserDetailsService(customUserDetailsService);

        return new ProviderManager(provider);

    }

    // custom token generation service 
    private OneTimeTokenService jbdcTokenService() {
//        return new JdbcOneTimeTokenService(jdbcOperations); // default JdbcOneTimeTokenService with default expiry of 5 minutes
        return new JDBCTokenGeneratorService(jdbcOperations); // custom OneTimeTokenService with default expiry of 15 minutes
    }

    // authentication success handler that redirects to home page after login
    private AuthenticationSuccessHandler successHandler() {
        return new SimpleUrlAuthenticationSuccessHandler("/home");
    }

}

```

{% endcode %}

### OneTimeTokenSuccessHandler

{% code fullWidth="true" %}

```java
@Profile("custom")
@Component
@Log4j2
@RequiredArgsConstructor
public class OneTimeTokenSuccessHandler implements OneTimeTokenGenerationSuccessHandler {

    // ott success handler that uses redirect to /ott/sent page
    private final OneTimeTokenGenerationSuccessHandler redirectHandler = new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
    private final AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/exp?message=Username not found");
    private final EmailService emailService;

    private final CustomUserDetailsService userDetailsService;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) throws IOException, ServletException {

        var email = "";
        try{
            email = checkAndGetEmail(oneTimeToken.getUsername()); // validated and get email
        }catch (Exception e){
            log.error("Exception getting email for user: {}", oneTimeToken.getUsername());
            failureHandler.onAuthenticationFailure(request, response, new UsernameNotFoundException("Exception getting email for user"));
            return;
        }

        // creates a logging processing url with path /login/ott and with the token
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request))
                .replacePath(request.getContextPath())
                .replaceQuery(null)
                .fragment(null)
                .path("/login/ott")
                .queryParam("token", oneTimeToken.getTokenValue());

        String tokenLink = builder.toUriString();

        System.out.println("Token Link: " + magicLink);

        var body = """
                Hello, %s! Below you will find your secure link to login !
                %s""".formatted(oneTimeToken.getUserName(),tokenLink);

        try {
            var sendTo = oneTimeToken.getUsername();
            log.info("Sending One Time Token to username: {}", sendTo);
            
            // sending mail to user
            emailService.sendMail(email, "One Time Token Login", body,false);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Exception sending email: {}", e.getMessage());
        }
        
        // redirects to token sent page
        this.redirectHandler.handle(request, response, oneTimeToken);
    }

    // getting user by user name from user details service
    private String checkAndGetEmail(String username) {
        log.info("Retrieving email for user: {}", username);

        var user = (AppUser)userDetailsService.loadUserByUsername(username);
        return user.getEmail();
    }
}
```

{% endcode %}

### UserDetailsService

```java
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final AppUserRepository appUserRepository;

    // fetches user from database and returns spring security UserDetails
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return appUserRepository.findByUsernameIgnoreCase(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    }
}
```

rest of the details like controller and html pages are the same as previous one.
