A runnable example demonstrating how to use the paseto4j library for secure token generation and validation in a Spring Boot application.
PASETO (Platform-Agnostic Security Tokens) is a modern token format that provides a secure alternative to JWT with several key advantages:
- Cryptographic agility: Built-in security best practices without requiring developer choices
- Simplicity: Fewer dangerous options mean fewer security pitfalls
- Version-based updates: Easy security upgrades across applications
- Authenticated encryption: All tokens are encrypted and authenticated by default
This example uses PASETO Version 4, which provides symmetric-key authenticated encryption using XChaCha20-Poly1305.
- Java 16 or higher
- Maven 3.6 or higher
Clone this repository and navigate to the project directory:
git clone https://github.com/arunbear/Paseto-Example-Java.git
cd Paseto-Example-JavaBuild the project:
./mvnw clean installThis project demonstrates a complete workflow for creating, encrypting, and decrypting PASETO tokens. Let's walk through each component.
The first step is to define what data you want to store in your token. In this example, we use the AppToken record:
@Builder
public record AppToken(String userId, String role, Instant expiresAt) {
public AppToken {
if (expiresAt.isBefore(Instant.now())) {
throw new IllegalArgumentException("Cannot create an expired token");
}
}
}Key points:
- Uses a Java
recordfor a clean, immutable data structure - Includes a compact constructor for validation (ensures tokens can't be created in an expired state)
- Contains user identification, role/permissions, and expiration time
The TokenService handles encryption and decryption:
@Service
public class TokenService {
@Value("${app.token.secret:#{null}}")
String secret = "";
@Value("${app.token.footer:#{null}}")
String footer = "";
public Try<String> encrypt(AppToken token) {
return Try.of(() -> {
String payload = mapper().writeValueAsString(token);
return Paseto.encrypt(key(), payload, footer);
});
}
public Try<AppToken> decrypt(String token) {
return Try.of(() -> {
String payload = Paseto.decrypt(key(), token, footer);
return mapper().readValue(payload, AppToken.class);
});
}
}Key features:
- Configurable secret key and optional footer from environment variables
- Uses the
Trymonad (from Vavr) for elegant error handling - Automatically converts tokens to/from JSON using Jackson
- Includes
JavaTimeModuleto handle Java 8+ time types
Create an application.properties file or set environment variables:
app.token.secret=your-secure-secret-key-here-minimum-32-characters
app.token.footer=optional-metadata-or-contextThe secret key should be at least 32 characters long for security.
The PasetoExampleApplication is a Spring Boot application that initializes everything:
@SpringBootApplication
public class PasetoExampleApplication {
static void main(String[] args) {
SpringApplication.run(PasetoExampleApplication.class, args);
}
}Here's a complete example of creating, encrypting, and decrypting tokens:
// Inject the TokenService (Spring will handle this)
@Autowired
TokenService tokenService;
// 1. Create a token
AppToken appToken = AppToken.builder()
.userId("user-12345")
.role("ADMIN")
.expiresAt(Instant.now().plus(1, ChronoUnit.HOURS))
.build();
// 2. Encrypt the token
Try<String> encrypted = tokenService.encrypt(appToken);
if (encrypted.isSuccess()) {
String pastetoToken = encrypted.get();
System.out.println("Token: " + pastetoToken);
} else {
System.out.println("Error: " + encrypted.getCause().getMessage());
}
// 3. Later, decrypt and validate the token
Try<AppToken> decrypted = tokenService.decrypt(pastetoToken);
if (decrypted.isSuccess()) {
AppToken decoded = decrypted.get();
System.out.println("User ID: " + decoded.userId());
System.out.println("Role: " + decoded.role());
} else {
System.out.println("Invalid token: " + decrypted.getCause().getMessage());
}See the TokenServiceTest file for comprehensive examples:
@Test
void a_good_token_can_be_encrypted_and_decrypted() {
// Create a valid token
AppToken appToken = AppToken.builder()
.userId("1234")
.role("USER")
.expiresAt(Instant.now().plus(5, ChronoUnit.MINUTES))
.build();
// Encrypt it
Try<String> encrypted = tokenService.encrypt(appToken);
then(encrypted.isSuccess()).isTrue();
// Decrypt and verify
Try<AppToken> decrypted = tokenService.decrypt(encrypted.get());
then(decrypted.isSuccess()).isTrue();
then(decrypted.get().userId()).isEqualTo("1234");
}Run the tests:
./mvnw testThe tests demonstrate:
- ✅ Valid tokens can be encrypted and decrypted successfully
- ✅ Invalid or tampered tokens are rejected
- ✅ Token data is preserved through encryption/decryption
| Technology | Purpose |
|---|---|
| Spring Boot | Web framework and dependency injection |
| paseto4j | PASETO implementation |
| Lombok | Reduces boilerplate with annotations |
| Jackson | JSON serialization with JSR310 support for Java Time |
| Vavr | Functional error handling with Try |
| Bouncy Castle | Cryptography provider |
| Error Prone | Static analysis to catch bugs |
| NullAway | Null pointer prevention |
# Build the project
./mvnw clean package
# Run the application
./mvnw spring-boot:run
# Run tests
./mvnw test- PASETO Official Documentation
- paseto4j GitHub Repository
- Spring Boot Documentation
- PASETO RFC
- Spring Boot Quick Guide to Replace JWT with PASETO - The article that inspired this project
This project is licensed under the GPL License. See the LICENSE file for details.