Index: backend/src/main/java/com/trekr/backend/BackendApplication.java
===================================================================
--- backend/src/main/java/com/trekr/backend/BackendApplication.java	(revision 72053b5fc3ff291757af1cdd64e8fdfcb9e9b17d)
+++ backend/src/main/java/com/trekr/backend/BackendApplication.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -1,4 +1,5 @@
 package com.trekr.backend;
 
+import io.github.cdimascio.dotenv.Dotenv;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -8,5 +9,81 @@
 
     public static void main(String[] args) {
-        SpringApplication.run(BackendApplication.class, args);
+        // Load .env file BEFORE Spring Boot starts
+        // This ensures system properties are available for ${} placeholder resolution
+        Dotenv dotenv = loadEnvironmentVariables();
+        
+        // Create SpringApplication instance
+        SpringApplication app = new SpringApplication(BackendApplication.class);
+        
+        // Set system properties and default properties from .env file
+        if (dotenv != null) {
+            java.util.Map<String, Object> defaultProps = new java.util.HashMap<>();
+            
+            dotenv.entries().forEach(entry -> {
+                String key = entry.getKey();
+                String value = entry.getValue();
+                if (value != null && !value.trim().isEmpty()) {
+                    // Set as system property (for ${} placeholders in application.properties)
+                    System.setProperty(key, value);
+                    // Collect for default properties
+                    defaultProps.put(key, value);
+                }
+            });
+            
+            // Set all default properties at once
+            if (!defaultProps.isEmpty()) {
+                app.setDefaultProperties(defaultProps);
+            }
+        }
+        
+        app.run(args);
+    }
+    
+    private static Dotenv loadEnvironmentVariables() {
+        Dotenv dotenv = null;
+        try {
+            // Try current directory (backend/)
+            dotenv = Dotenv.configure()
+                    .directory("./")
+                    .ignoreIfMissing()
+                    .load();
+        } catch (Exception e) {
+            // Try parent directory if current doesn't work
+            try {
+                dotenv = Dotenv.configure()
+                        .directory("../")
+                        .ignoreIfMissing()
+                        .load();
+            } catch (Exception e2) {
+                System.err.println("WARNING: Could not load .env file. Make sure .env exists in backend/ directory.");
+                System.err.println("Error: " + e2.getMessage());
+                return null;
+            }
+        }
+        
+        // Verify critical properties are loaded
+        if (dotenv != null) {
+            String url = dotenv.get("SPRING_DATASOURCE_URL");
+            String username = dotenv.get("SPRING_DATASOURCE_USERNAME");
+            String password = dotenv.get("SPRING_DATASOURCE_PASSWORD");
+            
+            if (url != null && !url.trim().isEmpty()) {
+                System.out.println("✓ Database URL loaded from .env");
+            } else {
+                System.err.println("✗ ERROR: SPRING_DATASOURCE_URL not found in .env file!");
+            }
+            if (username != null && !username.trim().isEmpty()) {
+                System.out.println("✓ Database username loaded from .env");
+            } else {
+                System.err.println("✗ ERROR: SPRING_DATASOURCE_USERNAME not found in .env file!");
+            }
+            if (password != null && !password.trim().isEmpty()) {
+                System.out.println("✓ Database password loaded from .env");
+            } else {
+                System.err.println("✗ ERROR: SPRING_DATASOURCE_PASSWORD not found in .env file!");
+            }
+        }
+        
+        return dotenv;
     }
 
Index: backend/src/main/java/com/trekr/backend/config/AuthProperties.java
===================================================================
--- backend/src/main/java/com/trekr/backend/config/AuthProperties.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/config/AuthProperties.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,15 @@
+package com.trekr.backend.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Getter @Setter
+@ConfigurationProperties(prefix = "auth")
+public class AuthProperties {
+    private String secret;
+    private int accessTokenMaxAge;
+    private int refreshTokenMaxAge;
+}
Index: backend/src/main/java/com/trekr/backend/config/LegacyPasswordEncoder.java
===================================================================
--- backend/src/main/java/com/trekr/backend/config/LegacyPasswordEncoder.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/config/LegacyPasswordEncoder.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,29 @@
+package com.trekr.backend.config;
+
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * Supports both BCrypt (for new users) and plain text (for legacy seed data).
+ * Use BCrypt for encoding; for matching, tries BCrypt first, then plain.
+ */
+public class LegacyPasswordEncoder implements PasswordEncoder {
+
+    private final BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder();
+
+    @Override
+    public String encode(CharSequence rawPassword) {
+        return bcrypt.encode(rawPassword);
+    }
+
+    @Override
+    public boolean matches(CharSequence rawPassword, String encodedPassword) {
+        if (encodedPassword == null || encodedPassword.isEmpty()) {
+            return false;
+        }
+        if (encodedPassword.startsWith("$2a$") || encodedPassword.startsWith("$2b$")) {
+            return bcrypt.matches(rawPassword, encodedPassword);
+        }
+        return rawPassword.toString().equals(encodedPassword);
+    }
+}
Index: backend/src/main/java/com/trekr/backend/config/SecurityConfig.java
===================================================================
--- backend/src/main/java/com/trekr/backend/config/SecurityConfig.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/config/SecurityConfig.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,30 @@
+package com.trekr.backend.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http
+                .csrf(AbstractHttpConfigurer::disable)
+                .authorizeHttpRequests(auth -> auth
+                        .requestMatchers("/api/auth/**").permitAll()
+                        .anyRequest().authenticated()
+                );
+        return http.build();
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new LegacyPasswordEncoder();
+    }
+}
Index: backend/src/main/java/com/trekr/backend/controller/AuthController.java
===================================================================
--- backend/src/main/java/com/trekr/backend/controller/AuthController.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/controller/AuthController.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,31 @@
+package com.trekr.backend.controller;
+
+import com.trekr.backend.dto.auth.AuthResponse;
+import com.trekr.backend.dto.auth.LoginRequest;
+import com.trekr.backend.dto.auth.RegisterRequest;
+import com.trekr.backend.service.AuthService;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/auth")
+@RequiredArgsConstructor
+public class AuthController {
+    
+    private final AuthService authService;
+    
+    @PostMapping("/register")
+    public ResponseEntity<AuthResponse> register(@Valid @RequestBody RegisterRequest request) {
+        AuthResponse response = authService.register(request);
+        return ResponseEntity.status(HttpStatus.CREATED).body(response);
+    }
+    
+    @PostMapping("/login")
+    public ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
+        AuthResponse response = authService.login(request);
+        return ResponseEntity.ok(response);
+    }
+}
Index: backend/src/main/java/com/trekr/backend/dto/auth/AuthResponse.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/auth/AuthResponse.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/dto/auth/AuthResponse.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,16 @@
+package com.trekr.backend.dto.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AuthResponse {
+    private String token;
+    private String type = "Bearer";
+    private Long userId;
+    private String username;
+    private String email;
+}
Index: backend/src/main/java/com/trekr/backend/dto/auth/LoginRequest.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/auth/LoginRequest.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/dto/auth/LoginRequest.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,14 @@
+package com.trekr.backend.dto.auth;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class LoginRequest {
+    
+    @NotBlank(message = "Username or email is required")
+    private String usernameOrEmail;
+    
+    @NotBlank(message = "Password is required")
+    private String password;
+}
Index: backend/src/main/java/com/trekr/backend/dto/auth/RegisterRequest.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/auth/RegisterRequest.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/dto/auth/RegisterRequest.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,22 @@
+package com.trekr.backend.dto.auth;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Data
+public class RegisterRequest {
+    
+    @NotBlank(message = "Email is required")
+    @Email(message = "Email must be valid")
+    private String email;
+    
+    @NotBlank(message = "Username is required")
+    @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
+    private String username;
+    
+    @NotBlank(message = "Password is required")
+    @Size(min = 6, message = "Password must be at least 6 characters")
+    private String password;
+}
Index: backend/src/main/java/com/trekr/backend/entity/User.java
===================================================================
--- backend/src/main/java/com/trekr/backend/entity/User.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/entity/User.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,31 @@
+package com.trekr.backend.entity;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Entity
+@Table(name = "USERS", schema = "trekr")
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class User {
+    
+    @Id
+    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
+    @SequenceGenerator(name = "user_seq", sequenceName = "user_id_seq", allocationSize = 1)
+    @Column(name = "user_id")
+    private Long userId;
+    
+    @Column(name = "email", nullable = false, unique = true)
+    private String email;
+    
+    @Column(name = "username", nullable = false, unique = true)
+    private String username;
+    
+    @Column(name = "password", nullable = false)
+    private String password;
+}
Index: backend/src/main/java/com/trekr/backend/exception/GlobalExceptionHandler.java
===================================================================
--- backend/src/main/java/com/trekr/backend/exception/GlobalExceptionHandler.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/exception/GlobalExceptionHandler.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,56 @@
+package com.trekr.backend.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+    
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<Map<String, Object>> handleValidationExceptions(
+            MethodArgumentNotValidException ex) {
+        Map<String, Object> errors = new HashMap<>();
+        Map<String, String> fieldErrors = new HashMap<>();
+        
+        ex.getBindingResult().getAllErrors().forEach((error) -> {
+            String fieldName = ((FieldError) error).getField();
+            String errorMessage = error.getDefaultMessage();
+            fieldErrors.put(fieldName, errorMessage);
+        });
+        
+        errors.put("timestamp", LocalDateTime.now());
+        errors.put("status", HttpStatus.BAD_REQUEST.value());
+        errors.put("message", "Validation failed");
+        errors.put("errors", fieldErrors);
+        
+        return ResponseEntity.badRequest().body(errors);
+    }
+    
+    @ExceptionHandler(RuntimeException.class)
+    public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException ex) {
+        Map<String, Object> error = new HashMap<>();
+        error.put("timestamp", LocalDateTime.now());
+        error.put("status", HttpStatus.BAD_REQUEST.value());
+        error.put("message", ex.getMessage());
+        
+        return ResponseEntity.badRequest().body(error);
+    }
+    
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
+        Map<String, Object> error = new HashMap<>();
+        error.put("timestamp", LocalDateTime.now());
+        error.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
+        error.put("message", "An unexpected error occurred");
+        
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
+    }
+}
Index: backend/src/main/java/com/trekr/backend/repository/UserRepository.java
===================================================================
--- backend/src/main/java/com/trekr/backend/repository/UserRepository.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/repository/UserRepository.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,15 @@
+package com.trekr.backend.repository;
+
+import com.trekr.backend.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends JpaRepository<User, Long> {
+    Optional<User> findByEmail(String email);
+    Optional<User> findByUsername(String username);
+    boolean existsByEmail(String email);
+    boolean existsByUsername(String username);
+}
Index: backend/src/main/java/com/trekr/backend/resources/application.properties
===================================================================
--- backend/src/main/java/com/trekr/backend/resources/application.properties	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/resources/application.properties	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,66 @@
+# application.properties
+
+# General app config
+spring.application.name=trekr
+
+#spring.datasource.hikari.data-source-properties.useServerPrepStmts=false
+#spring.datasource.hikari.data-source-properties.prepareThreshold=0
+
+logging.level.org.springframework.security=DEBUG
+
+# JPA & Hibernate
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.show-sql=false
+spring.jpa.properties.hibernate.format_sql=true
+spring.jpa.properties.hibernate.default_schema=trekr
+
+#logging.level.org.hibernate.SQL=DEBUG
+#logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
+
+
+# HikariCP settings
+spring.datasource.hikari.pool-name=trekrHikariPool
+
+# Maximum connections in the pool
+# Rule of thumb: (CPU cores × 2) + effective_spindle_count
+# For most apps: 10-20 is good
+spring.datasource.hikari.maximum-pool-size=20
+
+# Minimum idle connections always ready
+# Keep at least 5 ready for quick response
+spring.datasource.hikari.minimum-idle=5
+
+# How long to wait for a connection (before blocking the request) - 30sec
+spring.datasource.hikari.connection-timeout=30000
+
+# How long a connection can sit idle (before being closed) - 10min
+spring.datasource.hikari.idle-timeout=600000
+
+# Maximum lifetime of a connection (before being replaced with a new one) - 30min
+# Prevents stale connections
+spring.datasource.hikari.max-lifetime=1800000
+
+
+# JWT Configuration (supports JWT_SECRET or JWT_CONFIG_SECRET from .env)
+jwt.secret=${JWT_SECRET:${JWT_CONFIG_SECRET:dev-fallback-secret-min-32-chars-required}}
+
+# Base API path
+api.base.path=/api
+api.admin.path=/api/admin
+
+#spring.profiles.active=dev
+
+# Max file size
+server.tomcat.max-part-count=50
+
+logging.level.org.springframework.web=DEBUG
+logging.level.org.apache.coyote.http11=DEBUG
+
+
+# PostgreSQL connection
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.datasource.url=${SPRING_DATASOURCE_URL}
+spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
+spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
+
Index: backend/src/main/java/com/trekr/backend/security/UserPrincipal.java
===================================================================
--- backend/src/main/java/com/trekr/backend/security/UserPrincipal.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/security/UserPrincipal.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,18 @@
+package com.trekr.backend.security;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * Represents the currently logged-in user after we've validated their JWT.
+ * Controllers can inject this or get it from SecurityContextHolder.
+ */
+@Getter
+@RequiredArgsConstructor
+public class UserPrincipal implements Serializable {
+    private final Long userId;
+    private final String username;
+    private final String email;
+}
Index: backend/src/main/java/com/trekr/backend/service/AuthService.java
===================================================================
--- backend/src/main/java/com/trekr/backend/service/AuthService.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/service/AuthService.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,55 @@
+package com.trekr.backend.service;
+
+import com.trekr.backend.dto.auth.AuthResponse;
+import com.trekr.backend.dto.auth.LoginRequest;
+import com.trekr.backend.dto.auth.RegisterRequest;
+import com.trekr.backend.entity.User;
+import com.trekr.backend.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class AuthService {
+
+    private final UserRepository userRepository;
+    private final PasswordEncoder passwordEncoder;
+    private final JwtService jwtService;
+
+    @Transactional
+    public AuthResponse register(RegisterRequest request) {
+        if (userRepository.existsByEmail(request.getEmail())) {
+            throw new RuntimeException("Email already exists");
+        }
+        if (userRepository.existsByUsername(request.getUsername())) {
+            throw new RuntimeException("Username already exists");
+        }
+
+        User user = new User();
+        user.setEmail(request.getEmail().trim().toLowerCase());
+        user.setUsername(request.getUsername().trim());
+        user.setPassword(passwordEncoder.encode(request.getPassword()));
+
+        user = userRepository.save(user);
+
+        String token = jwtService.generateToken(user.getUserId(), user.getUsername(), user.getEmail());
+        return new AuthResponse(token, "Bearer", user.getUserId(), user.getUsername(), user.getEmail());
+    }
+
+    public AuthResponse login(LoginRequest request) {
+        String usernameOrEmail = request.getUsernameOrEmail().trim();
+        String emailLookup = usernameOrEmail.contains("@") ? usernameOrEmail.toLowerCase() : usernameOrEmail;
+        User user = userRepository.findByUsername(usernameOrEmail)
+                .or(() -> userRepository.findByEmail(emailLookup))
+                .orElseThrow(() -> new RuntimeException("Invalid credentials"));
+
+        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
+            throw new RuntimeException("Invalid credentials");
+        }
+
+        String token = jwtService.generateToken(user.getUserId(), user.getUsername(), user.getEmail());
+        return new AuthResponse(token, "Bearer", user.getUserId(), user.getUsername(), user.getEmail());
+    }
+}
Index: backend/src/main/java/com/trekr/backend/service/JwtService.java
===================================================================
--- backend/src/main/java/com/trekr/backend/service/JwtService.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
+++ backend/src/main/java/com/trekr/backend/service/JwtService.java	(revision 140d09810bfccea4e866a65432770142f194ebbf)
@@ -0,0 +1,36 @@
+package com.trekr.backend.service;
+
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+@Service
+public class JwtService {
+
+    private final SecretKey signingKey;
+    private static final long TOKEN_VALIDITY_MS = 24 * 60 * 60 * 1000; // 24 hours
+
+    public JwtService(@Value("${jwt.secret}") String secret) {
+        String key = secret != null && !secret.isBlank() ? secret : "dev-fallback-secret-min-32-chars-required";
+        if (key.length() < 32) {
+            key = key + "0".repeat(Math.max(0, 32 - key.length()));
+        }
+        this.signingKey = Keys.hmacShaKeyFor(key.getBytes(StandardCharsets.UTF_8));
+    }
+
+    public String generateToken(Long userId, String username, String email) {
+        return Jwts.builder()
+                .subject(String.valueOf(userId))
+                .claim("username", username)
+                .claim("email", email)
+                .issuedAt(new Date())
+                .expiration(new Date(System.currentTimeMillis() + TOKEN_VALIDITY_MS))
+                .signWith(signingKey)
+                .compact();
+    }
+}
