A comprehensive, production-ready authentication and user management service built with Spring Boot. This service provides robust security features including JWT-based authentication, refresh token rotation with HttpOnly cookies, OAuth2 social login (Google), email verification with OTP, password reset workflows, rate limiting, and database migrations. Designed for modern web applications requiring secure, scalable user authentication and authorization.
- Multi-Authentication Support: JWT-based authentication with OAuth2 social login (Google)
- Multi-Factor Authentication: TOTP-based MFA (RFC 6238) compatible with Google Authenticator, Authy, and any standard authenticator app — with QR code enrollment, manual secret entry, and 10 single-use recovery codes
- Secure Token Management: Refresh token rotation with one-time use and server-side revocation
- Stateless APIs: Short-lived JWT access tokens (15 min) with HttpOnly, Secure cookies for refresh tokens (XSS-safe)
- Hashed Token Storage: SHA-256 hashed refresh tokens stored securely in database
- Rate Limiting: Bucket4j-based rate limiting on authentication endpoints (10 requests/min per IP)
- Email Workflows: Event-driven email OTP registration and password reset with secure links
- Role-Based Access Control: RBAC with method-level security using @PreAuthorize annotations
- Database Migrations: Versioned schema management with Flyway
- API Documentation: OpenAPI/Swagger UI integration out of the box
- Social Authentication: Seamless OAuth2 integration allowing users to authenticate via Google accounts
- Containerized by Default: Multi-stage Docker build and a ready-to-run Docker Compose stack — no local Java or PostgreSQL installation required to run the service
- Language: Java 21
- Framework: Spring Boot 3.5.x, Spring Web, Spring Security, Spring OAuth2 Client
- Database: PostgreSQL 16
- Migrations: Flyway 10.x
- JWT: JJWT 0.12.x
- Rate Limiting: Bucket4j
- MFA / TOTP: dev.samstevens.totp 1.7.1 (RFC 6238)
- Mapping/Boilerplate: MapStruct, Lombok
- API Docs: springdoc-openapi
- OAuth2: Spring Security OAuth2 Client for social authentication
- Containerization: Docker (multi-stage build) + Docker Compose
Public Endpoints
POST /api/v1/auth/send-otp— Send registration OTP to emailPOST /api/v1/auth/verify-otp— Verify OTP; returns a temporary preAuth tokenPOST /api/v1/auth/register— Complete registration; requiresAuthorization: Bearer <preAuth-token>POST /api/v1/auth/login— Traditional login; returns access token (JSON) and sets refresh token cookiePOST /api/v1/auth/mfa/verify— Submit a TOTP code or recovery code to complete an MFA-gated loginPOST /api/v1/auth/refresh— Rotate refresh token and return new access tokenPOST /api/v1/auth/logout— Revoke refresh token and clear cookiePOST /api/v1/auth/forgot-password— Request password reset linkPOST /api/v1/auth/reset-password— Reset password using token
OAuth2 Endpoints
GET /oauth2/authorization/google— Initiate Google OAuth2 login flowGET /login/oauth2/code/google— OAuth2 callback endpoint (handled internally)
MFA Endpoints (require a valid access token)
POST /api/v1/mfa/setup— Generate a TOTP secret and return it as a raw key + QR code data URIPOST /api/v1/mfa/enable— Confirm enrollment with a live code from the authenticator app; returns 10 single-use recovery codesPOST /api/v1/mfa/disable— Disable MFA; requires the account password as confirmation
Protected Endpoints
GET /api/v1/users/me— Get current user profileGET /api/v1/users— List all users (ADMIN role required)
- Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI JSON: http://localhost:8080/v3/api-docs
Traditional Registration Flow
- Send OTP → Verify OTP → Receive preAuth token → Register with preAuth token → Receive access token + refresh cookie
OAuth2 Login Flow
- Redirect to
/oauth2/authorization/google→ User authenticates with Google → Redirect back with authorization code → Service processes user info → Set refresh token cookie → Redirect to frontend dashboard
MFA Login Flow
POST /auth/loginwith email + password → receive{ mfaRequired: true, mfaToken }instead of tokens- Open authenticator app → get current 6-digit code (or use a recovery code if the phone is unavailable)
POST /auth/mfa/verifywith{ mfaToken, code }→ receive access token + refresh cookie as normal
Token Management
- Access tokens are JWTs valid for 15 minutes
- Refresh tokens are HttpOnly cookies valid for 7 days
- Refresh endpoint rotates tokens for enhanced security
MFA adds a mandatory second step to login for accounts that have it enabled. It is fully opt-in — existing users without MFA enabled are unaffected.
How it works behind the scenes
When a user enables MFA, the server generates a secret key (stored in the database) and returns it as both a scannable QR code and a plain text string for manual entry. The user adds it to any standard TOTP app. From that point, every login requires the user to provide a 6-digit code that both the server and the app compute independently from the same secret and the current time — no network call to the app, no email, no SMS.
On login, after the password is verified, the server issues a short-lived MFA challenge token (5 minutes) instead of real session tokens. This challenge token can only be used at /auth/mfa/verify — it cannot authenticate any other endpoint. Real tokens are only issued once a valid TOTP code (or recovery code) is submitted against it.
Recovery codes
10 single-use backup codes are generated at enrollment. Each is SHA-256 hashed before storage and burned immediately on use. They are shown once and never again — the user is responsible for saving them securely. Any recovery code is accepted in place of a TOTP code at /auth/mfa/verify.
Enrollment flow
POST /api/v1/mfa/setup → { secret, qrCodeImageDataUri }
↓ scan QR in authenticator app (or enter secret manually)
POST /api/v1/mfa/enable { code } → { recoveryCodes: [ ... ] } ← save these
The application is fully containerized. You do not need Java, Maven, or a local PostgreSQL installation to run it — Docker handles the entire build and runtime, including the database.
- Docker and Docker Compose — both are bundled with Docker Desktop on Windows and macOS. On Linux, install the Docker Compose plugin alongside the Docker Engine.
That's the only requirement. Everything else — the JDK, the build, and the database — runs inside containers.
git clone https://github.com/noel-mugisha/Spring-Auth-Service.git
cd Spring-Auth-ServiceCopy the example environment file and fill in your own values:
cp .env.example .env# Database configuration
DB_URL=your_database_url_here
DB_USERNAME=your_database_username_here
DB_PASSWORD=your_database_password_here
# Mail configuration
MAIL_USERNAME=your_mail_username_here
MAIL_PASSWORD=your_mail_password_here
# JWT configuration (use a strong, base64-encoded 256-bit key)
JWT_SECRET_KEY=your_very_long_random_secret_key_base64_encoded
# Frontend Configuration
FRONTEND_URL=http://localhost:3000
# OAuth2 Configuration (Google)
GOOGLE_CLIENT_ID=your_google_client_id_here
GOOGLE_CLIENT_SECRET=your_google_client_secret_here
# MFA Configuration (optional — defaults to "SpringSecurityApp")
MFA_ISSUER_NAME=YourAppNameNote: When running via Docker Compose,
DB_USERNAMEandDB_PASSWORDalso provision the bundled PostgreSQL container, and the app connects to it automatically over the internal Docker network — you don't need to pointDB_URLatlocalhostor run Postgres yourself.
docker compose up --buildThis single command will:
- Build the application image (multi-stage Docker build — no local Java or Maven involved)
- Start a PostgreSQL 16 container with a persistent named volume
- Wait for the database to report healthy before starting the app
- Run Flyway migrations automatically on startup
- Expose the API on
http://localhost:8080
To run it normally after building the image (this is after the first time):
docker compose up -d- API base URL: http://localhost:8080
- Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI JSON: http://localhost:8080/v3/api-docs
To enable Google OAuth2 authentication:
- Go to the Google Cloud Console
- Create a new project or select an existing one
- Enable the Google+ API
- Create OAuth 2.0 credentials (Client ID and Client Secret)
- Add your frontend URL to authorized redirect URIs
- Set the redirect URI to:
{your-frontend-url}/login/oauth2/code/google - Add the Client ID and Client Secret to your
.envfile
- JWT Authentication: Traditional email/password with OTP verification
- OAuth2 Social Login: Google OAuth2 integration for seamless authentication
- Hybrid Support: Users can link OAuth2 accounts to existing profiles or create new accounts via social login
- Access Tokens: JWT tokens in
Authorization: Bearer <token>header, expires in 15 minutes - Refresh Tokens: HttpOnly, Secure cookies containing opaque UUIDs, stored as SHA-256 hashes in database
- Token Rotation: Each
/refreshcall invalidates the previous refresh token and issues a new one - Cookie Security: Refresh tokens use
HttpOnly,Secure, and appropriateSameSiteattributes
- Rate Limiting: 10 requests per minute per IP on authentication endpoints using Bucket4j
- CORS Configuration: Configurable allowed origins via
FRONTEND_URLenvironment variable - Pre-registration Security: Short-lived preAuth tokens (10 minutes) for secure registration completion
- Open Redirect Protection: OAuth2 redirects validated against configured frontend URL
- State Parameter: Prevents CSRF attacks during OAuth2 flow
- PKCE Support: Proof Key for Code Exchange for enhanced security
- Secure Redirects: Only allows redirects to pre-configured frontend URLs
- User Info Validation: Email verification required from OAuth2 providers
An optional admin seeder can bootstrap an admin account on startup (controlled via application.yaml):
- Email: admin@company.com
- Password: ChangeMe123! (change immediately or disable seeding)
The instructions below are for contributors actively working on the codebase and require a local Java 21 + Maven 3.9+ toolchain — they are not needed to simply run the application (see Setup above for the Docker-based quick start).
- Build:
mvnw.cmd -q -DskipTests package(Windows) or./mvnw -q -DskipTests package(Linux/Mac) - Tests:
mvnw.cmd testor./mvnw test— Docker is required, since the integration test suite uses Testcontainers to run against a real PostgreSQL instance
We welcome contributions that improve stability, performance, security, or developer experience.
-
Getting started
- Fork the repository and create a feature branch from main.
- Use Java 21 and Maven 3.9+.
- Run ./mvnw test to ensure the test suite passes before committing.
-
Development standards
- Follow idiomatic Spring Boot patterns and keep services/interface contracts clean.
- Keep security-sensitive changes small and well-documented in PR descriptions.
- Prefer MapStruct for mappings and avoid manual boilerplate when possible.
-
Commits & PRs
- Use conventional commit messages (e.g., feat:, fix:, docs:, refactor:, test:).
- Open focused PRs with a clear description, screenshots/logs when relevant, and test coverage.
- Link related issues and update README or OpenAPI docs when behavior changes.
By contributing, you agree that your contributions will be licensed under the MIT License.
For more information or inquiries, please reach out at: noelmugisha332@gmail.com
This project is licensed under the MIT License — see the LICENSE file for details.