Skip to content

izzoukeab/demo-spring-security-jwt-lib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spring Demo API 🚀

Java Spring Boot PostgreSQL Redis License

Demo REST API showcasing the Javloom library ecosystem — JWT authentication, passwordless SMS login, fine-grained permissions, and User CRUD.


Stack 🧱

Layer Technology
Runtime Java 21 + Spring Boot 3.4.1
Security Spring Security + spring-security-jwt-lib
Persistence PostgreSQL 16 + Spring Data JPA + Flyway
Cache / OTP Redis 7
Validation spring-rest-commons validators
Build Maven 3.9+

Prerequisites ✅

  • Docker Desktop 3.x+
  • Java 21+
  • Maven 3.9+

Run Locally ▶️

1. Start infrastructure

docker compose up -d

This starts:

  • PostgreSQL on localhost:5432
  • Redis on localhost:6379

2. Run the app:

./mvnw spring-boot:run

The API starts on http://localhost:8080 by default. 🌐

Configuration ⚙️

Main config file: src/main/resources/application.yaml

Default local settings include:

  • PostgreSQL: localhost:5432 (javloom_demo)
  • Redis: localhost:6379
  • App port: 8080

Admin Bootstrap 👑

On startup, the app checks whether an admin user exists by email and creates one if missing.

Config keys:

javloom:
  admin:
    enabled: true
    email: admin@javloom.io
    phone: +33612345678
    password: Admin1234!
    first-name: Admin
    last-name: Javloom

If javloom.admin.enabled=false, startup bootstrap is skipped. ⏭️


API Reference 📚

Auth 🔑

Email / Password login

POST /api/auth/login
Content-Type: application/json

{
  "email": "admin@javloom.io",
  "password": "Admin1234!"
}

Response:

{
  "userId": "00000000-0000-0000-0000-000000000001",
  "email": "admin@javloom.io",
  "permissions": ["USER_DELETE", "USER_READ", "USER_WRITE"],
  "tokens": {
    "accessToken": "eyJ...",
    "refreshToken": "eyJ...",
    "accessTokenExpiresIn": 900000,
    "refreshTokenExpiresIn": 604800000
  }
}

Refresh token

POST /api/auth/refresh
Content-Type: application/json

{
  "refreshToken": "eyJ..."
}

Logout

POST /api/auth/logout
Authorization: Bearer 

Passwordless SMS — send OTP

POST /api/auth/otp/send
Content-Type: application/json

{
  "phone": "+33612345678"
}

In development, the OTP is printed in the application logs instead of being sent via SMS. Look for a line starting with 📱 SMS to.


Passwordless SMS — verify OTP

POST /api/auth/otp/verify
Content-Type: application/json

{
  "phone": "+33612345678",
  "code": "123456"
}

Users 👥

All user endpoints require a valid Bearer token and the appropriate permission.

List users

GET /api/users?page=0&size=20
Authorization: Bearer 

Required permission: USER_READ

Response:

{
  "content": [...],
  "page": 0,
  "size": 20,
  "totalElements": 1,
  "totalPages": 1,
  "first": true,
  "last": true
}

Get user by id

GET /api/users/{id}
Authorization: Bearer 

Required permission: USER_READ


Create user

POST /api/users
Authorization: Bearer 
Content-Type: application/json

{
  "email": "john@example.com",
  "phone": "+33698765432",
  "password": "Secret1234!",
  "firstName": "John",
  "lastName": "Doe"
}

Required permission: USER_WRITE


Update user

PUT /api/users/{id}
Authorization: Bearer 
Content-Type: application/json

{
  "firstName": "Johnny",
  "phone": "+33611223344"
}

Required permission: USER_WRITE

All fields are optional — only provided fields are updated.


Delete user

DELETE /api/users/{id}
Authorization: Bearer 

Required permission: USER_DELETE


Quick test with curl ⚡

