or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

aot-native-support.mdauthentication-core.mdauthentication-events.mdauthentication-management.mdauthentication-tokens.mdauthorities.mdauthorization.mdcompromised-password.mdconcurrent-async.mddao-authentication.mdexpression-access-control.mdindex.mdjaas-authentication.mdjackson-serialization.mdmethod-security.mdobservation-metrics.mdone-time-tokens.mdprovisioning.mdsecurity-context.mdsession-management.mduser-details.md
tile.json

one-time-tokens.mddocs/

One-Time Token Authentication

One-time token (OTT) authentication provides a secure, time-limited authentication mechanism commonly used for password reset flows, magic links, email verification, and other temporary access scenarios.

Key Information for Agents

Core Capabilities:

  • OneTimeTokenService generates and consumes one-time tokens
  • Tokens are single-use (consumed after validation)
  • Tokens are time-limited (expire after duration, default: 5 minutes)
  • Tokens are user-specific (associated with username)
  • Authentication support via OneTimeTokenAuthenticationProvider
  • Reactive support via OneTimeTokenReactiveAuthenticationManager

Key Interfaces and Classes:

  • OneTimeToken - Interface: getTokenValue(), getUsername(), getExpiresAt()
  • OneTimeTokenService - Interface: generate(), consume()
  • GenerateOneTimeTokenRequest - Request object with username and optional expiration
  • DefaultOneTimeToken - Default implementation
  • InMemoryOneTimeTokenService - In-memory implementation (single instance, not clustered)
  • JdbcOneTimeTokenService - JDBC-based implementation (persistent, clustered)
  • OneTimeTokenAuthenticationToken - Unauthenticated token with OTT value
  • OneTimeTokenAuthentication - Authenticated token after validation
  • OneTimeTokenAuthenticationProvider - Authentication provider for OTT validation

Default Behaviors:

  • Token expiration: 5 minutes by default (configurable per-request via GenerateOneTimeTokenRequest)
  • Token consumption: Token removed after successful validation (single-use)
  • consume() returns null if token invalid, expired, or already used
  • Token value stored in getCredentials() and getPrincipal() of OneTimeTokenAuthenticationToken

Threading Model:

  • Synchronous service executes in calling thread
  • Reactive manager returns Mono<Authentication> for non-blocking execution
  • Thread-safe: Implementations should be thread-safe

Lifecycle:

  • Tokens generated via generate() with username and optional expiration
  • Tokens consumed via consume() during authentication (removed after use)
  • Expired tokens cleaned up automatically (JDBC service supports cron-based cleanup)

Exceptions:

  • InvalidOneTimeTokenException - Thrown when token invalid, expired, or already used
  • consume() returns null instead of throwing (check return value)

Edge Cases:

  • Token expiration: Checked during consume() (returns null if expired)
  • Already consumed: Token removed after first use (subsequent calls return null)
  • Invalid token: Returns null (not found in storage)
  • Expiration per-request: Use two-parameter constructor of GenerateOneTimeTokenRequest
  • Clustered deployments: Use JdbcOneTimeTokenService (not InMemoryOneTimeTokenService)
  • Token cleanup: JDBC service supports cron-based cleanup of expired tokens

One-time tokens are:

  • Single-use: Can only be consumed once
  • Time-limited: Expire after a specific duration
  • User-specific: Associated with a particular username
  • Secure: Cryptographically generated with sufficient entropy

Common use cases:

  • Password reset links
  • Magic link authentication (passwordless login)
  • Email verification
  • Two-factor authentication
  • Temporary access grants

Core Interfaces

OneTimeToken

Represents a one-time use authentication token.

public interface OneTimeToken extends Serializable

{ .api }

Key Methods:

String getTokenValue()

{ .api }

Returns the token string value.

String getUsername()

{ .api }

Returns the username associated with this token.

Instant getExpiresAt()

{ .api }

Returns the expiration timestamp of the token.

Usage:

OneTimeToken token = oneTimeTokenService.generate(
    new GenerateOneTimeTokenRequest("user@example.com")
);

String tokenValue = token.getTokenValue();
String username = token.getUsername();
Instant expiry = token.getExpiresAt();

// Send token via email
emailService.sendPasswordResetLink(username, tokenValue);

OneTimeTokenService

Service interface for generating and consuming one-time tokens.

public interface OneTimeTokenService

{ .api }

Key Methods:

OneTimeToken generate(GenerateOneTimeTokenRequest request)

{ .api }

Generates a new one-time token for the specified user.

OneTimeToken consume(String tokenValue) throws InvalidOneTimeTokenException

{ .api }

Consumes (validates and removes) a one-time token. Throws InvalidOneTimeTokenException if the token is invalid, expired, or already used.

Example:

@Service
public class PasswordResetService {

    private final OneTimeTokenService tokenService;
    private final EmailService emailService;

    public void initiatePasswordReset(String email) {
        GenerateOneTimeTokenRequest request =
            new GenerateOneTimeTokenRequest(email);

        OneTimeToken token = tokenService.generate(request);

        String resetLink = "https://example.com/reset-password?token=" +
            token.getTokenValue();

        emailService.sendPasswordResetEmail(email, resetLink);
    }

    public void validateResetToken(String tokenValue) {
        try {
            OneTimeToken token = tokenService.consume(tokenValue);
            // Token is valid, proceed with password reset
            String username = token.getUsername();
            // Show password reset form for username
        } catch (InvalidOneTimeTokenException e) {
            // Token is invalid, expired, or already used
            throw new InvalidResetTokenException("Invalid or expired reset token");
        }
    }
}

ReactiveOneTimeTokenService

Reactive version of the token service for non-blocking operations.

public interface ReactiveOneTimeTokenService

{ .api }

Key Methods:

Mono<OneTimeToken> generate(GenerateOneTimeTokenRequest request)

{ .api }

Reactively generates a one-time token.

Mono<OneTimeToken> consume(String tokenValue)

{ .api }

Reactively consumes a one-time token.

Example:

@Service
public class ReactivePasswordResetService {

    private final ReactiveOneTimeTokenService tokenService;

    public Mono<Void> initiatePasswordReset(String email) {
        return tokenService.generate(new GenerateOneTimeTokenRequest(email))
            .flatMap(token ->
                emailService.sendPasswordResetEmail(
                    email,
                    buildResetLink(token.getTokenValue())
                )
            )
            .then();
    }

    public Mono<String> validateResetToken(String tokenValue) {
        return tokenService.consume(tokenValue)
            .map(OneTimeToken::getUsername)
            .onErrorMap(
                InvalidOneTimeTokenException.class,
                e -> new InvalidResetTokenException("Invalid or expired token")
            );
    }
}

Request Objects

GenerateOneTimeTokenRequest

Request object for generating one-time tokens.

public class GenerateOneTimeTokenRequest

{ .api }

Constructor:

public GenerateOneTimeTokenRequest(String username)

{ .api }

Key Methods:

String getUsername()

{ .api }

Returns the username for which the token should be generated.

Example:

GenerateOneTimeTokenRequest request =
    new GenerateOneTimeTokenRequest("user@example.com");

OneTimeToken token = tokenService.generate(request);

Token Implementations

DefaultOneTimeToken

Default implementation of the OneTimeToken interface.

public class DefaultOneTimeToken implements OneTimeToken

{ .api }

Constructor:

public DefaultOneTimeToken(String tokenValue, String username, Instant expiresAt)

{ .api }

Example:

DefaultOneTimeToken token = new DefaultOneTimeToken(
    "abc123...",
    "user@example.com",
    Instant.now().plus(15, ChronoUnit.MINUTES)
);

Service Implementations

InMemoryOneTimeTokenService

In-memory implementation suitable for single-instance applications and testing.

public class InMemoryOneTimeTokenService implements OneTimeTokenService

{ .api }

Configuration:

@Configuration
public class OneTimeTokenConfig {

