Skip to main content

Authentication & Security

Jazzy Framework provides a comprehensive authentication and security system with JWT tokens, role-based access control, and declarative security configuration. The security system is designed to be both powerful and easy to use, requiring minimal configuration while providing enterprise-grade security features.

Quick Start

The easiest way to add authentication to your Jazzy application is with the @EnableJazzyAuth annotation:

@EnableJazzyAuth(
userClass = User.class,
repositoryClass = UserRepository.class,
loginMethod = LoginMethod.EMAIL,
jwtSecret = "your-secret-key",
jwtExpirationHours = 24,
authBasePath = "/api/auth"
)
public class MyApp {
public static void main(String[] args) {
Config config = new Config();
Router router = new Router();

Server server = new Server(router, config);
server.start(8080);

// Automatically provides:
// POST /api/auth/register - User registration
// POST /api/auth/login - User login
// GET /api/auth/me - Current user info
}
}

This single annotation automatically:

  • ✅ Creates authentication endpoints (/register, /login, /me)
  • ✅ Configures JWT token generation and validation
  • ✅ Sets up security interceptors for protected routes
  • ✅ Integrates with your user entity and repository
  • ✅ Provides role-based access control

User Entity Requirements

Your user entity must have specific fields for authentication to work:

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// Required for EMAIL login
@Column(unique = true)
private String email;

// Required for USERNAME login
@Column(unique = true)
private String username;

// Required - will be hashed automatically
private String password;

// Optional - defaults to "USER"
private String role = "USER";

// Optional fields
private String firstName;
private String lastName;
private boolean active = true;

// getters and setters...
}

Field Requirements by Login Method

Login MethodRequired Fields
LoginMethod.EMAILemail, password
LoginMethod.USERNAMEusername, password

Security Configuration

Use SecurityConfig to define URL-based security rules:

@Component
public class AppSecurityConfig extends SecurityConfig {

@Override
public void configure() {
// Public endpoints (no authentication required)
publicEndpoints(
"/", // Home page
"/api/auth/**", // Authentication endpoints
"/api/public/**", // Public API
"/health" // Health check
);

// Secure endpoints (JWT authentication required)
requireAuth(
"/api/user/**", // User-specific endpoints
"/api/protected", // Protected resources
"/api/data/**" // Data endpoints
);

// Admin endpoints (JWT + ADMIN role required)
requireRole("ADMIN",
"/api/admin/**", // Admin panel
"/api/users/manage", // User management
"/api/system/**" // System administration
);
}
}

Wildcard Pattern Support

Security rules support powerful wildcard patterns:

