Modern Applicant Tracking System built with Spring Boot, Supabase Auth, and PostgreSQL. Features JWT authentication, multi-tenant architecture, and a Kanban-style recruitment pipeline.
- Overview
- Features
- Architecture
- Getting Started
- Authentication
- API Documentation
- Project Structure
- Configuration
- Security
- Deployment
Mini-ATS is a modern recruitment management platform that helps organizations track job candidates through a visual Kanban pipeline. Built with Spring Boot and Supabase, it features enterprise-grade authentication, multi-tenancy, and role-based access control.
- π Supabase Authentication: JWT-based auth with ES256 validation
- π’ Multi-tenant Architecture: Complete organization-level data isolation
- π Kanban Pipeline: Visual tracking (NEW β SCREENING β INTERVIEW β OFFER / REJECTED)
- π₯ User Management: Admin users can create organizations and manage users
- π Candidate Scorecards: 5-category rating system with auto-calculated averages
- π Secure by Default: JWT validation, CORS protection, security headers
-
β Supabase Auth Integration
- JWT token validation using JWKS (ES256)
- Automatic user creation in both
auth.usersandpublic.users - Session management with secure tokens
-
β Role-Based Access Control
- ADMIN: Full system access, create users/orgs, manage all data
- USER: Organization-scoped access only
-
β Security Features
- JWT validation on all protected endpoints
- CORS configuration for allowed origins
- Spring Security integration
- Service Role Key for admin operations
- β Admin and User roles
- β Organization-based access control
- β Admin impersonation (view any org's data)
- β
User creation via Admin Panel:
- Creates user in Supabase Auth
- Creates matching record in
public.users - Links with same UUID
- Password management
- β Create and manage job postings
- β Filter by status (ACTIVE/CLOSED/DRAFT)
- β Search by title and department
- β Track applications per job
- β Department-based organization
- β Complete candidate profiles
- β LinkedIn profile integration
- β Skills management (array field)
- β Contact information storage
- β Search and filter capabilities
- β
Scorecard System:
- Technical Skills (1-5)
- Communication (1-5)
- Culture Fit (1-5)
- Experience (1-5)
- Leadership (1-5)
- Auto-calculated overall rating
- Real-time updates
- β
Activity Timeline:
- Track all candidate interactions
- Notes and comments
- Status changes
- β Visual tracking across 5 stages
- β Drag-and-drop status updates
- β Enriched data (candidate + job info)
- β Filter by job or candidate
- β Pipeline statistics and metrics
- β Stage-specific business logic
| Pattern | Usage |
|---|---|
| Builder | Immutable domain entities (Lombok) |
| Repository | Data access abstraction layer |
| DTO | Immutable records for API contracts |
| Service | Business logic separation |
| Filter | JWT authentication via Spring Security |
βββββββββββββββββββββββββββββββββββββββ
β REST Controllers (8) β
β - CORS protected β
β - JWT authenticated β
βββββββββββββββββββββββββββββββββββββββ€
β Security Layer β
β - JwtAuthenticationFilter β
β - JwtTokenValidator (ES256) β
βββββββββββββββββββββββββββββββββββββββ€
β Service Layer (7) β
β - UserService β
β - CandidateService β
β - JobService β
β - ApplicationService β
β - ScorecardService β
β - ActivityService β
β - SupabaseAuthService β (NEW) β
βββββββββββββββββββββββββββββββββββββββ€
β Repository Layer (7) β
β - Supabase implementations β
β - Direct REST API calls β
βββββββββββββββββββββββββββββββββββββββ€
β Supabase β
β - PostgreSQL Database β
β - Auth System (JWT) β
β - JWKS Endpoint β
βββββββββββββββββββββββββββββββββββββββ
Key Tables:
auth.users- Supabase Auth users (managed by Supabase)public.users- Application user profiles (linked to auth.users)public.organizations- Tenant organizationspublic.jobs- Job postingspublic.candidates- Candidate profilespublic.applications- Pipeline stage trackingpublic.scorecards- Candidate evaluationspublic.activities- Audit trail
| Technology | Version | Purpose |
|---|---|---|
| Spring Boot | 3.2.2 | Application Framework |
| Java | 17 | Programming Language |
| Maven | 3.8+ | Build Tool |
| PostgreSQL | 15+ | Database (via Supabase) |
| Library | Purpose |
|---|---|
spring-boot-starter-web |
REST API |
spring-boot-starter-security |
Security & Auth |
jjwt (0.12.5) |
JWT validation (ES256) |
postgresql |
Database driver |
lombok |
Boilerplate reduction |
jackson |
JSON serialization |
httpclient5 |
Supabase API calls |
dotenv-java |
Environment configuration |
java -version # Java 17+
mvn -version # Maven 3.8+git clone <your-repo-url>
cd mini-ATSCreate .env file in project root:
# Supabase Configuration
SUPABASE_URL=https://xlrbdnnferxnitillzmt.supabase.co
SUPABASE_ANON_KEY=your_anon_key_here
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key_here
# Database Configuration
DB_URL=jdbc:postgresql://aws-0-eu-central-2.pooler.supabase.com:6543/postgres?user=postgres.xlrbdnnferxnitillzmt&password=YOUR_PASSWORD&pgbouncer=true
DB_USERNAME=postgres
DB_PASSWORD=your_db_password
# Application Configuration
SPRING_PROFILES_ACTIVE=dev
SERVER_PORT=8080
# JWT Configuration
SUPABASE_JWT_JWKS_URL=https://xlrbdnnferxnitillzmt.supabase.co/auth/v1/.well-known/jwks.json
JWT_EXPIRATION=86400000
# CORS Configuration (Frontend URLs)
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000,https://your-frontend.vercel.app
# Admin Configuration
ADMIN_DEFAULT_EMAIL=admin@acme.com
ADMIN_DEFAULT_PASSWORD=ChangeThisPassword123!# Build
mvn clean install
# Run
mvn spring-boot:runAPI available at: http://localhost:8080/api
curl http://localhost:8080/api/healthExpected response:
{
"success": true,
"data": "OK",
"error": null,
"timestamp": "2026-02-09T14:00:00Z"
}This application uses Supabase Authentication with JWT tokens validated using ES256 (Elliptic Curve) cryptography.
ββββββββββββ ββββββββββββ ββββββββββββ
β Frontend β β Backend β β Supabase β
ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ
β β β
β 1. Login β β
ββββββββββββββββββββββββββββββββββββββββ> β
β β β
β 2. JWT Token β β
β <ββββββββββββββββββββββββββββββββββββββ β
β β β
β 3. API Request β β
β + Bearer Token β β
βββββββββββββββββββ> β β
β β β
β β 4. Fetch JWKS β
β βββββββββββββββββββ> β
β β β
β β 5. Public Keys β
β β <ββββββββββββββββββ β
β β β
β β 6. Validate Token β
β β (ES256) β
β β β
β 7. Response β β
β <βββββββββββββββββββ β
β β β
1. JwtTokenValidator.java
- Fetches JWKS (JSON Web Key Set) from Supabase
- Validates ES256 signatures using public keys
- Extracts user email and ID from claims
- Caches JWKS for performance
2. JwtAuthenticationFilter.java
- Intercepts all HTTP requests
- Extracts JWT from
Authorization: Bearer <token>header - Validates token and sets Spring Security context
- Fetches user role from database
3. SupabaseAuthService.java β NEW
- Creates users in Supabase Auth via Admin API
- Uses Service Role Key for privileged operations
- Deletes users (rollback on errors)
- Auto-confirms email
Flow:
- Admin calls
POST /api/userswith user data + password - Backend calls Supabase Admin API to create auth user
- Supabase returns auth user ID (UUID)
- Backend creates
public.usersrecord with same UUID - User can immediately log in with credentials
Code Example:
@Service
public class UserService {
@Transactional
public UserDTO createUser(UserDTO userDTO) {
// 1. Create in Supabase Auth
String authUserId = supabaseAuthService.createAuthUser(
userDTO.getEmail(),
userDTO.getPassword()
);
// 2. Create in public.users with same ID
User user = User.builder()
.id(UUID.fromString(authUserId)) // β Same ID!
.email(userDTO.getEmail())
.role(UserRole.fromString(userDTO.getRole()))
.build();
return UserDTO.fromEntity(userRepository.save(user));
}
}- β ES256 Signatures: Asymmetric cryptography (public/private keys)
- β JWKS Validation: Fetch and verify public keys from Supabase
- β Token Expiration: Tokens expire after configured time
- β Role-Based Access: Extract user role from database
- β Spring Security Integration: Automatic authentication context
- β CORS Protection: Only allowed origins can call API
http://localhost:8080/api
Success:
{
"success": true,
"data": { ... },
"error": null,
"timestamp": "2026-02-09T14:00:00Z"
}Error:
{
"success": false,
"data": null,
"error": "Error message here",
"timestamp": "2026-02-09T14:00:00Z"
}All protected endpoints require JWT token:
curl -H "Authorization: Bearer eyJhbGci..." \
http://localhost:8080/api/users| Resource | Endpoints | Description |
|---|---|---|
| Organizations | 6 | CRUD operations |
| Users | 10 | User management, authentication |
| Jobs | 14 | Job postings |
| Candidates | 9 | Candidate profiles |
| Applications | 20 | Kanban pipeline |
| Scorecards | 8 | Candidate evaluations β |
| Activities | 6 | Audit trail β |
| Health | 4 | System monitoring |
Total: ~77 REST endpoints
# Get user by email (after login)
GET /api/users/email/{email}
Authorization: Bearer <token>
# Create new user (admin only)
POST /api/users
{
"email": "newuser@acme.com",
"fullName": "New User",
"role": "USER",
"organizationId": "uuid",
"password": "SecurePass123!"
}
# Create admin user
POST /api/users/admin
{
"email": "newadmin@acme.com",
"fullName": "New Admin",
"organizationId": "uuid",
"password": "AdminPass123!"
}# Get candidates for organization
GET /api/candidates/organization/{orgId}
# Create candidate
POST /api/candidates
{
"organizationId": "uuid",
"fullName": "Anna Andersson",
"email": "anna@example.com",
"phone": "+46701234567",
"linkedinUrl": "https://linkedin.com/in/anna",
"skills": ["React", "TypeScript", "Node.js"],
"summary": "Senior developer with 5 years experience"
}# Create scorecard
POST /api/scorecards
{
"candidateId": "uuid",
"technicalSkills": 4,
"communication": 5,
"cultureFit": 4,
"experience": 3,
"leadership": 4
}
# Get scorecards for candidate
GET /api/scorecards/candidate/{candidateId}# Get all applications for organization
GET /api/applications/organization/{orgId}
# Create application (add to pipeline)
POST /api/applications
{
"candidateId": "uuid",
"jobId": "uuid",
"status": "NEW"
}
# Update application status (move in pipeline)
PATCH /api/applications/{id}/status
{
"status": "INTERVIEW",
"notes": "Scheduled for technical interview"
}mini-ATS/
βββ src/main/java/com/miniats/
β βββ MiniAtsApplication.java # Application entry point
β β
β βββ config/
β β βββ SecurityConfig.java # Spring Security + JWT
β β βββ SupabaseConfig.java # Supabase client
β β βββ WebConfig.java # CORS configuration
β β βββ DotenvConfig.java # Environment variables
β β
β βββ security/ # β Authentication Layer
β β βββ JwtAuthenticationFilter.java # Intercepts requests
β β βββ JwtTokenValidator.java # Validates ES256 tokens
β β βββ SupabaseUserPrincipal.java # User identity
β β
β βββ controller/ # REST API Controllers
β β βββ BaseController.java # Common response methods
β β βββ UserController.java # User management
β β βββ OrganizationController.java # Organizations
β β βββ JobController.java # Jobs
β β βββ CandidateController.java # Candidates
β β βββ ApplicationController.java # Pipeline
β β βββ ScorecardController.java # Scorecards β
β β βββ ActivityController.java # Activity timeline β
β β
β βββ service/ # Business Logic
β β βββ UserService.java
β β βββ OrganizationService.java
β β βββ JobService.java
β β βββ CandidateService.java
β β βββ ApplicationService.java
β β βββ ScorecardService.java # β
β β βββ ActivityService.java # β
β β βββ SupabaseAuthService.java # β NEW: User creation
β β
β βββ repository/ # Data Access Layer
β β βββ UserRepository.java
β β βββ OrganizationRepository.java
β β βββ JobRepository.java
β β βββ CandidateRepository.java
β β βββ ApplicationRepository.java
β β βββ ScorecardRepository.java # β
β β βββ ActivityRepository.java # β
β β βββ impl/ # Supabase implementations
β β βββ SupabaseUserRepository.java
β β βββ SupabaseOrganizationRepository.java
β β βββ SupabaseJobRepository.java
β β βββ SupabaseCandidateRepository.java
β β βββ SupabaseApplicationRepository.java
β β βββ SupabaseScorecardRepository.java
β β βββ SupabaseActivityRepository.java
β β
β βββ domain/
β β βββ model/ # Domain Entities (Immutable)
β β β βββ User.java
β β β βββ Organization.java
β β β βββ Job.java
β β β βββ Candidate.java
β β β βββ Application.java
β β β βββ Scorecard.java # β
β β β βββ Activity.java # β
β β βββ enums/
β β βββ UserRole.java
β β βββ JobStatus.java
β β βββ ApplicationStatus.java
β β βββ ActivityType.java # β
β β
β βββ dto/ # Data Transfer Objects (Records)
β β βββ UserDTO.java
β β βββ OrganizationDTO.java
β β βββ JobDTO.java
β β βββ CandidateDTO.java
β β βββ ApplicationDTO.java
β β βββ ScorecardDTO.java # β
β β βββ ActivityDTO.java # β
β β
β βββ exception/ # Error Handling
β βββ GlobalExceptionHandler.java
β βββ ResourceNotFoundException.java
β
βββ src/main/resources/
β βββ application.yml # Spring Boot configuration
β βββ logback-spring.xml # Logging configuration
β
βββ .env # Environment variables (gitignored)
βββ .env.example # Example environment file
βββ pom.xml # Maven dependencies
βββ README.md # This file
βββ .gitignore
Required in .env:
# Supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
# Database
DB_URL=jdbc:postgresql://db.your-project.supabase.co:6543/postgres
DB_USERNAME=postgres
DB_PASSWORD=your_password
# JWT
SUPABASE_JWT_JWKS_URL=https://your-project.supabase.co/auth/v1/.well-known/jwks.json
JWT_EXPIRATION=86400000
# CORS (comma-separated)
CORS_ALLOWED_ORIGINS=http://localhost:5173,https://your-frontend.com
# Admin
ADMIN_DEFAULT_EMAIL=admin@acme.com
ADMIN_DEFAULT_PASSWORD=ChangeThisPassword123!Key configurations:
server:
port: 8080
servlet:
context-path: /api
supabase:
url: ${SUPABASE_URL}
anon-key: ${SUPABASE_ANON_KEY}
service-role-key: ${SUPABASE_SERVICE_ROLE_KEY}
jwt:
jwks-url: ${SUPABASE_JWT_JWKS_URL}
expiration: ${JWT_EXPIRATION:86400000}
cors:
allowed-origins: ${CORS_ALLOWED_ORIGINS}
logging:
level:
com.miniats: DEBUG
org.springframework.security: DEBUG- User logs in via frontend (Supabase Auth)
- Frontend receives JWT token (ES256 signed)
- Frontend sends token in
Authorization: Bearer <token>header - JwtAuthenticationFilter intercepts request
- JwtTokenValidator validates token:
- Fetches JWKS from Supabase
- Verifies ES256 signature
- Checks expiration
- Spring Security sets authentication context
- Controller processes request with authenticated user
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.headers(headers -> headers
.contentSecurityPolicy("default-src 'self'")
.xssProtection()
.frameOptions().deny()
)
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
}
}Only allowed origins can access the API:
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList(
corsAllowedOrigins.split(",")
));
configuration.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"
));
configuration.setAllowCredentials(true);
return source;
}- Use strong JWT secrets
- Enable HTTPS only
- Set secure CORS origins
- Enable rate limiting
- Configure firewall rules
- Set up monitoring/alerts
- Enable database backups
- Implement request logging
mvn clean packageOutput: target/mini-ats-1.0.0.jar
java -jar target/mini-ats-1.0.0.jar- Push to GitHub
- Connect Railway to repo
- Set environment variables
- Deploy automatically
heroku create mini-ats-backend
git push heroku main
heroku config:set SUPABASE_URL=...- Upload JAR file
- Configure environment
- Deploy
FROM eclipse-temurin:17-jre
COPY target/mini-ats-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]docker build -t mini-ats .
docker run -p 8080:8080 --env-file .env mini-atsProblem: 401 Unauthorized errors
Solutions:
- Check
SUPABASE_JWT_JWKS_URLis correct - Verify token is not expired
- Check backend logs for specific error
- Verify user exists in both
auth.usersANDpublic.users
Problem: 400 Bad Request when creating users
Solutions:
- Verify
SUPABASE_SERVICE_ROLE_KEYis set - Check password meets requirements (min 6 chars)
- Verify email doesn't already exist
- Check Supabase dashboard for auth user creation
Problem: Frontend can't connect
Solutions:
- Add frontend URL to
CORS_ALLOWED_ORIGINS - Restart backend after changing CORS
- Verify format:
http://localhost:5173,https://other.com
Problem: Can't connect to Supabase
Solutions:
- Verify
DB_URL,DB_USERNAME,DB_PASSWORD - Check Supabase project is running
- Verify network connectivity
- Check if using pooler URL (port 6543)
- Frontend README: talentflow-pro/README.md
- Supabase Docs: supabase.com/docs
- Spring Security: spring.io/projects/spring-security
Main branches:
main- Production-ready code
Completed features:
- β
candidate-detail-view- Scorecards, notes, activity - β
integrate-jwt-auth- Supabase JWT authentication - β
admin-user-creation- User creation via Admin API
# Create feature branch
git checkout -b feature/my-feature
# Make changes and commit
git add .
git commit -m "feat: add my feature"
# Push to GitHub
git push origin feature/my-feature
# Merge when ready
git checkout main
git merge feature/my-feature
git push origin mainCode Metrics:
- REST Controllers: 8
- Service Classes: 7
- Repository Implementations: 7
- Domain Entities: 7
- DTOs: 7
- Total Endpoints: ~77
- Lines of Code: ~8,000
Test Coverage: (Add when tests are implemented)
- Unit Tests: TBD
- Integration Tests: TBD
- Coverage: TBD%
MIT License
Built with:
- Spring Boot & Spring Security
- Supabase for authentication & database
- PostgreSQL
- Maven
- Lombok
- Love β€οΈ and coffee β
Questions or issues? Open an issue on GitHub or contact the development team.
- β Initial release
- β Supabase JWT authentication (ES256)
- β Multi-tenant architecture
- β Kanban pipeline
- β Candidate scorecards
- β Activity timeline
- β User management with Admin API
- β Complete CRUD operations
- Email notifications
- File upload (resumes, cover letters)
- Calendar integration
- Advanced search
- Analytics dashboard
- Export capabilities