    @Bean
    public OneTimeTokenService oneTimeTokenService() {
        return new InMemoryOneTimeTokenService();
    }
}

Characteristics:

  • Stores tokens in memory
  • Tokens lost on application restart
  • Not suitable for clustered deployments
  • Good for development and testing

Usage:

@Service
public class MagicLinkService {

    private final InMemoryOneTimeTokenService tokenService;

    public String generateMagicLink(String email) {
        OneTimeToken token = tokenService.generate(
            new GenerateOneTimeTokenRequest(email)
        );

        return "https://example.com/login?token=" + token.getTokenValue();
    }

    public boolean validateMagicLink(String tokenValue) {
        try {
            OneTimeToken token = tokenService.consume(tokenValue);
            return true;
        } catch (InvalidOneTimeTokenException e) {
            return false;
        }
    }
}

JdbcOneTimeTokenService

JDBC-based implementation for persistent token storage across application restarts and clusters.

public class JdbcOneTimeTokenService implements OneTimeTokenService

{ .api }

Key Methods:

void setDefaultExpiresIn(Duration defaultExpiresIn)

{ .api }

Sets the default expiration duration for generated tokens (default: 15 minutes).

Database Schema:

CREATE TABLE one_time_tokens (
    token_value VARCHAR(255) PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    expires_at TIMESTAMP NOT NULL
);

CREATE INDEX idx_ott_username ON one_time_tokens(username);
CREATE INDEX idx_ott_expires_at ON one_time_tokens(expires_at);

Configuration:

@Configuration
public class JdbcOneTimeTokenConfig {

    @Bean
    public JdbcOneTimeTokenService jdbcOneTimeTokenService(
        DataSource dataSource
    ) {
        JdbcOneTimeTokenService service = new JdbcOneTimeTokenService(dataSource);
        service.setDefaultExpiresIn(Duration.ofMinutes(30));
        return service;
    }

    @Bean
    public DataSource dataSource() {
        // Configure your DataSource
        return new HikariDataSource(config);
    }
}

Complete Example:

@Service
public class SecurePasswordResetService {

    private final JdbcOneTimeTokenService tokenService;
    private final UserDetailsManager userManager;
    private final PasswordEncoder passwordEncoder;

    public void initiatePasswordReset(String email) {
        if (!userManager.userExists(email)) {
            // Don't reveal whether user exists
            logger.warn("Password reset attempted for non-existent user: {}", email);
            return;
        }

        GenerateOneTimeTokenRequest request = new GenerateOneTimeTokenRequest(email);
        OneTimeToken token = tokenService.generate(request);

        String resetUrl = buildResetUrl(token.getTokenValue());
        emailService.sendPasswordResetEmail(email, resetUrl, token.getExpiresAt());

        logger.info("Password reset initiated for user: {}", email);
    }

    public void resetPassword(String tokenValue, String newPassword) {
        try {
            OneTimeToken token = tokenService.consume(tokenValue);
            String username = token.getUsername();

            UserDetails user = userManager.loadUserByUsername(username);
            UserDetails updated = User.builder()
                .username(username)
                .password(passwordEncoder.encode(newPassword))
                .authorities(user.getAuthorities())
                .build();

            userManager.updateUser(updated);

            logger.info("Password reset successful for user: {}", username);
        } catch (InvalidOneTimeTokenException e) {
            logger.warn("Invalid password reset token: {}", tokenValue);
            throw new InvalidResetTokenException("Token is invalid or expired");
        }
    }

    private String buildResetUrl(String token) {
        return UriComponentsBuilder
            .fromHttpUrl("https://example.com/reset-password")
            .queryParam("token", token)
            .toUriString();
    }
}

InMemoryReactiveOneTimeTokenService

Reactive in-memory implementation for WebFlux applications.

public class InMemoryReactiveOneTimeTokenService
    implements ReactiveOneTimeTokenService

{ .api }

Configuration:

@Configuration
public class ReactiveOttConfig {

    @Bean
    public ReactiveOneTimeTokenService reactiveTokenService() {
        return new InMemoryReactiveOneTimeTokenService();
    }
}

