Skip to content

feat(auth): Session-based authentication with JPA user/role model#66

Open
ebouchut wants to merge 32 commits into
devfrom
feat/jpa-entities-session-auth
Open

feat(auth): Session-based authentication with JPA user/role model#66
ebouchut wants to merge 32 commits into
devfrom
feat/jpa-entities-session-auth

Conversation

@ebouchut

@ebouchut ebouchut commented Jul 1, 2026

Copy link
Copy Markdown
Owner

This PR adds:

  • session-based authentication (registration, login, logout):
    • JPA user/role model on PostgreSQL,
    • Persistence and testing
    • ADRs (Architecture Decision Records)
  • Documentation

Note

Authentication uses server-side sessions with Spring Security form login which suits the Thymeleaf server-rendered UI.
It does not use JWT, which is planned for v2 when switching to a React frontend.
See ADR-0001.

New Features

Authentication and authorization

  • Spring Security filter chain with form login under the /auth/ URL prefix
    (/auth/login, /auth/register, /auth/logout); everything else requires
    authentication.
  • Registration flow: RegisterForm (Bean Validation) → AuthController
    RegistrationService, which enforces unique username/email, hashes the
    password with BCrypt, and assigns the default STUDENT role.
  • CustomUserDetailsService
    • maps roles to ROLE_-prefixed authorities,
    • honours the is_active (disabled) and is_locked (locked) account flags.
  • Session cookie hardening:
    • HttpOnly
    • SameSite=Lax
    • (with Secure documented for HTTPS).

Data model and migrations

  • User entity and its Spring Data repository
    • UUID primary key to mitigate IDOR attacks, see ADR-0003)
  • Role entity and its Spring Data repository
  • Liquibase schema:
    • hand-written formatted-SQL migrations),
    • One atomic changeset per file, see ADR-0005,
    • A changeset is dedicated to seeding the STUDENT, INSTRUCTOR, and ADMIN roles.

Frontend

  • Thymeleaf templates for home, login, register, and dashboard, wired to the
    /auth/ endpoints.
  • Forms post through th:action, so Spring Security's CSRF token is injected automatically.

