A reactive authentication and registration microservice built with Spring Boot (WebFlux) and R2DBC (MySQL). The service provides endpoints for user registration and login with JWT-based authentication, using non-blocking reactive programming. Now includes secure rotating refresh token sessions backed by Redis.
You can view the full OpenAPI specification here:
👉 openapi.json
- Reactive and non-blocking using Spring WebFlux
- JWT authentication for stateless access token validation
- Rotating refresh token support stored in Redis
- Secure password hashing with BCrypt
- Role-based access control
- Centralized exception handling for consistent API error responses
- MySQL via R2DBC (fully non-blocking)
- Containerized with Docker Compose
- Environment-based configuration for portability
io.baxter.authentication
├── api
│ ├── controllers # REST endpoints (login, register, refresh)
│ ├── models # Request/Response DTOs
│ └── services # AccessService interface + AccessServiceImpl
├── data
│ ├── models # UserDataModel, RoleDataModel, UserRoleDataModel
│ └── repository # Reactive R2DBC repositories
├── infrastructure
│ ├── auth # JwtTokenGenerator, PasswordEncryption, Security config
│ ├── behavior
│ │ ├── exceptions # Domain + validation exceptions
│ │ ├── handlers # Global exception handling
│ │ └── helper # Shared utilities (e.g., factory helpers)
│ ├── redis # Reactive Redis config + token serialization
│ └── config # OpenAPI config, DateTime Clock config
├── src/main/resources
│ └── application.properties
├── docker-compose.yml
└── Application.java
On login
- Generate short-lived Access Token (JWT)
- Generate long-lived Refresh Token ID (UUID)
- Store in Redis:
- key:
refresh_token:{id} - value:
{ username, roles[], issuedAt, expiresAt } - TTL: matches refresh lifetime (e.g., 30 days)
- key:
- Return
{ accessToken, refreshTokenId }to the client
On refresh
- Client sends
refreshTokenId - Load
refresh_token:{id}from Redis - If missing → 401
- If
expiresAt< now → delete Redis key → 401 - Generate new access token
- Rotate refresh token:
- Create new
refreshTokenId - Save new Redis value with TTL
- Delete old Redis key
- Create new
- Return
{ accessToken, refreshTokenId: newId }
| Layer | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.5+ (WebFlux) |
| Reactive Engine | Project Reactor (Mono / Flux) |
| Security | JWT (HS256) for access tokens, BCrypt for password hashing |
| Refresh Token Store | Redis 7 (Reactive RedisTemplate) |
| Database | MySQL 8 (R2DBC Reactive Driver) |
| Build Tool | Gradle 8.7+ (Kotlin DSL) |
| API Documentation | OpenAPI / Swagger (springdoc-openapi) |
| Configuration | Environment-based (application.properties + Docker .env) |
| Containerization | Docker & Docker Compose |
| CI/CD | GitHub Actions + Codecov + SonarCloud |
Environment variables are used for flexibility.
application.properties expects:
spring.application.name=io.baxter.authentication
spring.r2dbc.url=${SPRING_R2DBC_URL}
spring.r2dbc.username=${SPRING_R2DBC_USERNAME}
spring.r2dbc.password=${SPRING_R2DBC_PASSWORD}
spring.security.oauth2.resourceserver.jwt.secret-key=${JWT_SECRET}
jwt.expiration-ms=${JWT_EXPIRATION_MS}
logging.level.root=INFO
logging.level.io.baxter=DEBUG
spring.data.redis.host=${SPRING_REDIS_HOST}
spring.data.redis.port=${SPRING_REDIS_PORT}
- Create a
.envfile in the project root:
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=authentication_db
MYSQL_USER=authuser
MYSQL_PASSWORD=authpass
MYSQL_PORTS=3306:3306
MYSQL_URL=r2dbc:mysql://db:3306/authentication_db
JWT_SECRET=supersecretkey
JWT_EXPIRATION_MS=3600000
API_URL=http://localhost:9000
API_PORTS=9000:8080
COMPOSE_PROJECT_NAME=auth
- Build and start services:
docker compose up --build- Redis container:
authenticaiton-redis - MySQL container:
authentication-db - API container:
authentication-api - API available at:
http://localhost:8080/api/auth
If you prefer to run without Docker:
- Start a local MySQL database.
- Set environment variables or edit
application.properties - Run the application with Gradle:
./gradlew bootRunRobert Baxter
💻 GitHub