Usage:

@Service
public class ReactiveMagicLinkService {

    private final InMemoryReactiveOneTimeTokenService tokenService;

    public Mono<String> generateMagicLink(String email) {
        return tokenService.generate(new GenerateOneTimeTokenRequest(email))
            .map(token -> "https://example.com/login?token=" +
                token.getTokenValue());
    }

    public Mono<Boolean> authenticateWithMagicLink(String tokenValue) {
        return tokenService.consume(tokenValue)
            .map(token -> true)
            .onErrorReturn(InvalidOneTimeTokenException.class, false);
    }
}

Authentication Support

OneTimeTokenAuthenticationToken

Unauthenticated authentication token containing a one-time token value.

public class OneTimeTokenAuthenticationToken
    extends AbstractAuthenticationToken

{ .api }

Key Methods:

String getTokenValue()

{ .api }

Returns the token value being authenticated.

Example:

OneTimeTokenAuthenticationToken authRequest =
    new OneTimeTokenAuthenticationToken("abc123...");

OneTimeTokenAuthentication

Authenticated authentication token after successful OTT validation.

public class OneTimeTokenAuthentication extends AbstractAuthenticationToken

{ .api }

Example:

UserDetails user = userDetailsService.loadUserByUsername(token.getUsername());
OneTimeTokenAuthentication authenticated =
    new OneTimeTokenAuthentication(user, user.getAuthorities());

OneTimeTokenAuthenticationProvider

Authentication provider that validates one-time tokens.

public class OneTimeTokenAuthenticationProvider implements AuthenticationProvider

{ .api }

Configuration:

@Configuration
public class OttAuthenticationConfig {

    @Bean
    public OneTimeTokenAuthenticationProvider ottAuthenticationProvider(
        OneTimeTokenService tokenService,
        UserDetailsService userDetailsService
    ) {
        return new OneTimeTokenAuthenticationProvider(tokenService, userDetailsService);
    }

    @Bean
    public AuthenticationManager authenticationManager(
        OneTimeTokenAuthenticationProvider ottProvider
    ) {
        return new ProviderManager(ottProvider);
    }
}

Complete Authentication Flow:

@RestController
@RequestMapping("/api/auth")
public class MagicLinkAuthController {

    private final OneTimeTokenService tokenService;
    private final AuthenticationManager authenticationManager;

    @PostMapping("/magic-link/request")
    public ResponseEntity<Void> requestMagicLink(@RequestParam String email) {
        OneTimeToken token = tokenService.generate(
            new GenerateOneTimeTokenRequest(email)
        );

        String magicLink = buildMagicLink(token.getTokenValue());
        emailService.sendMagicLink(email, magicLink);

        return ResponseEntity.ok().build();
    }

    @PostMapping("/magic-link/authenticate")
    public ResponseEntity<AuthResponse> authenticateWithMagicLink(
        @RequestParam String token
    ) {
        try {
            OneTimeTokenAuthenticationToken authRequest =
                new OneTimeTokenAuthenticationToken(token);

            Authentication authentication =
                authenticationManager.authenticate(authRequest);

            SecurityContextHolder.getContext().setAuthentication(authentication);

            return ResponseEntity.ok(new AuthResponse(
                authentication.getName(),
                generateJwt(authentication)
            ));
        } catch (AuthenticationException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
    }

    private String buildMagicLink(String token) {
        return "https://example.com/auth/magic-link?token=" + token;
    }
}

OneTimeTokenReactiveAuthenticationManager

Reactive authentication manager for one-time tokens.

public class OneTimeTokenReactiveAuthenticationManager
    implements ReactiveAuthenticationManager

{ .api }

Configuration:

@Configuration
public class ReactiveOttAuthConfig {

