Skip to content

feat(backend): JWT Authentication & RBAC with Refresh Token Rotation (#23) - #33

Merged
meshackyaro merged 1 commit into
workman-labs:developmentfrom
jayteemoney:feat/jwt-auth-rbac-refresh-rotation
Jul 24, 2026
Merged

feat(backend): JWT Authentication & RBAC with Refresh Token Rotation (#23)#33
meshackyaro merged 1 commit into
workman-labs:developmentfrom
jayteemoney:feat/jwt-auth-rbac-refresh-rotation

Conversation

@jayteemoney

Copy link
Copy Markdown
Contributor

Closes #23.

What this adds

Spring Security JWT authentication with role-based authorization and secure refresh-token rotation (revocation + reuse detection) for backend-api. Full design notes: backend-api/docs/AUTHENTICATION.md.

Flow: registerloginrefresh (rotate) → logout, plus /me and an ADMIN-gated demo endpoint.

Key design decisions

  • Dedicated UserAccount principal (email + bcrypt hash + Role), separate from the Client/SkilledWorker domain entities, so the existing (messy) domain login/registration is left untouched. The legacy JwtUtils/ClientServiceImpl.login stub is intentionally not modified — migrating it onto this flow is a documented follow-up.
  • Access token = short-lived HS256 JWT (sub/email/role claims) → per-request authorization needs no DB hit. Refresh token = opaque 256-bit secret, stored only as a SHA-256 hash (a DB leak exposes no usable tokens).
  • Rotation + reuse detection: each refresh revokes the presented token and issues a successor in the same family (session lineage). Replaying an already-revoked token revokes the whole family, killing the session.
  • Reuse revoke runs in a REQUIRES_NEW transaction. Detection happens in rotate(), which then throws to signal the caller — and that throw would otherwise roll back the revocation. This was a real bug the integration test caught before it could ship.
  • Concurrency-safe: revoke-old is an atomic conditional UPDATE … WHERE revoked=false, so two simultaneous refreshes can't both mint a successor; the loser is treated as reuse.
  • No privilege escalation: self-registration can only create CLIENT/SKILLED_WORKER, never ADMIN.
  • RBAC via URL rules + @EnableMethodSecurity/@PreAuthorize (/api/v1/admin/** → hasRole('ADMIN')).
  • Consistent error contract { error, success:false } across 400/401/403/409, including filter-level 401/403 via custom entry-point / access-denied handlers.

Acceptance criteria

  • Implements the objective — JWT auth + RBAC + refresh rotation with revocation & reuse detection.
  • No failing tests / compilation errors — full suite green (see below).
  • Failure & concurrency paths covered — reuse → 401, race-loss, wrong password, expired/unknown token, RBAC 403; plus a real multi-threaded concurrent-refresh test asserting at most one winner and a fully-revoked family.
  • OpenAPI documented + consistent error contract — springdoc Swagger UI at /swagger-ui.html; uniform {error, success} responses.
  • Maven dependency caching in CI — already present via actions/setup-java cache: maven in test.yml (the CI already runs a Postgres service the integration tests use).

Verification (run locally against Postgres 16)

  • ./mvnw test43 passed, 0 failed
  • ./mvnw verifyBUILD SUCCESS (jar built + repackaged)

Tests: unit (JwtServiceTest, RefreshTokenServiceTest) run without a DB; AuthIntegrationTest (@SpringBootTest + MockMvc) runs against the CI Postgres service.

New dependencies

  • spring-boot-starter-validation@Valid on request DTOs
  • springdoc-openapi-starter-webmvc-ui:2.6.0 — OpenAPI 3 + Swagger UI

Notes for reviewers

  • New tables (user_accounts, refresh_tokens) are created by Hibernate (ddl-auto=update).
  • JWT_SECRET ships with a dev-only placeholder default; it must be overridden in real environments (documented).
  • No changes to existing endpoints — the previously public client/skilledWorker routes remain permitAll, so current behaviour and tests are unaffected.

🤖 Generated with Claude Code

…kman-labs#23)

Implements Spring Security JWT auth with role-based authorization and
secure refresh-token rotation, including revocation and reuse detection.

Auth model:
- New UserAccount principal (email + bcrypt hash + Role), separate from the
  Client/SkilledWorker domain entities so existing flows are untouched.
- Access token: short-lived HS256 JWT (sub/email/role claims) so per-request
  authorization needs no DB hit. Stateless SecurityConfig + JWT filter.
- Refresh token: opaque 256-bit secret, stored only as a SHA-256 hash.

Rotation & security properties:
- Every refresh rotates: the presented token is revoked and a successor is
  issued in the same family (login-session lineage).
- Reuse detection: replaying an already-revoked token revokes the whole
  family, killing the session. The family revoke runs in a REQUIRES_NEW
  transaction so the signalling throw can't roll it back.
- Concurrency-safe: revoke-old is an atomic conditional UPDATE, so two
  simultaneous refreshes cannot both mint a successor; the loser is reuse.
- Self-registration can never create an ADMIN (privilege-escalation guard).

Also:
- RBAC via URL rules + @EnableMethodSecurity/@PreAuthorize; /api/v1/admin/**
  requires ADMIN.
- Consistent {error, success} error contract across 400/401/403/409 and
  filter-level 401/403 handlers.
- OpenAPI/Swagger UI (springdoc) + bean validation on request DTOs.
- Tests: unit (JwtService, RefreshTokenService incl. reuse & race-loss) and
  @SpringBootTest integration (register/login/refresh/logout, RBAC 403/200,
  reuse -> 401, concurrent-refresh). Full suite: 43 passing via `./mvnw verify`.
- Docs: backend-api/docs/AUTHENTICATION.md + README API-reference section.

New deps: spring-boot-starter-validation, springdoc-openapi-starter-webmvc-ui.
Closes workman-labs#23.

@meshackyaro meshackyaro 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.

Security is an integral part of this SaaS project and I can see that it was handled properly.

Well done @jayteemoney. Thanks for your contribution

@meshackyaro
meshackyaro merged commit 327f3a6 into workman-labs:development Jul 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JWT Authentication & RBAC with Refresh Token Rotation

2 participants