Testing

  • Unit tests (Mockito) for the registration and user-details services.
  • Slice tests (@DataJpaTest) for the repositories.
  • End-to-end AuthFlowTest (MockMvc:
    • covers the flow: register => reject-anonymous => reject-wrong-password => login => dashboard,
    • Proves CSRF is wired.
  • Tests:
    • use a real PostgreSQL via Testcontainers (see ADR-0006),
    • test database is shared as a static singleton container (see ADR-0008,
    • run under Surefire (ADR-0009).
  • make test wires Testcontainers to Podman automatically.

Documentation and tooling

  • Add reference docs:
    • GLOSSARY.md,
    • ARCHITECTURE.md,
    • docs/tech-stacks.md.
  • Add ADRs 0005–0009 (ADR stands for Architecture Design Records)
  • Add the implementation plan for this feature.
  • make run target to start the databases and the app (container-engine
    agnostic).

Testing done

All tests passed:

  • `make test: 8 tests, 0 failures.
  • Manual browser smoke test: register => login => /dashboard => logout.

ebouchut added 30 commits June 18, 2026 19:10
Hand-write Liquibase database migrations instead of usiing the useless MCD-generated DDL
…eSQL

The smoke test now behaves the same locally and in CI .
It no longer needs the dev environment running:

- Run LearnDevApplicationTests against an ephemeral PostgreSQL started by
Testcontainers.
- Exclude the MongoDB auto-configuration.
Configure Spring Security for session-based form login with a BCrypt password encoder. Authentication endpoints are grouped under /auth, and that URL convention is documented in CONTRIBUTING.
Add AuthController serving the home, login and dashboard views and
handling the registration form (display and submission). Duplicate
username/email and validation errors are surfaced as field errors;
a successful registration redirects to /auth/login?registered.

Add the four Thymeleaf templates under the /auth/ URL prefix. Forms
post through th:action, so Spring Security's CSRF token is injected
automatically.
Set spring.thymeleaf.cache to false in the dev profile so template
edits are picked up without restarting the app.
Record the decision to name every test *Test/*Tests and run the whole
suite under Surefire in mvn test, rather than adding the Failsafe plugin
with an *IT suffix. Prompted by an AuthFlowIT test that mvn test silently
skipped because no Failsafe plugin is configured.
Drive the full auth journey through MockMvc against the shared Postgres
container: an anonymous request to /dashboard redirects to login, a new
account registers, a wrong password is rejected, and correct credentials
authenticate and land on /dashboard. The register POST uses a CSRF token,
proving CSRF protection is wired correctly.
Set the session cookie to HttpOnly so client-side JavaScript cannot read
it (mitigates XSS session theft) and SameSite=Lax so it is withheld on
cross-site requests (CSRF defense in depth). The Secure attribute is left
commented out until the app is served over HTTPS.
Add three cross-linked reference docs: GLOSSARY.md (domain and technical
term definitions), docs/tech-stacks.md (catalogue of tools and versions),
and ARCHITECTURE.md (layers, request flow, auth, data, and testing). Each
points to the others and to the ADRs, keeping what/how/why separate.
Add a run target that starts the Podman machine if the socket is
unreachable, brings up the Postgres and Mongo containers, then runs the
Spring Boot app in the foreground. Also list it in the help output.
Tick the completed steps for the JPA entities and session-auth plan.
The manual browser smoke test (Task 11, Step 3) is left unchecked as it
has not been performed yet.
ebouchut added 2 commits July 1, 2026 16:29
Rewrite the run section (sdk env, podman guard, compose, mvnw) and add a
note on why Podman needs a Linux VM on macOS and Windows. Add a
Documentation section linking ARCHITECTURE, tech-stacks, GLOSSARY, and the
ADRs. Use 'podman info' rather than 'podman machine info' for the
machine-up check, since the latter succeeds even when the machine is down.
- Drop the frontend/npm mention (the backend uses Maven only) 
  and the stale 'cd backend' step now that the app is at the project root. 
- Add a test  naming-convention note (test classes end in Test) .
- Reword the Podman Testcontainers setup.
Copilot AI review requested due to automatic review settings July 1, 2026 17:50
@ebouchut ebouchut self-assigned this Jul 1, 2026
@ebouchut ebouchut added documentation Improvements or additions to documentation feature backend auth Authentication & authorization (login, sessions, password reset, tokens) labels Jul 1, 2026
@ebouchut ebouchut moved this to In Review in learn-dev-project Jul 1, 2026
@ebouchut ebouchut added this to the v0.3 - MVP Backend milestone Jul 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces session-based authentication for the server-rendered Spring Boot app, backed by new JPA User/Role entities and repositories aligned with the existing Liquibase schema, plus a Testcontainers-based PostgreSQL test strategy to validate mappings and migrations against real Postgres.

Changes:

  • Add JPA user/role model (entities + repositories) and seed base roles via Liquibase.
  • Implement Spring Security session form-login with registration flow (DTO validation, controller, services, templates).
  • Add/expand test coverage using a shared Testcontainers PostgreSQL base (slice tests + end-to-end MockMvc flow) and document architecture/testing conventions.

Reviewed changes

Copilot reviewed 39 out of 40 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/test/java/com/ericbouchut/learndev/user/repository/UserRepositoryTest.java Adds a JPA slice test validating User persistence and repository queries against Postgres.
src/test/java/com/ericbouchut/learndev/support/AbstractPostgresIT.java Provides a shared singleton Postgres Testcontainers base for tests.
src/test/java/com/ericbouchut/learndev/role/repository/RoleRepositoryTest.java Adds a JPA slice test validating seeded roles are queryable.
src/test/java/com/ericbouchut/learndev/LearnDevApplicationTests.java Updates context smoke test to run against Testcontainers Postgres and excludes Mongo auto-config.
src/test/java/com/ericbouchut/learndev/auth/RegistrationServiceTest.java Adds unit tests for registration hashing, default role assignment, and duplicate email rejection.
src/test/java/com/ericbouchut/learndev/auth/CustomUserDetailsServiceTest.java Adds unit tests for mapping roles to Spring Security authorities and unknown-user behavior.
src/test/java/com/ericbouchut/learndev/auth/AuthFlowTest.java Adds end-to-end MockMvc test covering register → login → protected page flow.
src/main/resources/templates/register.html Adds Thymeleaf registration page with validation error rendering.
src/main/resources/templates/login.html Adds Thymeleaf login page with registered/logout/error messages.
src/main/resources/templates/home.html Adds a basic home page with auth navigation links.
src/main/resources/templates/dashboard.html Adds a protected dashboard view showing authenticated username and logout form.
src/main/resources/db/changelog/changes/V20260623105538-seed-roles.sql Seeds baseline roles (STUDENT, INSTRUCTOR, ADMIN) via Liquibase formatted SQL.
src/main/resources/application.yaml Hardens session cookie settings (HttpOnly, SameSite).
src/main/resources/application-dev.yaml Adds dev Thymeleaf caching override and clarifies Liquibase sectioning.
src/main/java/com/ericbouchut/learndev/user/repository/UserRepository.java Introduces Spring Data JPA repository for User.
src/main/java/com/ericbouchut/learndev/user/entity/User.java Introduces User JPA entity with UUID PK and role association.
src/main/java/com/ericbouchut/learndev/role/repository/RoleRepository.java Introduces Spring Data JPA repository for Role.
src/main/java/com/ericbouchut/learndev/role/entity/Role.java Introduces Role JPA entity.
src/main/java/com/ericbouchut/learndev/common/config/SecurityConfig.java Adds Spring Security filter chain configuration and BCrypt password encoder.
src/main/java/com/ericbouchut/learndev/auth/RegistrationService.java Implements registration business logic (uniqueness checks, hashing, default role).
src/main/java/com/ericbouchut/learndev/auth/exception/DuplicateUsernameException.java Adds domain exception for duplicate usernames.
src/main/java/com/ericbouchut/learndev/auth/exception/DuplicateEmailException.java Adds domain exception for duplicate emails.
src/main/java/com/ericbouchut/learndev/auth/dto/RegisterForm.java Adds validated form-backing DTO for registration.
src/main/java/com/ericbouchut/learndev/auth/CustomUserDetailsService.java Loads users/roles into Spring Security UserDetails authorities.
src/main/java/com/ericbouchut/learndev/auth/AuthController.java Adds MVC endpoints for home/login/dashboard and registration form + submission.
README.md Updates run instructions and expands documentation/contributing pointers.
pom.xml Adds validation + Testcontainers dependencies.
Makefile Adds test/run targets and Podman-aware Testcontainers wiring.
GLOSSARY.md Adds shared domain/technical terminology for the project.
docs/tech-stacks.md Documents the technology stack and versions used.
docs/plans/2026-06-16-jpa-entities-session-auth.md Adds a detailed implementation plan/record for the feature.
docs/adr/README.md Expands ADR index with newly added decisions.
docs/adr/0009-run-tests-under-surefire-not-failsafe.md Records decision to run all tests under Surefire using *Test suffix.
docs/adr/0008-share-singleton-testcontainers-postgres.md Records decision to use singleton static container vs @Container lifecycle.
docs/adr/0007-use-postgresql-over-mysql.md Records database choice ADR.
docs/adr/0006-test-against-real-postgres-testcontainers.md Records decision to use real Postgres via Testcontainers for tests.
docs/adr/0005-handwrite-liquibase-migrations-over-mcd-ddl.md Records decision to hand-write Liquibase migrations.
CONTRIBUTING.md Updates contribution guidance, routing/testing conventions, and how to run tests with Podman/Testcontainers.
ARCHITECTURE.md Adds architecture overview, request flow, auth model, and testing strategy description.
.gitignore Removes ignored generated DDL artifacts under docs/database/ddl.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Makefile
Comment on lines +28 to +34
@SOCK=$$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}' 2>/dev/null); \
if [ -n "$$SOCK" ]; then \
echo "Podman detected, socket: $$SOCK"; \
DOCKER_HOST="unix://$$SOCK" TESTCONTAINERS_RYUK_DISABLED=true ./mvnw test; \
else \
./mvnw test; \
fi
Comment on lines +40 to +58
@Transactional
public User register(RegisterForm form) {
if (users.existsByUsername(form.username())) {
throw new DuplicateUsernameException(form.username());
}
if (users.existsByEmail(form.email())) {
throw new DuplicateEmailException(form.email());
}
Role student = roles.findByRoleName("STUDENT")
.orElseThrow(() -> new IllegalStateException("STUDENT role not seeded"));

User user = new User();
user.setUsername(form.username());
user.setEmail(form.email());
user.setPassword(encoder.encode(form.password()));
user.getRoles().add(student);

return users.save(user);
}
Comment on lines +69 to +76
// Plain many-to-many: Hibernate inserts (user_id, role_id); the extra
// user_roles columns (assigned_at) are populated by their DB defaults.
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
Comment thread README.md
Comment thread README.md
Comment thread README.md
@ebouchut ebouchut linked an issue Jul 2, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth Authentication & authorization (login, sessions, password reset, tokens) backend documentation Improvements or additions to documentation feature

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

🔵 User roles and permissions

2 participants