    @Bean
    public OneTimeTokenReactiveAuthenticationManager ottReactiveAuthManager(
        ReactiveOneTimeTokenService tokenService,
        ReactiveUserDetailsService userDetailsService
    ) {
        return new OneTimeTokenReactiveAuthenticationManager(
            tokenService,
            userDetailsService
        );
    }
}

Exceptions

InvalidOneTimeTokenException

Exception thrown when a one-time token is invalid, expired, or already used.

public class InvalidOneTimeTokenException extends AuthenticationException

{ .api }

Example Handling:

@Service
public class TokenValidationService {

    private final OneTimeTokenService tokenService;

    public ValidationResult validateToken(String tokenValue) {
        try {
            OneTimeToken token = tokenService.consume(tokenValue);
            return ValidationResult.success(token.getUsername());
        } catch (InvalidOneTimeTokenException e) {
            if (e.getMessage().contains("expired")) {
                return ValidationResult.expired();
            } else if (e.getMessage().contains("not found")) {
                return ValidationResult.notFound();
            } else {
                return ValidationResult.alreadyUsed();
            }
        }
    }
}

Complete Application Example

@Configuration
@EnableWebSecurity
public class MagicLinkSecurityConfig {

    @Bean
    public JdbcOneTimeTokenService oneTimeTokenService(DataSource dataSource) {
        JdbcOneTimeTokenService service = new JdbcOneTimeTokenService(dataSource);
        service.setDefaultExpiresIn(Duration.ofMinutes(15));
        return service;
    }

    @Bean
    public OneTimeTokenAuthenticationProvider ottProvider(
        OneTimeTokenService tokenService,
        UserDetailsService userDetailsService
    ) {
        return new OneTimeTokenAuthenticationProvider(tokenService, userDetailsService);
    }

    @Bean
    public AuthenticationManager authenticationManager(
        OneTimeTokenAuthenticationProvider ottProvider,
        DaoAuthenticationProvider daoProvider
    ) {
        return new ProviderManager(ottProvider, daoProvider);
    }
}

@Service
public class MagicLinkAuthenticationService {

    private final OneTimeTokenService tokenService;
    private final AuthenticationManager authenticationManager;
    private final EmailService emailService;

    public void sendMagicLink(String email) {
        GenerateOneTimeTokenRequest request = new GenerateOneTimeTokenRequest(email);
        OneTimeToken token = tokenService.generate(request);

        String magicLink = buildMagicLink(token.getTokenValue());
        Duration expiresIn = Duration.between(Instant.now(), token.getExpiresAt());

        emailService.sendMagicLinkEmail(email, magicLink, expiresIn);
    }

    public Authentication authenticateWithToken(String tokenValue) {
        OneTimeTokenAuthenticationToken authRequest =
            new OneTimeTokenAuthenticationToken(tokenValue);

        return authenticationManager.authenticate(authRequest);
    }

    private String buildMagicLink(String token) {
        return "https://example.com/login/magic?token=" + token;
    }
}

@RestController
@RequestMapping("/auth/magic-link")
public class MagicLinkController {

    private final MagicLinkAuthenticationService authService;

    @PostMapping("/send")
    public ResponseEntity<MessageResponse> sendMagicLink(
        @RequestBody @Valid MagicLinkRequest request
    ) {
        authService.sendMagicLink(request.getEmail());
        return ResponseEntity.ok(
            new MessageResponse("Magic link sent to " + request.getEmail())
        );
    }

    @PostMapping("/verify")
    public ResponseEntity<AuthResponse> verifyMagicLink(
        @RequestParam String token,
        HttpServletRequest request,
        HttpServletResponse response
    ) {
        try {
            Authentication auth = authService.authenticateWithToken(token);
            SecurityContextHolder.getContext().setAuthentication(auth);

            // Create session
            HttpSession session = request.getSession(true);
            session.setAttribute(
                HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
                SecurityContextHolder.getContext()
            );

            return ResponseEntity.ok(new AuthResponse(
                auth.getName(),
                session.getId()
            ));
        } catch (InvalidOneTimeTokenException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new AuthResponse("Invalid or expired magic link"));
        }
    }
}

Package

  • org.springframework.security.authentication.ott
  • org.springframework.security.authentication.ott.reactive