# 1. Login
TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@javloom.io","password":"Admin1234!"}' \
  | jq -r '.tokens.accessToken')

# 2. List users
curl http://localhost:8080/api/users \
  -H "Authorization: Bearer $TOKEN"

# 3. Create user
curl -X POST http://localhost:8080/api/users \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "password": "Secret1234!",
    "firstName": "Jane",
    "lastName": "Doe"
  }'

# 4. Send OTP
curl -X POST http://localhost:8080/api/auth/otp/send \
  -H "Content-Type: application/json" \
  -d '{"phone":"+33612345678"}'
# → check logs for OTP code

# 5. Verify OTP
curl -X POST http://localhost:8080/api/auth/otp/verify \
  -H "Content-Type: application/json" \
  -d '{"phone":"+33612345678","code":""}'

Error response format 🧯

All errors follow the ApiError format from spring-rest-commons:

{
  "message": "User not found with id: abc",
  "exceptionName": "ResourceNotFoundException",
  "timestamp": "2026-04-18T19:00:00Z"
}

Validation errors include field-level details:

{
  "message": "Validation failed",
  "exceptionName": "ValidationException",
  "timestamp": "2026-04-18T19:00:00Z",
  "fieldErrors": [
    { "field": "email", "message": "must not be blank" },
    { "field": "phone", "message": "must be a valid E.164 phone number" }
  ]
}

Project structure 🗂️

javloom-demo-api/
├── docker-compose.yml
├── pom.xml
└── src/main/
    ├── java/io/javloom/demo/
    │   ├── DemoApplication.java
    │   ├── config/
    │   │   ├── SecurityConfig.java
    │   │   ├── RedisConfig.java
    │   │   └── JpaConfig.java
    │   ├── auth/
    │   │   ├── AuthController.java
    │   │   ├── JpaRefreshTokenStore.java
    │   │   ├── RedisOtpStore.java
    │   │   ├── ConsoleSmsAdapter.java
    │   │   ├── UserDetailsServiceImpl.java
    │   │   ├── RefreshTokenEntity.java
    │   │   └── RefreshTokenRepository.java
    │   └── user/
    │       ├── Permission.java
    │       ├── User.java
    │       ├── UserRepository.java
    │       ├── UserMapper.java
    │       ├── UserService.java
    │       ├── UserController.java
    │       └── dto/
    │           ├── UserDto.java
    │           ├── CreateUserRequest.java
    │           └── UpdateUserRequest.java
    └── resources/
        ├── application.yml
        └── db/migration/
            └── V1__init.sql

Javloom libraries used 📦

Library Role
spring-rest-commons ApiError, PageResponse, @NoHtml, @NullOrNotBlank
spring-security-jwt-lib JWT generation, refresh token rotation, @HasPermission, passwordless SMS

The JWT library defines interfaces (ports) that must be implemented by the consuming project. Here is how each port is wired in this demo:

Port Implementation Technology Role
RefreshTokenStore JpaRefreshTokenStore PostgreSQL + JPA Persists and rotates refresh tokens
OtpStore RedisOtpStore Redis Stores OTP codes with TTL
SmsPort ConsoleSmsAdapter Console logs Simulates SMS dispatch via logs
SecurityUserService UserDetailsServiceImpl PostgreSQL + JPA Loads users by email and phone

Replacing the SMS adapter 📲

ConsoleSmsAdapter logs OTPs to the console. To use a real SMS provider, implement SmsPort and register it as a Spring bean:

@Component
@RequiredArgsConstructor
public class TwilioSmsAdapter implements SmsPort {

    private final TwilioProperties props;

    @Override
    public void send(String phone, String message) {
        // Twilio SDK call
    }
}

License 📄

MIT — free to use in personal and commercial projects.


About

Demo REST API showcasing the Javloom library ecosystem — JWT authentication, passwordless SMS login, fine-grained permissions, and User CRUD.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages