Enterprise authentication and RBAC platform — JWT auth service with Keycloak/LDAP SSO, role-based access control, OTP verification, and a React admin frontend.
| Layer | Technology |
|---|---|
| Backend | Java 21, Spring Boot 3, PostgreSQL, Redis, Flyway |
| Auth | JWT (HS512), Spring Security 6, Spring LDAP |
| SSO | Keycloak (dev) / Keycloak (prod), OIDC + PKCE |
| Async | AWS SQS + SES (LocalStack in dev) |
| Frontend | React 19, Vite, TailwindCSS, Axios |
| Infrastructure | Docker Compose, Kubernetes (k8s/) |
fp-be/
├── auth-be/ # Spring Boot auth service (port 8080)
├── auth-fe/ # React admin frontend (port 5173)
├── k8s/ # Kubernetes manifests
├── docker-compose.yml # Local infrastructure
└── README.md
- Java 21
- Maven 3.9+
- Node.js 20+
- Docker Desktop
- AWS CLI (for LocalStack SES setup)
docker compose up -d| Service | URL / Host | Credentials |
|---|---|---|
| PostgreSQL (auth) | localhost:5433 |
admin / admin |
| PostgreSQL (product) | localhost:5434 |
admin / admin |
| Redis | localhost:6379 |
— |
| pgAdmin | http://localhost:5050 | admin@example.com / admin |
| Keycloak | http://localhost:8180 | admin / admin |
| OpenLDAP | localhost:389 |
cn=admin,dc=corp,dc=example,dc=com / admin |
| phpLDAPadmin | http://localhost:6443 | same as OpenLDAP |
LocalStack resets verified SES identities on every restart. After docker compose up -d:
aws --endpoint-url=http://localhost:4566 ses verify-email-identity \
--email-address noreply@shop.com --region us-east-1OTP emails will fail with
MessageRejectedExceptionif this step is skipped or after a LocalStack container restart.
Realm data persists across restarts via
dev-filestorage.
- Open http://localhost:8180 → log in as
admin / admin - Create realm → name:
corporate - Clients → Create client → ID:
fp-auth-client→ enable Direct access grants- Valid redirect URIs:
http://localhost:5173/*,http://localhost:5174/* - Web origins:
http://localhost:5173
- Valid redirect URIs:
- Create test users (Users → Add user → set email as username → Email verified ON → set password, Temporary OFF):
| Password | |
|---|---|
alice@corp.example.com |
Alice@Pass1! |
bob@corp.example.com |
Bob@Pass1! |
carol@corp.example.com |
Carol@Pass1! |
carolis pre-seeded inGRP-SYSTEM-ADMINS(LDAP bootstrap) and maps toSYSTEM_ADMINgroup via Flyway migrationV19.
cd auth
mvn spring-boot:run| Endpoint | URL |
|---|---|
| API base | http://localhost:8080 |
| Swagger UI | http://localhost:8080/swagger-ui.html |
cd auth-fe
npm install
npm run devAdmin pages require RBAC permissions delivered through groups. The hierarchy is:
Users → Groups → Roles → Permissions
AD users' group memberships are managed in LDAP/Keycloak. Local users are assigned groups via the admin UI or API.
carol is already in GRP-SYSTEM-ADMINS → maps to SYSTEM_ADMIN group → gets ROLE_SYSTEM_ADMIN. Log in via POST /auth/ad/login with a Keycloak id_token.
To add another user, add them to GRP-SYSTEM-ADMINS in OpenLDAP via phpLDAPadmin (http://localhost:6443) and create a matching Keycloak account.
Assign the user to SYSTEM_ADMIN (or SUPER_ADMIN) via SQL:
INSERT INTO user_group_memberships (user_id, group_id)
SELECT u.id, g.id FROM users u
JOIN user_groups g ON g.name = 'SYSTEM_ADMIN'
WHERE u.email = 'youruser@example.com';Or use the admin UI: Users → assign group.
| Group | Role | Key permissions |
|---|---|---|
SYSTEM_ADMIN |
ROLE_SYSTEM_ADMIN |
DASHBOARD_VIEW, USER_GROUPS_MANAGE, GROUP_MANAGE, ROLE_MANAGE, PERMISSION_MANAGE, AUDIT_LOG_VIEW, SYSTEM_CONFIG_VIEW |
SUPER_ADMIN |
ROLE_SUPER_ADMIN |
all permissions |
RETAIL_CUSTOMER |
ROLE_CUSTOMER_BASIC |
ACCOUNT_VIEW, TRANSACTION_VIEW |
Backend — sensible defaults for local dev; no .env file required.
| Variable | Default | Description |
|---|---|---|
JWT_SECRET |
(dev key) | Min 32-byte secret — change in production |
AD_ENABLED |
true |
Enable AD/OIDC login |
AD_LDAP_PASSWORD |
admin |
LDAP bind password (use svc-ldap in prod) |
CORS_ALLOWED_ORIGINS |
localhost:5173,5174 |
Frontend origins |
REDIS_HOST |
localhost |
Redis host |
AWS_ENDPOINT |
http://localhost:4566 |
LocalStack endpoint (blank = real AWS) |
Frontend — auth-fe/.env:
VITE_API_BASE_URL=http://localhost:8080
VITE_KEYCLOAK_URL=http://localhost:8180
VITE_KEYCLOAK_REALM=corporate
VITE_KEYCLOAK_CLIENT_ID=fp-auth-client
VITE_AUDIT_PAGE_SIZE=10# Stop all services
docker compose down
# Reset OpenLDAP (re-seeds bootstrap.ldif on next start)
docker compose rm -sf openldap && docker compose up -d openldap
# Wipe Keycloak data and start fresh
docker compose rm -sf keycloak && docker volume rm fp-be_keycloak_data && docker compose up -d keycloak
# Re-verify SES sender after LocalStack restart
aws --endpoint-url=http://localhost:4566 ses verify-email-identity \
--email-address noreply@shop.com --region us-east-1
# View backend logs
docker compose logs -f auth-db
# Run backend tests
cd auth && mvn testerDiagram
USERS ||--o{ ADDRESSES : has
USERS ||--o{ OTP_VERIFICATION : has
USERS ||--o{ PASSWORD_HISTORY : has
USERS ||--o{ USER_LOG : has
USERS ||--o{ USER_ROLE_ASSIGNMENTS : has
USERS ||--o{ USER_GROUP_MEMBERSHIPS : has
USERS ||--o{ AUDIT_LOG : creates
ROLES ||--o{ ROLE_PERMISSIONS : has
ROLES ||--o{ GROUP_ROLES : has
ROLES ||--o{ USER_ROLE_ASSIGNMENTS : assigned
PERMISSIONS ||--o{ ROLE_PERMISSIONS : has
USER_GROUPS ||--o{ GROUP_ROLES : has
USER_GROUPS ||--o{ USER_GROUP_MEMBERSHIPS : has
USER_GROUPS ||--o{ AD_GROUP_MAPPINGS : mapped
USERS {
int id PK
string name
string email UK
string phone
string password
string status
string date_of_birth
string gender
string profile_picture_url
int failed_login_attempts
string locked_until
string last_login_at
string password_changed_at
string ad_object_id
string auth_provider
string created_at
string updated_at
}
ADDRESSES {
int id PK
int user_id FK
string address_line1
string address_line2
string street
string postal_code
string state
string country
}
OTP_VERIFICATION {
int id PK
int user_id FK
string otp_hash
string expires_at
int attempts
int used
string created_at
string updated_at
}
PASSWORD_HISTORY {
int id PK
int user_id FK
string password_hash
string created_at
}
USER_LOG {
int id PK
int user_id FK
string user_token
string token_type
string issued_at
string expires_at
string ip_address
string user_agent
string created_at
string updated_at
}
ROLES {
int id PK
string name UK
string description
string created_at
string updated_at
}
PERMISSIONS {
int id PK
string code UK
string description
string category
string created_at
string updated_at
}
ROLE_PERMISSIONS {
int role_id PK, FK
int permission_id PK, FK
}
USER_ROLE_ASSIGNMENTS {
int user_id PK, FK
int role_id PK, FK
string assigned_at
}
USER_GROUPS {
int id PK
string name UK
string description
string type
string created_at
string updated_at
}
GROUP_ROLES {
int group_id PK, FK
int role_id PK, FK
}
USER_GROUP_MEMBERSHIPS {
int user_id PK, FK
int group_id PK, FK
string assigned_at
}
AD_GROUP_MAPPINGS {
int id PK
string ad_group_id UK
string ad_group_name
int local_group_id FK
int auto_created
string created_at
string updated_at
}
AUDIT_LOG {
int id PK
int actor_id FK
string actor_name
string action
string resource
string resource_id
string details
string ip_address
string status
string created_at
}
FLYWAY_SCHEMA_HISTORY {
int installed_rank PK
string version
string description
string type
string script
int checksum
string installed_by
string installed_on
int execution_time
int success
}
- Set
JWT_SECRETto a cryptographically random ≥ 32-byte value (Vault / KMS) - Set
AD_LDAP_USER_DN/AD_LDAP_PASSWORDto thesvc-ldapservice account from secrets manager - Set
CORS_ALLOWED_ORIGINSto exact production frontend URL - Set
AWS_ENDPOINTto blank (uses real AWS SQS / SES) - Replace Keycloak with Keycloak — update
AD_JWKS_URI,AD_ISSUER,AD_AUDIENCE - Use LDAPS (
ldaps://) for LDAP - Enable HTTPS at load balancer / ingress
- Disable SQL debug logging (
application-prod.properties)