Skip to content
Paulo-UchoaPublic

About

Security-focused authentication service: HttpOnly cookie auth with refresh token rotation and reuse detection, CSRF, Argon2id (Spring Boot 3 + Angular)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

Sentinel

A security-focused authentication service that does cookie-based auth properly — built with Spring Boot 3 and Angular.

CI Java Spring Boot Angular

Most tutorials store JWTs in localStorage, where any XSS payload can read them. Sentinel keeps tokens out of JavaScript entirely: a short-lived access token and a rotating refresh token live in HttpOnly cookies, protected against CSRF with a double-submit token. It demonstrates the patterns that matter for real authentication — refresh token rotation with reuse detection, brute-force lockout, Argon2id hashing, security headers and an audit trail — not just "issue a JWT and hope".

Why it's interesting

  • 🍪 Tokens never touch JavaScript — access and refresh tokens are HttpOnly, Secure, SameSite cookies. An XSS payload cannot exfiltrate them.
  • 🔁 Refresh token rotation with reuse detection — every refresh issues a brand-new token and revokes the old one. If an already-used (stolen) token is replayed, the entire token family is revoked and the session is killed. This is the headline feature.
  • 🛡️ CSRF defense — because auth rides on cookies, every state-changing request must echo a double-submit XSRF-TOKEN.
  • 🔒 Brute-force protection — accounts lock automatically after repeated failed logins.
  • 🧂 Argon2id password hashing — modern, memory-hard hashing via a delegating encoder.
  • 🧾 Audit trail — every auth event (login, failure, lockout, refresh, reuse detected, logout) is persisted.
  • 🪖 Hardened responses — CSP, HSTS, X-Frame-Options: DENY, referrer policy, stateless sessions.

How refresh token rotation + reuse detection works

Refresh tokens are opaque random values; only their SHA-256 hash is stored. Each login starts a token family. Rotation replaces the token on every use; replaying an old one means it was stolen.

sequenceDiagram
    participant C as Client (cookies)
    participant S as Sentinel
    C->>S: POST /login
    S-->>C: access (15m) + refresh R1  [family F]
    C->>S: POST /refresh (R1)
    S->>S: R1 valid -> rotate
    S-->>C: new access + refresh R2  [family F], R1 revoked
    Note over C,S: attacker stole R1 and replays it
    C->>S: POST /refresh (R1)
    S->>S: R1 already revoked -> REUSE DETECTED
    S->>S: revoke entire family F (R2 included)
    S-->>C: 401 + cookies cleared (re-login required)
Loading

Cookie & CSRF model

Cookie Flags Scope Purpose
access_token HttpOnly, Secure*, SameSite=Strict / Short-lived (15 min) signed JWT
refresh_token HttpOnly, Secure*, SameSite=Strict /api/auth Long-lived (7 days) opaque, rotating
XSRF-TOKEN readable by JS, SameSite / Double-submit CSRF token

* Secure is enabled via configuration for production (HTTPS); it is off by default for local HTTP.

The SPA reads XSRF-TOKEN and echoes it as the X-XSRF-TOKEN header on every mutating request; the server rejects the request if they don't match. Login and registration are CSRF-exempt (there is no ambient session to abuse yet); refresh and logout are not.

Threat model

Threat Mitigation
XSS stealing tokens Tokens are HttpOnly — unreadable from JavaScript
CSRF Double-submit XSRF-TOKEN + SameSite cookies
Stolen refresh token Rotation + reuse detection revokes the whole family
Brute-force / credential stuffing Account lockout after N failed attempts
Password database leak Argon2id (memory-hard) hashing, never plaintext
User enumeration Generic "invalid email or password" + constant-work on unknown users
Clickjacking X-Frame-Options: DENY + CSP frame-ancestors 'none'
Token tampering Access token is an HMAC-signed JWT

Tech stack

Concern Technology
Backend Java 17, Spring Boot 3.3, Spring Security 6
Tokens JWT (jjwt) access token, opaque rotating refresh token
Hashing Argon2id (BouncyCastle) via DelegatingPasswordEncoder
Persistence PostgreSQL, Spring Data JPA, Flyway
Frontend Angular 21, TypeScript, standalone components, signals
Docs springdoc-openapi (Swagger UI)
Testing JUnit 5, Mockito, Spring Security Test, Testcontainers
Tooling Maven, Docker, GitHub Actions

Getting started

Backend + database (Docker)

docker compose up --build

Backend with zero setup (in-memory H2)

cd backend
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev

Frontend (Angular)

cd frontend
npm install
npm start

The app runs at http://localhost:4200 and talks to the API at http://localhost:8080.

API overview

Method Endpoint Auth Description
POST /api/auth/register — Create an account, sets auth cookies
POST /api/auth/login — Authenticate, sets auth cookies
POST /api/auth/refresh cookie + CSRF Rotate tokens (detects reuse)
POST /api/auth/logout cookie + CSRF Revoke the session, clears cookies
GET /api/auth/me access cookie Current authenticated user
GET /api/auth/csrf — Bootstrap the XSRF-TOKEN cookie

Testing

cd backend
./mvnw verify
  • Unit — RefreshTokenService rotation/reuse logic and AuthService lockout rules.
  • Web — full cookie + CSRF flow through MockMvc (register → /me, rotation → reuse → 401, lockout).
  • Integration — the same flow against a real PostgreSQL with Flyway via Testcontainers (skipped automatically without Docker, runs in CI).

Project structure

sentinel/
├── backend/                  # Spring Boot auth service
│   └── src/main/java/com/paulouchoa/sentinel
│       ├── auth/             # controller, service, DTOs, exceptions
│       ├── token/            # JWT, refresh rotation + reuse detection, cookies
│       ├── security/         # cookie auth filter, CSRF filter, entry point
│       ├── user/             # user entity & repository
│       ├── audit/            # auth event trail
│       ├── config/           # security, properties, OpenAPI
│       └── common/           # error handling
├── frontend/                 # Angular SPA (login, register, guarded dashboard)
├── docker-compose.yml
└── .github/workflows/ci.yml

Production notes

  • Set SENTINEL_COOKIE_SECURE=true (HTTPS) and a strong SENTINEL_JWT_SECRET (32+ bytes).
  • Serve the SPA and API on the same site so SameSite=Strict cookies flow; consider the __Host- cookie prefix for the access token.
  • Put the service behind TLS and a rate limiter at the edge in addition to the per-account lockout.

Roadmap

  • Email verification and password reset
  • Active session management (list / revoke devices)
  • TOTP-based two-factor authentication
  • Scheduled cleanup of expired refresh tokens

About

Security-focused authentication service: HttpOnly cookie auth with refresh token rotation and reuse detection, CSRF, Argon2id (Spring Boot 3 + Angular)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages