Skip to main content
This guide covers the authentication system including user registration, login, and password validation using the Value Object pattern.

Overview

The authentication system provides:
  • User registration with password validation
  • Login functionality
  • Password requirements enforcement through ContrasenaVO
  • Domain-driven design with Value Objects

Authentication Endpoints

User Registration

curl -X POST http://localhost:8080/registro \
  -H "Content-Type: application/json" \
  -d '{
    "usuario": "john_doe",
    "contrasena": "Password123"
  }'

Endpoint Details

  • Method: POST
  • Path: /registro
  • Request Body: RegistrarUsuarioRequest
public record RegistrarUsuarioRequest(
    String usuario,
    String contrasena
) {}

Controller Implementation

@PostMapping("/registro")
public UsuarioPOJO registroUsuario(@RequestBody RegistrarUsuarioRequest request) {
    RegistrarUsuarioCommand command = new RegistrarUsuarioCommand(
        request.usuario(), 
        new ContrasenaVO(request.contrasena())
    );
    return registrarUsuarioHandler.handle(command);
}
The password is wrapped in a ContrasenaVO (Value Object) which validates the password format before creating the user.

User Login

curl -X GET http://localhost:8080/login/john_doe/Password123

Endpoint Details

  • Method: GET
  • Path: /login/{usuario}/{contrasena}
  • Path Parameters:
    • usuario: Username
    • contrasena: Password

Controller Implementation

@GetMapping("/login/{usuario}/{contrasena}")
public UsuarioPOJO loginUsuario(
    @PathVariable String usuario, 
    @PathVariable String contrasena
) {
    LoginUsuarioQuery query = new LoginUsuarioQuery(usuario, contrasena);
    return loginUsuarioHandler.handle(query);
}
Sending passwords in URL path parameters is not secure for production. Consider using POST with request body or implementing token-based authentication (JWT) instead.

Password Validation with ContrasenaVO

Value Object Pattern

The ContrasenaVO (Contraseña Value Object) encapsulates password validation logic:
public class ContrasenaVO {

    private final String value;

    public ContrasenaVO(String value) {
        if (value == null || value.isEmpty()) {
            throw new InvalidPasswordException("La contraseña no puede estar vacía");
        }
        if (value.length() < 8) {
            throw new InvalidPasswordException("La contraseña debe tener al menos 8 caracteres");
        }
        if (value.length() > 64) {
            throw new InvalidPasswordException("La contraseña no puede superar los 64 caracteres");
        }
        if (!value.matches(".*[A-Za-z].*") || !value.matches(".*\\d.*")) {
            throw new InvalidPasswordException(
                "La contraseña debe contener al menos una letra y un número"
            );
        }
        this.value = value;
    }
}

Password Requirements

Passwords must meet the following criteria:
1

Non-Empty

Password cannot be null or empty.
if (value == null || value.isEmpty()) {
    throw new InvalidPasswordException("La contraseña no puede estar vacía");
}
2

Minimum Length

Password must be at least 8 characters long.
if (value.length() < 8) {
    throw new InvalidPasswordException(
        "La contraseña debe tener al menos 8 caracteres"
    );
}
3

Maximum Length

Password cannot exceed 64 characters.
if (value.length() > 64) {
    throw new InvalidPasswordException(
        "La contraseña no puede superar los 64 caracteres"
    );
}
4

Complexity Requirements

Password must contain at least one letter and one number.
if (!value.matches(".*[A-Za-z].*") || !value.matches(".*\\d.*")) {
    throw new InvalidPasswordException(
        "La contraseña debe contener al menos una letra y un número"
    );
}

Password Security Features

Immutability

ContrasenaVO is immutable with a final value field, preventing password modification:
private final String value;

Secure toString()

The toString() method masks the password:
@Override
public String toString() {
    return "****";
}
This prevents accidental password exposure in logs.

Password Update

To change a password, create a new ContrasenaVO instance:
public ContrasenaVO actualizarContrasena(String nuevaContrasena) {
    return new ContrasenaVO(nuevaContrasena);
}

Request and Response Models

UsuarioPOJO

The domain model representing a user:
public class UsuarioPOJO {
    private Long id;
    private String usuario;
    private ContrasenaVO contrasena;
}

Response Example

{
  "id": 1,
  "usuario": "john_doe",
  "contrasena": "****"
}
The password field returns "****" in responses due to the overridden toString() method in ContrasenaVO.

Implementation Architecture

CQRS Pattern

The authentication system follows CQRS (Command Query Responsibility Segregation):
// Command
RegistrarUsuarioCommand command = new RegistrarUsuarioCommand(
    request.usuario(), 
    new ContrasenaVO(request.contrasena())
);

// Handler
registrarUsuarioHandler.handle(command);
Commands modify state (create new user).
// Query
LoginUsuarioQuery query = new LoginUsuarioQuery(usuario, contrasena);

// Handler
loginUsuarioHandler.handle(query);
Queries retrieve data (fetch user).

Validation Error Handling

When password validation fails, InvalidPasswordException is thrown:
public class InvalidPasswordException extends RuntimeException {
    public InvalidPasswordException(String message) {
        super(message);
    }
}
This exception is caught by the GlobalExceptionHandler and returned as a 400 Bad Request response. See the Error Handling guide for more details.

Testing Authentication

Valid Registration Examples

{
  "usuario": "john_doe",
  "contrasena": "SecurePass123"
}

Invalid Registration Examples

{
  "usuario": "bob",
  "contrasena": "Pass1"
}
// Error: "La contraseña debe tener al menos 8 caracteres"

Security Recommendations

Current Implementation Limitations:
  1. Passwords are not hashed - stored as plain text
  2. No session management or JWT tokens
  3. Login endpoint uses GET with password in URL
  4. No rate limiting or brute force protection

Production Recommendations

1

Password Hashing

Use BCrypt or Argon2 to hash passwords:
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String hashedPassword = encoder.encode(contrasena.getValue());
2

Token-Based Authentication

Implement JWT tokens instead of passing credentials with each request:
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>
3

HTTPS Only

Always use HTTPS in production to encrypt credentials in transit.
4

Security Headers

Add Spring Security for comprehensive protection:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Next Steps

Error Handling

Learn about exception handling for authentication errors

User Endpoints

View complete user API documentation

Build docs developers (and LLMs) love