PatternDescriptionExamples
/path/*Matches single level/api/users/123
/api/users/123/profile
/path/**Matches all levels/api/users/123
/api/users/123/profile
/exactExact match only/exact
/exact/sub

Authentication Endpoints

When you use @EnableJazzyAuth, these endpoints are automatically created:

POST /api/auth/register

Register a new user account.

Request:

{
"email": "user@example.com",
"username": "john_doe",
"password": "securePassword123",
"firstName": "John",
"lastName": "Doe"
}

Response (Success):

{
"success": true,
"message": "User registered successfully",
"data": {
"user": {
"id": 1,
"email": "user@example.com",
"username": "john_doe",
"firstName": "John",
"lastName": "Doe",
"role": "USER",
"active": true
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}

POST /api/auth/login

Authenticate and receive JWT token.

Request (Email Login):

{
"email": "user@example.com",
"password": "securePassword123"
}

Request (Username Login):

{
"username": "john_doe",
"password": "securePassword123"
}

Response (Success):

{
"success": true,
"message": "Login successful",
"data": {
"user": {
"id": 1,
"email": "user@example.com",
"username": "john_doe",
"role": "USER"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}

GET /api/auth/me

Get current user information using JWT token.

Request Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response:

{
"success": true,
"data": {
"user": {
"id": 1,
"email": "user@example.com",
"username": "john_doe",
"firstName": "John",
"lastName": "Doe",
"role": "USER",
"active": true
}
}
}

JWT Token Usage

Client-Side Usage

Include the JWT token in the Authorization header for protected requests:

// JavaScript example
const token = localStorage.getItem('authToken');

fetch('/api/protected/data', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
// Java client example
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Authorization", "Bearer " + token);
connection.setRequestProperty("Content-Type", "application/json");

Token Structure

JWT tokens contain the following claims:

{
"sub": "1", // User ID
"email": "user@example.com", // User email
"roles": ["USER"], // User roles
"iat": 1640995200, // Issued at timestamp
"exp": 1641081600 // Expiration timestamp
}

Role-Based Access Control

Built-in Roles

RoleDescriptionUsage
USERStandard userDefault role for registered users
ADMINAdministratorCan access admin endpoints

Custom Roles

You can extend the role system by using custom role values:

// In your User entity
private String role = "MODERATOR"; // Custom role

// In SecurityConfig
@Override
public void configure() {
requireRole("ADMIN", "/api/admin/**");
requireRole("MODERATOR", "/api/moderate/**");
// Note: Currently only ADMIN role is fully supported
// Custom roles are stored but not enforced by SecurityInterceptor
}

Advanced Configuration

Custom JWT Configuration

@EnableJazzyAuth(
userClass = User.class,
repositoryClass = UserRepository.class,
loginMethod = LoginMethod.EMAIL,

// JWT Configuration
jwtSecret = "your-256-bit-secret-key-here",
jwtExpirationHours = 168, // 7 days

// Endpoint Configuration
authBasePath = "/auth", // Changes to /auth/* instead of /api/auth/*
)
public class MyApp {
// ...
}

Multiple Login Methods

Currently, you can choose one login method per application:

// Email-based login
@EnableJazzyAuth(
loginMethod = LoginMethod.EMAIL,
// ... other config
)

// Username-based login
@EnableJazzyAuth(
loginMethod = LoginMethod.USERNAME,
// ... other config
)

Password Security

Passwords are automatically hashed using BCrypt with a secure salt:

  • ✅ Passwords are never stored in plain text
  • ✅ BCrypt with automatic salt generation
  • ✅ Secure password verification during login
  • ✅ Protection against rainbow table attacks

Error Handling

Authentication Errors

401 Unauthorized - Missing or invalid JWT token:

{
"error": "Authentication required"
}

403 Forbidden - Valid token but insufficient permissions:

{
"error": "Admin access required"
}

Registration Errors

400 Bad Request - Validation errors:

{
"success": false,
"message": "User with email user@example.com already exists"
}

Login Errors

401 Unauthorized - Invalid credentials:

{
"success": false,
"message": "Invalid credentials"
}

Integration Examples

With Auto-CRUD System

Combine authentication with CRUD for secure APIs:

@EnableJazzyAuth(
userClass = User.class,
repositoryClass = UserRepository.class,
loginMethod = LoginMethod.EMAIL
)
public class SecureApp {
public static void main(String[] args) {
// Authentication endpoints: /api/auth/*
// CRUD endpoints will respect SecurityConfig rules

Config config = new Config();
Router router = new Router();

Server server = new Server(router, config);
server.start(8080);
}
}

@Component
@Crud(
entity = Product.class,
endpoint = "/api/products",
enablePagination = true
)
public class ProductController {
// All endpoints automatically protected by SecurityConfig
// GET /api/products - public if configured
// POST /api/products - secure if configured
}

Custom Security Logic

Add custom security logic to your controllers:

@Component
public class UserController {

public Response getProfile(Request request) {
// Get user from JWT token (automatically validated by SecurityInterceptor)
String authHeader = request.header("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);

// Extract user info from token (optional, for additional checks)
JwtUtil jwtUtil = // get from DI container
JwtClaims claims = jwtUtil.validateToken(token);
String userId = claims.getUserId();

// Your custom logic here
return Response.json(JSON.of("userId", userId));
}

return Response.json(JSON.of("error", "No token")).status(401);
}
}

Best Practices

Security

  • ✅ Use strong JWT secrets (256+ bits)
  • ✅ Set appropriate token expiration times
  • ✅ Use HTTPS in production
  • ✅ Validate input data properly
  • ✅ Log authentication attempts
  • ❌ Don't store JWT tokens in local storage (use httpOnly cookies)
  • ❌ Don't use weak passwords
  • ❌ Don't expose JWT secrets in logs

Architecture

  • ✅ Keep SecurityConfig rules simple and clear
  • ✅ Use specific patterns over broad wildcards when possible
  • ✅ Group related endpoints under common paths
  • ✅ Document your security rules
  • ✅ Test security configurations thoroughly

Development

// Good: Clear and organized
publicEndpoints("/", "/api/auth/**", "/health");
requireAuth("/api/user/**", "/api/data/**");
requireRole("ADMIN", "/api/admin/**");

// Avoid: Overlapping or conflicting rules
publicEndpoints("/api/**"); // Too broad
requireAuth("/api/user/**"); // Conflicts with above

Migration Guide

From No Authentication (v0.4 and earlier)

  1. Add User Entity
@Entity
public class User {
@Id @GeneratedValue
private Long id;

@Column(unique = true)
private String email;

private String password;
private String role = "USER";

// getters/setters
}
  1. Add User Repository
@Component
public interface UserRepository extends BaseRepository<User, Long> {
Optional<User> findByEmail(String email);
}
  1. Add Security Config
@Component 
public class SecurityConfig extends SecurityConfig {
@Override
public void configure() {
publicEndpoints("/", "/api/auth/**");
requireAuth("/api/**");
}
}
  1. Enable Authentication
@EnableJazzyAuth(
userClass = User.class,
repositoryClass = UserRepository.class,
loginMethod = LoginMethod.EMAIL
)
public class MyApp {
// ... rest of your app unchanged
}

Your existing endpoints will now be protected according to your SecurityConfig rules!

Troubleshooting

Common Issues

Authentication endpoints not found (404)

  • ✅ Check @EnableJazzyAuth annotation is present
  • ✅ Verify authBasePath configuration
  • ✅ Ensure user repository is registered in DI container

Security rules not working

  • ✅ Verify SecurityConfig extends SecurityConfig
  • ✅ Check SecurityConfig is annotated with @Component
  • ✅ Ensure configure() method is implemented
  • ✅ Test endpoint patterns with exact URLs

JWT token validation fails

  • ✅ Check JWT secret configuration
  • ✅ Verify token format in Authorization header
  • ✅ Check token expiration time
  • ✅ Ensure token includes "Bearer " prefix

Registration/login fails silently

  • ✅ Check database configuration
  • ✅ Verify user entity field mappings
  • ✅ Check repository method signatures
  • ✅ Enable SQL logging to see database queries

Debugging Tips

Enable debug logging:

// Add to application.properties
logging.level.jazzyframework.security=DEBUG

Test authentication manually:

# Register user
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"test123"}'

# Login
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"test123"}'

# Access protected endpoint
curl http://localhost:8080/api/protected \
-H "Authorization: Bearer YOUR_JWT_TOKEN"