diff --git a/README.md b/README.md index 7a48799..8f40492 100644 --- a/README.md +++ b/README.md @@ -1 +1,196 @@ # Auth-API-Gateway + +A modern reactive Spring Boot API Gateway that provides JWT-based authentication and authorization for microservices. Built with Spring Cloud Gateway and WebFlux for high-performance, non-blocking operations. + +## Key Features + +- **JWT Authentication** - Stateless authentication with access and refresh tokens +- **User Management** - Registration, login, and profile management +- **Reactive Architecture** - Built on Spring WebFlux for non-blocking I/O +- **Global Exception Handling** - Structured error responses across all endpoints +- **Secure Password Storage** - BCrypt hashing with 12 rounds +- **Token Refresh Mechanism** - 7-day refresh tokens for seamless user experience + +## Tech Stack + +- **Java 21** - LTS version +- **Spring Boot 3.5.7** - Framework +- **Spring Cloud Gateway** - Reactive gateway +- **Spring Security** - Authentication & authorization +- **PostgreSQL + R2DBC** - Reactive database access +- **JJWT 0.12.5** - JWT token handling +- **Maven** - Build tool + +## Quick Start + +1. **Clone the repository** + ```bash + git clone https://github.com/matthewhou19/auth-api-gateway.git + cd auth-api-gateway + ``` + +2. **Start PostgreSQL database** + + **Option A: Using Docker (Recommended)** + ```bash + docker compose up -d + ``` + + **Option B: Use existing PostgreSQL** + + Make sure PostgreSQL is running on `localhost:5432` with: + - Database: `auth_gateway_db` + - Username: `postgres` + - Password: `password` + +3. **Run the application** + ```bash + cd apigateway + ./mvnw spring-boot:run + ``` + + **Note:** Database tables (`users`, `refresh_tokens`) are created automatically on startup from `schema.sql`. No manual database setup required! + +4. **Access the API** + - Base URL: `http://localhost:8080` + - All endpoints are prefixed with `/auth` + +## API Endpoints + +### Public Endpoints (No authentication required) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/auth/register` | Register a new user | +| POST | `/auth/login` | Login with email/username and password | +| GET | `/auth/refresh/{token}` | Refresh access token using refresh token | + +### Protected Endpoints (Requires JWT) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/auth/me` | Get authenticated user details | +| DELETE | `/auth/logout` | Logout and invalidate refresh token | + +### Example: Register a User + +```bash +curl -X POST http://localhost:8080/auth/register \ + -H "Content-Type: application/json" \ + -d "{\"email\": \"user@example.com\", \"username\": \"johndoe\", \"name\": \"John Doe\", \"password\": \"SecurePass123@\"}" +``` + +**Note**: Using double quotes and escaped JSON for better shell compatibility. Password uses `@` instead of `!` to avoid shell history expansion issues in some terminals. + +### Example: Login + +```bash +curl -X POST http://localhost:8080/auth/login \ + -H "Content-Type: application/json" \ + -d "{\"emailUsername\": \"user@example.com\", \"password\": \"SecurePass123@\"}" +``` + +Response: +```json +{ + "accessToken": "eyJhbGciOiJIUzI1NiJ9...", + "refreshToken": "base64-encoded-token" +} +``` + +### Example: Access Protected Endpoint + +```bash +curl -X GET http://localhost:8080/auth/me \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" +``` + +## Configuration + +### Default Settings + +The application uses the following default database settings (matching the `docker-compose.yml`): + +```yaml +spring: + r2dbc: + url: r2dbc:postgresql://localhost:5432/auth_gateway_db + username: postgres + password: password +``` + +### Custom Configuration + +For production or custom setups, modify `apigateway/src/main/resources/application.yaml`: + +**Custom Database:** +```yaml +spring: + r2dbc: + url: r2dbc:postgresql://your-host:5432/your-database + username: your-username + password: your-password +``` + +**JWT Secret (CRITICAL for Production!):** +```yaml +jwt: + secret: your-secure-secret-key-minimum-32-characters-long +``` + +⚠️ **Security Warning**: The default JWT secret is for development only. MUST be changed for production! + +## Database Schema + +The application automatically creates the required database tables on startup: + +**Tables:** +- `users` - Stores user account information (id, name, username, email, password) +- `refresh_tokens` - Manages refresh token lifecycle (id, user_id, token, expiry_date, created_at) + +**Schema Definition:** +- Table definitions are in `apigateway/src/main/resources/schema.sql` +- Tables are created automatically via Spring's SQL initialization feature +- Uses `CREATE TABLE IF NOT EXISTS` to prevent errors on restart +- Includes indexes on `refresh_tokens` for optimal performance + +**Configuration:** +```yaml +spring: + sql: + init: + mode: never # Use 'always' only in dev/test profiles; handle prod schema via migrations + platform: postgresql +``` + +**Note:** For production environments, use proper database migration tools like Flyway or Liquibase instead of SQL initialization mode. + +## Password Requirements + +- Minimum 8 characters +- At least one uppercase letter +- At least one lowercase letter +- At least one digit +- At least one special character + +## Project Structure + +``` +apigateway/ +├── src/main/java/vaultweb/apigateway/ +│ ├── config/ # Security & JWT configuration +│ ├── controller/ # REST API endpoints +│ ├── service/ # Business logic +│ ├── model/ # Database entities +│ ├── repositories/ # Data access layer +│ ├── dto/ # Request/Response objects +│ ├── exceptions/ # Exception handling +│ └── util/ # Utility classes +└── src/main/resources/ + ├── application.yaml # Application configuration + └── schema.sql # Database schema (auto-applied on startup) +``` + +## License + +[Add your license here] diff --git a/apigateway/src/main/java/vaultweb/apigateway/controller/GatewayAuthController.java b/apigateway/src/main/java/vaultweb/apigateway/controller/GatewayAuthController.java index e8cf4cf..5f9240d 100644 --- a/apigateway/src/main/java/vaultweb/apigateway/controller/GatewayAuthController.java +++ b/apigateway/src/main/java/vaultweb/apigateway/controller/GatewayAuthController.java @@ -48,8 +48,8 @@ public Mono register(@Valid @RequestBody UserRegistrationRequest re } @ResponseStatus(HttpStatus.OK) - @GetMapping("/switch-jwt/{token}") - public Mono switchJwtToken(@PathVariable @Valid @NotEmpty String token) { + @GetMapping("/refresh/{token}") + public Mono refreshToken(@PathVariable @Valid @NotEmpty String token) { return authService.switchToken(token); } diff --git a/apigateway/src/main/resources/application.yaml b/apigateway/src/main/resources/application.yaml index 2fa58ae..2afdad9 100644 --- a/apigateway/src/main/resources/application.yaml +++ b/apigateway/src/main/resources/application.yaml @@ -2,12 +2,16 @@ spring: application: name: apigateway r2dbc: - url: r2dbc:postgresql://localhost:5432/mydatabase + url: r2dbc:postgresql://localhost:5432/auth_gateway_db username: postgres password: password + sql: + init: + mode: never # Use 'always' only in dev/test profiles; handle prod schema via migrations + platform: postgresql jwt: secret: b9f4f7c2-6d8a-4a41-9e3a-99c42e67d9ab-53f1fa3434aa12af9df7e98e894e-change-me auth: - publicUrls: /auth/login,/auth/register,/auth/reset-password,/auth/switch-jwt/** \ No newline at end of file + publicUrls: /auth/login,/auth/register,/auth/reset-password,/auth/refresh/** \ No newline at end of file diff --git a/apigateway/src/main/resources/schema.sql b/apigateway/src/main/resources/schema.sql new file mode 100644 index 0000000..9261b60 --- /dev/null +++ b/apigateway/src/main/resources/schema.sql @@ -0,0 +1,23 @@ +-- Users table +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + username VARCHAR(255) UNIQUE NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL +); + +-- Refresh tokens table +CREATE TABLE IF NOT EXISTS refresh_tokens ( + id BIGSERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token VARCHAR(500) NOT NULL, + expiry_date TIMESTAMP NOT NULL, + created_at TIMESTAMP NOT NULL +); + +-- Create index on user_id for better query performance +CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id); + +-- Create index on token for faster lookups +CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token ON refresh_tokens(token); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9306509 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + postgres: + image: postgres:16-alpine + container_name: auth-gateway-db + # NOTE: These credentials are intended for local development only. + # For any shared or production-like environment, override POSTGRES_USER, + # POSTGRES_PASSWORD, and POSTGRES_DB with secure, environment-specific values. + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: auth_gateway_db + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: