A production-ready, multi-tenant authentication service built with Spring Boot. Suraksha provides comprehensive authentication solutions including email/password login, passwordless magic URLs, two-factor authentication (2FA), and secure token management. Built with Redis for caching, RabbitMQ for asynchronous email delivery, and PostgreSQL for persistence, it offers enterprise-grade security with RS256 JWT signing, refresh token rotation, and organization-level isolation.
- Features
- Architecture
- Authentication Flows
- Database Schema
- Tech Stack
- Project Structure
- API Endpoints
- JWT Token Claims
- Email System
- Getting Started
- Security Features
- Development
- Troubleshooting
- Email/Password Login - Traditional authentication with Bcrypt password hashing
- Magic URL Authentication - Passwordless login via secure email links (10-minute expiration)
- Two-Factor Authentication (2FA) - Optional OTP-based 2FA with 4-digit codes (2-5 minute expiration)
- Token Refresh - Secure refresh token rotation with HttpOnly cookies
- Logout - Token revocation and session termination
- JWT Authentication - RS256-signed JWTs with 15-minute expiration
- JWKS Endpoint - Standard
.well-known/jwks.jsonfor public key distribution - Password Security - Bcrypt hashing with salt
- Token Security - SHA256 hashing for refresh tokens and magic URLs
- Redirect Validation - Organization-scoped redirect URL whitelisting
- Multi-Tenant Organizations - Isolated user namespaces per organization
- API Keys - HMAC-SHA256 hashed API keys for organization access
- User Isolation - Same email can exist across different organizations
- Forgot Password - Secure token-based password reset flow
- Password Reset Tokens - Base64-encoded 64-byte random tokens with Redis storage
- PostgreSQL - Persistent storage with JPA/Hibernate
- Redis - Token caching, OTP storage, and session management
- RabbitMQ - Message queue for asynchronous email delivery
- Flyway - Database migrations
graph TB
subgraph "Client Layer"
Client[Client Application]
end
subgraph "API Gateway Layer"
API[Spring Boot API<br/>Port 8080]
JWKS[JWKS Endpoint<br/>/.well-known/jwks.json]
end
subgraph "Service Layer"
AuthSvc[Authentication Services]
TokenSvc[Token Services]
EmailSvc[Email Services]
OrgSvc[Organization Services]
end
subgraph "Security Layer"
JWT[JWT Service<br/>RS256 Signing]
Hash[Hashing Service<br/>SHA256/Bcrypt]
Valid[Validation Service<br/>Redirect URLs]
end
subgraph "Data Layer"
PG[(PostgreSQL<br/>User Data)]
Redis[(Redis<br/>Tokens & Cache)]
RMQ[RabbitMQ<br/>Email Queue]
end
subgraph "External Services"
SMTP[SMTP Server]
end
Client -->|HTTPS| API
Client -->|Verify JWT| JWKS
API --> AuthSvc
API --> TokenSvc
API --> OrgSvc
AuthSvc --> EmailSvc
AuthSvc --> JWT
AuthSvc --> Hash
AuthSvc --> Valid
TokenSvc --> JWT
TokenSvc --> Hash
EmailSvc -->|Enqueue| RMQ
RMQ -->|Consume| SMTP
AuthSvc --> PG
AuthSvc --> Redis
TokenSvc --> Redis
OrgSvc --> PG
style Client fill:#bbdefb,stroke:#1976d2,stroke-width:2px,color:#000
style API fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style JWKS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style AuthSvc fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style TokenSvc fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style EmailSvc fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style OrgSvc fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style JWT fill:#f8bbd0,stroke:#c2185b,stroke-width:2px,color:#000
style Hash fill:#f8bbd0,stroke:#c2185b,stroke-width:2px,color:#000
style Valid fill:#f8bbd0,stroke:#c2185b,stroke-width:2px,color:#000
style PG fill:#b2dfdb,stroke:#00796b,stroke-width:2px,color:#000
style Redis fill:#ffccbc,stroke:#d84315,stroke-width:2px,color:#000
style RMQ fill:#ffe0b2,stroke:#ef6c00,stroke-width:2px,color:#000
style SMTP fill:#d1c4e9,stroke:#512da8,stroke-width:2px,color:#000
graph LR
subgraph "Docker Environment"
subgraph "Application Container"
App[Suraksha Spring Boot<br/>Port 8080]
end
subgraph "Data Containers"
DB[PostgreSQL<br/>Port 5433]
Cache[Redis<br/>Port 6379]
Queue[RabbitMQ<br/>Ports 5672, 15672]
end
end
subgraph "External"
Client[Client Apps]
SMTP[Email Provider]
end
Client -->|HTTP/HTTPS| App
App -->|JDBC| DB
App -->|Redis Protocol| Cache
App -->|AMQP| Queue
Queue -->|SMTP| SMTP
style App fill:#4caf50
style DB fill:#2196f3
style Cache fill:#f44336
style Queue fill:#ff9800
style Client fill:#9c27b0
style SMTP fill:#607d8b
graph LR
subgraph Controllers["Controllers"]
UC[UserController]
JC[JwksController]
end
subgraph CoreServices["Core Services"]
LS[LoginService]
RS[RegistrationService]
MUS[MagicUrlService]
TFS[TwoFactorService]
FPS[ForgotPasswordService]
RTS[RefreshTokenService]
LOS[LogoutService]
end
subgraph SupportServices["Support Services"]
JS[JwtService]
OS[OtpService]
HS[HashingService]
HMS[HmacService]
VRS[ValidRedirectService]
ETS[EmailTemplateService]
MSS[MailSenderService]
end
subgraph DataAccess["Data Access"]
UR[UserRepository]
OR[OrganisationsRepository]
RTR[RefreshTokenRepository]
end
UC --> LS
UC --> RS
UC --> MUS
UC --> TFS
UC --> FPS
UC --> RTS
UC --> LOS
JC --> JS
LS --> JS
LS --> OS
LS --> HS
LS --> VRS
RS --> JS
RS --> HS
MUS --> HS
MUS --> ETS
MUS --> MSS
TFS --> OS
TFS --> ETS
TFS --> MSS
FPS --> HS
FPS --> ETS
FPS --> MSS
RTS --> JS
RTS --> HS
LS --> UR
RS --> UR
MUS --> UR
FPS --> UR
RS --> OR
RTS --> RTR
style UC fill:#bbdefb,stroke:#1976d2,stroke-width:2px,color:#000
style JC fill:#bbdefb,stroke:#1976d2,stroke-width:2px,color:#000
style LS fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style RS fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style MUS fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style TFS fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style FPS fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style RTS fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style LOS fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
style JS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style OS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style HS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style HMS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style VRS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style ETS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style MSS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
style UR fill:#f8bbd0,stroke:#c2185b,stroke-width:2px,color:#000
style OR fill:#f8bbd0,stroke:#c2185b,stroke-width:2px,color:#000
style RTR fill:#f8bbd0,stroke:#c2185b,stroke-width:2px,color:#000
sequenceDiagram
participant C as Client
participant API as Spring Boot API
participant Auth as LoginService
participant Hash as HashingService
participant JWT as JwtService
participant DB as PostgreSQL
participant Redis as Redis
C->>API: POST /api/v1/auth/login<br/>{email, password, orgId}
API->>Auth: authenticate()
Auth->>DB: findUser(email, orgId)
DB-->>Auth: UserEntity
Auth->>Hash: verifyPassword()
Hash-->>Auth: valid
alt 2FA Enabled
Auth->>Redis: storeOTP(email, code)
Auth->>C: {twoFaRequired: true}
else 2FA Disabled
Auth->>JWT: generateToken(user)
JWT-->>Auth: JWT Token
Auth->>Hash: hashRefreshToken()
Auth->>Redis: storeRefreshToken()
Auth->>C: Set Cookies<br/>{jwt, refresh_token}
Auth-->>C: {status: true}
end
sequenceDiagram
participant C as Client
participant API as Spring Boot API
participant Magic as MagicUrlService
participant Hash as HashingService
participant Email as EmailService
participant Redis as Redis
participant RMQ as RabbitMQ
participant JWT as JwtService
Note over C,RMQ: Phase 1: Request Magic URL
C->>API: POST /api/v1/magic-url<br/>{email, orgId}
API->>Magic: sendMagicUrl()
Magic->>Hash: generateSecureToken()
Hash-->>Magic: token
Magic->>Hash: hashToken(token)
Hash-->>Magic: hashedToken
Magic->>Redis: store(hashedToken, 10min)
Magic->>Email: generateMagicUrlEmail()
Email->>RMQ: enqueue(email)
Magic-->>C: {status: true, message: "sent"}
Note over C,JWT: Phase 2: Verify Magic URL
C->>C: User clicks link in email
C->>API: GET /api/v1/verify-magic-url?token=xxx
API->>Magic: verifyMagicUrl(token)
Magic->>Hash: hashToken(token)
Magic->>Redis: getAndDelete(hashedToken)
Redis-->>Magic: userData
Magic->>JWT: generateToken(user)
JWT-->>Magic: JWT Token
Magic->>API: tokens
API->>C: Set Cookies & Redirect
sequenceDiagram
participant C as Client
participant API as Spring Boot API
participant TFA as TwoFactorService
participant OTP as OtpService
participant Email as EmailService
participant Redis as Redis
participant JWT as JwtService
Note over C,Redis: Phase 1: Login triggers OTP
C->>API: POST /api/v1/auth/login<br/>(2FA enabled user)
API->>OTP: generateOTP()
OTP->>OTP: random(1000-9999)
OTP->>Redis: store(email:otp, 2-5min)
OTP->>Email: sendOtpEmail(email, code)
API-->>C: {twoFaRequired: true}
Note over C,JWT: Phase 2: Verify OTP
C->>API: POST /api/v1/auth/2fa/otp<br/>{email, otp, orgId}
API->>TFA: verifyOtp()
TFA->>Redis: get(email:otp)
Redis-->>TFA: storedOtp
TFA->>TFA: compare(otp, storedOtp)
TFA->>Redis: delete(email:otp)
TFA->>JWT: generateToken(user)
JWT-->>TFA: JWT Token
TFA->>API: tokens
API->>C: Set Cookies<br/>{jwt, refresh_token}
sequenceDiagram
participant C as Client
participant API as Spring Boot API
participant RT as RefreshTokenService
participant Hash as HashingService
participant JWT as JwtService
participant Redis as Redis
participant DB as PostgreSQL
C->>API: POST /api/v1/auth/refresh<br/>Cookie: refresh_token, refresh_token_id
API->>RT: refreshToken()
RT->>Hash: hashToken(refresh_token)
Hash-->>RT: hashedToken
RT->>Redis: get(refresh_token_id)
Redis-->>RT: storedToken
RT->>RT: compare(hashedToken, storedToken)
alt Token Valid
RT->>Redis: delete(old_token_id)
RT->>DB: getUser(userId)
RT->>JWT: generateNewToken(user)
JWT-->>RT: new JWT
RT->>Hash: hashNewRefreshToken()
RT->>Redis: store(new_token, 30min)
RT->>DB: save(new_refresh_token)
RT->>API: new tokens
API->>C: Set New Cookies<br/>{jwt, refresh_token}
else Token Invalid/Expired
RT-->>API: error
API-->>C: 401 Unauthorized
end
sequenceDiagram
participant C as Client
participant API as Spring Boot API
participant FP as ForgotPasswordService
participant Hash as HashingService
participant Email as EmailService
participant Redis as Redis
participant DB as PostgreSQL
Note over C,Redis: Phase 1: Request Reset
C->>API: POST /api/v1/forgot-password<br/>{email}
API->>FP: generateResetToken()
FP->>Hash: generateSecureToken(64 bytes)
Hash-->>FP: token
FP->>Hash: encodeBase64(token)
Hash-->>FP: resetToken
FP->>Redis: store(email:resetToken, TTL)
FP->>Email: sendResetEmail(email, token)
FP-->>C: {resetToken, message}
Note over C,DB: Phase 2: Reset Password
C->>API: POST /api/v1/reset-password<br/>{token, newPassword}
API->>FP: resetPassword()
FP->>Redis: get(token)
Redis-->>FP: email
FP->>Hash: bcrypt(newPassword)
FP->>DB: updatePassword(email, hashedPassword)
FP->>Redis: delete(token)
FP-->>C: {status: true}
erDiagram
ORGANISATIONS ||--o{ USERS : "has"
ORGANISATIONS ||--o{ VALID_REDIRECTS : "configures"
USERS ||--o{ REFRESH_TOKENS : "owns"
ORGANISATIONS {
uuid id PK
string name
string organisation_id UK "org_xxxxx"
string api_key_hash "HMAC-SHA256"
timestamp created_at
timestamp updated_at
}
USERS {
uuid id PK
string mail_id
string password_hash "Bcrypt"
uuid organisation_id FK
boolean two_fa_enabled
timestamp created_at
timestamp updated_at
}
REFRESH_TOKENS {
uuid id PK
uuid user_id FK
string token_hash "SHA256"
string client_ip
string user_agent
timestamp created_at
timestamp expires_at
}
VALID_REDIRECTS {
uuid id PK
uuid organisation_id FK
string redirect_url
timestamp created_at
}
REDIS_KEYS {
string key PK
string value
int ttl "Time To Live"
}
REDIS_KEYS ||--o{ OTP_STORAGE : "stores"
REDIS_KEYS ||--o{ MAGIC_URL_TOKENS : "stores"
REDIS_KEYS ||--o{ PASSWORD_RESET_TOKENS : "stores"
REDIS_KEYS ||--o{ REFRESH_TOKEN_CACHE : "caches"
OTP_STORAGE {
string key "email:otp"
string otp "4-digit code"
int ttl "2-5 minutes"
}
MAGIC_URL_TOKENS {
string key "magic:hashedToken"
json value "user data"
int ttl "10 minutes"
}
PASSWORD_RESET_TOKENS {
string key "reset:email"
string token
int ttl "configurable"
}
REFRESH_TOKEN_CACHE {
string key "refresh:tokenId"
string hashedToken
int ttl "30 minutes"
}
| Component | Technology | Purpose |
|---|---|---|
| Framework | Spring Boot 4.0 | Application framework |
| Language | Java 21 | Programming language |
| Database | PostgreSQL 15 | Persistent data storage |
| Cache | Redis | Token storage & session management |
| Message Queue | RabbitMQ | Asynchronous email delivery |
| Migrations | Flyway | Database version control |
| JWT Library | jjwt 0.12.5 | JSON Web Token handling |
| Security | Spring Security | Authentication & authorization |
| ORM | Hibernate/JPA | Object-relational mapping |
| Build Tool | Maven | Dependency management & builds |
| Containerization | Docker Compose | Development environment |
| Spring Mail + SMTP | Email delivery | |
| Validation | Jakarta Validation | DTO validation |
suraksha/
├── docker-compose.yml # PostgreSQL container configuration
├── suraksha/ # Spring Boot application root
│ ├── pom.xml # Maven dependencies
│ └── src/main/
│ ├── java/com/aneesh/suraksha/
│ │ ├── SurakshaApplication.java # Application entry point
│ │ ├── SecurityConfig.java # Spring Security configuration
│ │ │
│ │ ├── config/ # Application configuration
│ │ │ ├── AppSecretConfig.java # JWT & secret keys
│ │ │ └── RabbitMQConfig.java # Message queue setup
│ │ │
│ │ ├── redis/ # Redis configuration
│ │ │ └── configuration/
│ │ │ └── RedisConfig.java # Redis connection setup
│ │ │
│ │ ├── dto/ # Shared DTOs
│ │ │ └── MailDto.java # Email data transfer object
│ │ │
│ │ └── users/ # User domain module
│ │ │
│ │ ├── component/ # Utility components
│ │ │ ├── ClientIPAddress.java # IP address extraction
│ │ │ ├── OrganisationIdGenerator.java # Org ID generation
│ │ │ └── RefreshTokenGenerator.java # Token generation
│ │ │
│ │ ├── configuration/
│ │ │ └── PasswordEncoderConfig.java # Bcrypt configuration
│ │ │
│ │ ├── controller/ # REST API endpoints
│ │ │ ├── JwksController.java # Public key endpoint
│ │ │ ├── TestController.java # Development endpoints
│ │ │ └── UserController.java # Auth API routes
│ │ │
│ │ ├── dto/ # Data transfer objects
│ │ │ ├── LoginRequest.java
│ │ │ ├── LoginResponse.java
│ │ │ ├── RegisterRequest.java
│ │ │ ├── RegisterResponse.java
│ │ │ ├── MagicLinkRequest.java
│ │ │ ├── MagicLinkResponse.java
│ │ │ ├── OTPRequest.java
│ │ │ ├── OTPResponse.java
│ │ │ ├── RefreshTokenResponse.java
│ │ │ ├── CreateOrganizationRequest.java
│ │ │ ├── CreateOrganizationResponse.java
│ │ │ └── ...
│ │ │
│ │ ├── model/ # JPA entities & repositories
│ │ │ ├── UserEntity.java # User entity
│ │ │ ├── UserRepository.java # User data access
│ │ │ ├── Organisations.java # Organization entity
│ │ │ ├── OrganisationsRepository.java # Org data access
│ │ │ ├── RefreshToken.java # Refresh token entity
│ │ │ └── RefreshTokenRepository.java # Token data access
│ │ │
│ │ └── service/ # Business logic layer
│ │ ├── LoginService.java # Login authentication
│ │ ├── RegistrationService.java # User registration
│ │ ├── MagicUrlService.java # Passwordless auth
│ │ ├── TwofactorService.java # 2FA logic
│ │ ├── OtpService.java # OTP generation/validation
│ │ ├── ForgotPasswordService.java # Password reset
│ │ ├── EmailTemplateService.java # HTML templates
│ │ ├── MailSenderService.java # Email queue producer
│ │ ├── EmailProducer.java # RabbitMQ producer
│ │ ├── JwtService.java # JWT operations
│ │ ├── RefreshTokenService.java # Token refresh
│ │ ├── RefreshCheck.java # Token validation
│ │ ├── LogoutService.java # Session termination
│ │ ├── ValidRedirectService.java # URL validation
│ │ ├── HashingService.java # SHA256/Bcrypt
│ │ ├── HmacService.java # HMAC operations
│ │ └── OrganisationOnboard.java # Org creation
│ │
│ └── resources/
│ ├── application.properties # Application configuration
│ └── db/migration/ # Flyway SQL migrations
│ ├── V1__initial_schema.sql
│ ├── V2__add_organisations.sql
│ └── ...
POST /api/v1/auth/register
Content-Type: application/json
{
"mailId": "[email protected]",
"password": "securepassword",
"organisationId": "org_xxxxx"
}Response:
- Sets
jwtcookie (HttpOnly, 15 minutes) - Sets
refresh_tokencookie (HttpOnly, 30 minutes) - Sets
refresh_token_idcookie (HttpOnly, 30 minutes)
{
"status": true,
"message": "User Created Successfully",
"token": "eyJhbGc..."
}POST /api/v1/auth/login
Content-Type: application/json
{
"mailId": "[email protected]",
"password": "securepassword",
"organisationId": "org_xxxxx",
"redirect": "https://app.example.com/dashboard"
}Response (without 2FA):
{
"status": true,
"message": "Success"
}Response (with 2FA enabled):
{
"status": true,
"message": "OTP sent to your email",
"twoFaRequired": true
}Request a magic sign-in link:
POST /api/v1/magic-url
Content-Type: application/json
{
"mailId": "[email protected]",
"organisationId": "org_xxxxx",
"redirect": "https://app.example.com/dashboard"
}Verify the magic URL (user clicks link in email):
GET /api/v1/verify-magic-url?token=<magic_token>&redirect=<redirect_url>POST /api/v1/auth/2fa/otp
Content-Type: application/json
{
"mailId": "[email protected]",
"otp": "1234",
"organisationId": "org_xxxxx",
"redirect": "https://app.example.com/dashboard"
}POST /api/v1/auth/refresh
Cookie: refresh_token=<token>; refresh_token_id=<token_id>POST /api/v1/auth/logout
Cookie: refresh_token_id=<token_id>POST /api/v1/forgot-password
Content-Type: application/json
{
"mailId": "[email protected]"
}Response:
{
"resetToken": "base64-encoded-token",
"message": "Password reset token generated"
}Note: In production, the reset token should be sent via email, not returned in the response.
POST /api/v1/organisations
Content-Type: application/json
{
"name": "My Company"
}Response:
{
"id": "org_xxxxx",
"name": "My Company",
"apiKey": "suraksha_apiKey_xxxxxxxx"
}Important: The API key is only returned once during creation. Store it securely.
GET /api/v1/organisationsGET /api/v1/usersGET /.well-known/jwks.jsonResponse:
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"n": "...",
"e": "AQAB"
}
]
}Tokens issued by Suraksha include the following claims:
| Claim | Description | Example |
|---|---|---|
sub |
User's email address | "[email protected]" |
userId |
User's UUID | "123e4567-e89b-12d3-a456-426614174000" |
mailId |
User's email address | "[email protected]" |
organisationId |
Organization ID | "org_abc123xyz" |
iat |
Issued at timestamp | 1679097600 |
exp |
Expiration timestamp | 1679098500 (15 min) |
graph LR
subgraph "Application Layer"
Service[Auth/Magic/OTP Service]
Template[EmailTemplateService]
Producer[MailSenderService]
end
subgraph "Message Queue"
Exchange[EMAIL_EXCHANGE]
Queue[Email Queue]
end
subgraph "Delivery Layer"
Consumer[Email Consumer]
SMTP[SMTP Server]
end
subgraph "User"
Inbox[User Inbox]
end
Service -->|Generate| Template
Template -->|HTML Email| Producer
Producer -->|Enqueue| Exchange
Exchange -->|Route| Queue
Queue -->|Consume| Consumer
Consumer -->|Send| SMTP
SMTP -->|Deliver| Inbox
style Service fill:#4caf50
style Template fill:#2196f3
style Exchange fill:#ff9800
style Queue fill:#ff9800
style Consumer fill:#9c27b0
style SMTP fill:#607d8b
style Inbox fill:#f44336
Professional HTML email templates with responsive design:
- OTP Emails - 4-digit OTP codes with monospace formatting
- Magic URL Emails - Secure sign-in links with button and raw link
- Password Reset Emails - Token-based reset links
- Dark Mode Support - Automatic color scheme detection
- Responsive Design - Mobile-friendly layouts
- Branded Design - Suraksha branding with GitHub logo
- RabbitMQ Integration - Non-blocking email sending
- Email Queue -
EMAIL_EXCHANGEandEMAIL_ROUTING_KEY - Reliability - Message persistence ensures delivery
- Scalability - Multiple consumers for high volume
| Email Type | Expiration | Storage | Purpose |
|---|---|---|---|
| OTP | 2-5 min | Redis | Two-factor authentication |
| Magic URL | 10 min | Redis | Passwordless login |
| Password Reset | Custom | Redis | Password recovery |
- Java 21+
- Docker & Docker Compose
- Maven
- Redis (via Docker or local)
- RabbitMQ (via Docker or local)
# Start PostgreSQL (port 5433)
docker-compose up -d
# Start Redis (port 6379)
docker run -d --name suraksha-redis -p 6379:6379 redis:latest
# Start RabbitMQ (ports 5672, 15672)
docker run -d --name suraksha-rabbitmq \
-p 5672:5672 -p 15672:15672 \
rabbitmq:3-managementService Ports:
- PostgreSQL:
5433 - Redis:
6379 - RabbitMQ:
5672(AMQP),15672(Management UI)
Create suraksha/src/main/resources/application.properties:
# Database Configuration
spring.datasource.url=jdbc:postgresql://localhost:5433/my_database
spring.datasource.username=admin
spring.datasource.password=securepassword
# JPA Configuration
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# Flyway Migration
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
# Redis Configuration
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.timeout=60000
# RabbitMQ Configuration
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
# Email Queue Configuration
rabbitmq.exchange.name=EMAIL_EXCHANGE
rabbitmq.routing.key=EMAIL_ROUTING_KEY
# RSA Keys for JWT (generate using openssl)
rsa.private-key=-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----
rsa.public-key=-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----
# Application Secrets (base64 encoded)
app.secret.key=<base64-secret-for-jwt>
app.secret_refresh.key=<base64-secret-for-refresh-tokens>
app.secret_api.key=<base64-secret-for-api-keys>
# Server Configuration
server.port=8080# Generate private key
openssl genrsa -out private.key 2048
# Convert to PKCS#8 format (required by Java)
openssl pkcs8 -topk8 -inform PEM -outform PEM \
-nocrypt -in private.key -out private_pkcs8.key
# Generate public key
openssl rsa -in private.key -pubout -out public.key
# View keys (copy to application.properties)
cat private_pkcs8.key
cat public.key# Generate JWT secret
openssl rand -base64 32
# Generate refresh token secret
openssl rand -base64 32
# Generate API key secret
openssl rand -base64 32cd suraksha
./mvnw spring-boot:runThe server starts on http://localhost:8080.
# Check JWKS endpoint
curl http://localhost:8080/.well-known/jwks.json
# Check RabbitMQ Management UI
open http://localhost:15672 # Login: guest/guest
# Check Redis connection
redis-cli -h localhost -p 6379 pinggraph TB
subgraph "Token Generation"
Gen[Generate Token]
Sign[RS256 Signature]
Store[Store in Redis]
end
subgraph "Token Storage"
Cookie[HttpOnly Cookie]
Redis[(Redis Cache)]
DB[(PostgreSQL)]
end
subgraph "Token Validation"
Extract[Extract from Cookie]
Verify[Verify Signature]
Check[Check Expiration]
Lookup[Lookup in Redis]
end
subgraph "Security Measures"
Rotate[Token Rotation]
Revoke[Revocation Support]
Expire[Auto Expiration]
end
Gen --> Sign
Sign --> Store
Store --> Cookie
Store --> Redis
Store --> DB
Extract --> Verify
Verify --> Check
Check --> Lookup
Lookup --> Rotate
Lookup --> Revoke
Lookup --> Expire
style Sign fill:#f44336
style Cookie fill:#4caf50
style Redis fill:#ff9800
style Verify fill:#2196f3
style Rotate fill:#9c27b0
- RS256 Signing - Asymmetric cryptography with public key verification
- Short-lived JWTs - 15-minute expiration minimizes token theft impact
- Refresh Token Rotation - New refresh token issued on each use
- HttpOnly Cookies - Prevents XSS attacks by blocking JavaScript access
- Secure & SameSite Flags - All cookies have
SecureandSameSite=Strict - Token Revocation - Individual token invalidation via logout
- Bcrypt Hashing - Industry-standard with automatic salting
- Configurable Work Factor - Adjustable computational cost
- No Plain Text Storage - Passwords never stored or logged
- Password Reset Tokens - Cryptographically secure random generation
- HMAC-SHA256 Hashing - API keys hashed before storage
- One-time Display - Keys only shown at creation time
- Organization Scoping - Keys tied to specific organizations
- SHA256 Hashing - Tokens hashed before Redis storage
- Metadata Tracking - IP address and User-Agent recorded
- 30-minute Expiration - Configurable TTL
- Redis-backed Storage - Fast, distributed validation
- Single-use Tokens - Rotation on each refresh prevents replay attacks
- Time-limited Tokens - Magic URLs (10 min), OTPs (2-5 min)
- SHA256 Hashing - Magic URL tokens hashed in Redis
- Single-use Tokens - Deleted after successful verification
- Cryptographically Secure -
SecureRandomfor generation - Redis TTL - Automatic expiration via time-to-live
- Whitelist-based - Only organization-scoped URLs allowed
- URL Validation - Prevents open redirect vulnerabilities
- Organization Isolation - Each org maintains own whitelist
- Client IP Tracking - All requests log IP addresses
- User-Agent Logging - Browser and device information
- Audit Trail - Metadata stored with refresh tokens
cd suraksha
./mvnw clean package./mvnw test# Development profile
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
# Production profile
./mvnw spring-boot:run -Dspring-boot.run.profiles=prod# Create new migration
# Add file: src/main/resources/db/migration/V{version}__description.sql
# View migration status
./mvnw flyway:info
# Migrate database
./mvnw flyway:migrate
# Rollback (use with caution)
./mvnw flyway:undoSpring Boot Actuator endpoints (if enabled):
# Health check
curl http://localhost:8080/actuator/health
# Application info
curl http://localhost:8080/actuator/info
# Metrics
curl http://localhost:8080/actuator/metricsIssue: Cannot connect to PostgreSQL
Solutions:
# Check if PostgreSQL container is running
docker ps | grep postgres
# Check port availability
lsof -i :5433
# View container logs
docker logs <postgres-container-id>
# Restart container
docker-compose restart
# Verify credentials in application.propertiesIssue: Redis connection failures
Solutions:
# Check Redis container
docker ps | grep redis
# Test Redis connection
redis-cli -h localhost -p 6379 ping
# Expected: PONG
# Check port availability
lsof -i :6379
# View Redis logs
docker logs suraksha-redis
# Clear Redis cache
redis-cli -h localhost -p 6379 FLUSHALLIssue: Email queue not working
Solutions:
# Check RabbitMQ container
docker ps | grep rabbitmq
# Access Management UI
open http://localhost:15672
# Login: guest/guest
# Check port availability
lsof -i :5672
lsof -i :15672
# View RabbitMQ logs
docker logs suraksha-rabbitmq
# Restart RabbitMQ
docker restart suraksha-rabbitmqIssue: Emails not being delivered
Diagnosis:
# Check RabbitMQ queue
# Visit: http://localhost:15672/#/queues
# Look for messages in EMAIL_EXCHANGE queue
# Check application logs
tail -f suraksha/logs/spring.log
# Verify SMTP configuration
# Ensure email service consumer is runningSolutions:
- Verify RabbitMQ connection in
application.properties - Check SMTP credentials and server availability
- Ensure email consumer service is running
- Check firewall rules for SMTP port (usually 587 or 465)
Issue: Tokens not validating
Diagnosis:
# Check Redis for stored tokens
redis-cli -h localhost -p 6379 KEYS "*"
# Check specific OTP
redis-cli -h localhost -p 6379 GET "otp:[email protected]"
# Check token expiration
redis-cli -h localhost -p 6379 TTL "magic:hashedtoken"Solutions:
- Ensure Redis is running and accessible
- Verify token hasn't expired (10 min for magic URLs, 2-5 min for OTPs)
- Check application logs for token generation errors
- Ensure clock synchronization (tokens are time-sensitive)
Issue: RSA key format errors
Solutions:
# Ensure OpenSSL is installed
openssl version
# Use PKCS#8 format (required by Java)
openssl pkcs8 -topk8 -inform PEM -outform PEM \
-nocrypt -in private.key -out private_pkcs8.key
# Verify key format
openssl rsa -in private_pkcs8.key -check
# Test key pair match
openssl rsa -in private.key -pubout | diff - public.keyIssue: JWT verification failing
Diagnosis:
- Check JWT structure:
jwt.io - Verify signature algorithm (should be RS256)
- Check token expiration
Solutions:
- Ensure public key matches private key used for signing
- Verify JWT hasn't expired (15-minute default)
- Check refresh token exists in Redis
- Ensure system clocks are synchronized
- Verify
rsa.public-keyinapplication.propertiesis correct
Issue: High memory usage or slow responses
Solutions:
# Check Java heap size
java -XX:+PrintFlagsFinal -version | grep HeapSize
# Increase heap size
export MAVEN_OPTS="-Xmx2048m -Xms512m"
./mvnw spring-boot:run
# Monitor JVM
jstat -gc <pid> 1000
# Check database connections
# Adjust in application.properties:
spring.datasource.hikari.maximum-pool-size=10
# Monitor Redis memory
redis-cli -h localhost -p 6379 INFO memoryContributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License.
Aneesh Patne
For questions, issues, or feature requests, please open an issue on GitHub.
- OAuth2/OIDC integration
- Social login (Google, GitHub, etc.)
- Rate limiting and brute force protection
- Session management UI
- Admin dashboard
- Audit log system
- Webhook support for authentication events
- Multi-region deployment guide
- Kubernetes deployment manifests
- Prometheus metrics integration
Built with ❤️ using Spring Boot