Sistem Single Sign-On (SSO) enterprise dengan fitur multi-account, role per aplikasi, auto-refresh token, dan tema Technology light/dark.
- Node.js 18+
- PostgreSQL 14+
- npm / yarn / pnpm
npm installcp .env.local.example .env.local
# Edit .env.local dengan kredensial database dan secret JWT AndaGenerate JWT secrets yang kuat:
node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"# Buat database PostgreSQL
createdb sso_db
# Push schema & generate client
npx prisma migrate dev --name init
# Seed data awal (admin user + sample apps)
npm run db:seednpm run devUsername : admin
Password : Admin@SSO2024!
src/
βββ app/
β βββ api/
β β βββ auth/
β β β βββ login/ POST β Login username/NIK
β β β βββ register/ POST β Register (pending approval)
β β β βββ refresh/ POST β Auto-refresh access token
β β β βββ logout/ POST β Logout one/all sessions
β β β βββ me/ GET β Current user + app roles
β β β βββ sessions/ GET/DELETE β Session management
β β βββ admin/
β β βββ users/ GET/POST β User management
β β β βββ [id]/
β β β βββ approve/ POST β Approve/reject registration
β β β βββ roles/ PUT/DELETE β Assign app roles
β β βββ apps/ GET/POST β Application management
β β β βββ [id]/roles/ GET/POST β Roles per app
β β βββ roles/ SSO global roles
β βββ login/ Login page
β βββ dashboard/ User dashboard
β β βββ apps/ My applications
β β βββ sessions/ Active sessions
β βββ admin/
β βββ users/ User management
β βββ apps/ Application management
β βββ roles/ Role management
βββ components/
β βββ layout/
β β βββ ThemeProvider.tsx Light/dark theme
β β βββ DashboardLayout.tsx Sidebar layout
β βββ ui/
β β βββ ThemeToggle.tsx
β βββ dashboard/
β β βββ DashboardHome.tsx
β β βββ SessionsClient.tsx
β βββ admin/
β βββ AdminUsersClient.tsx
β βββ AdminAppsClient.tsx
β βββ AdminRolesClient.tsx
βββ lib/
β βββ prisma.ts DB client singleton
β βββ auth.ts Login, logout, refresh logic
β βββ token.ts JWT sign/verify + fingerprinting
β βββ accounts.ts Multi-account localStorage manager
β βββ useAutoRefresh.ts Auto-refresh hook
βββ middleware.ts Route protection + auth injection
Login Request
β
Server validates credentials
β
Creates: Session record (DB) + Access Token (JWT 15min) + Refresh Token (JWT 7d)
β
Response:
- Access Token β httpOnly Cookie (inaccessible to JS)
- Refresh Token β httpOnly Cookie (path=/api/auth/refresh only)
- User data β Response body (no tokens)
β
Client stores only account metadata in localStorage (name, email, avatar)
NO tokens in localStorage/sessionStorage
- Every 14 minutes, client calls
/api/auth/refresh - Old refresh token is immediately revoked (token rotation)
- New access + refresh tokens are issued
- On tab focus after >60s hidden: refresh triggered automatically
- Login dengan beberapa akun
- Saved accounts tampil di login screen (seperti Google Account Picker)
- Switch account dengan satu klik (tetap perlu password)
- Remove individual account dari saved list
- Logout satu session tanpa mempengaruhi sesi lain
Satu user SSO bisa memiliki role berbeda di setiap aplikasi:
| User | ERP System | CRM System | HRIS |
|---|---|---|---|
| Alice | Admin | Developer | Viewer |
| Bob | Viewer | Supervisor | Manager |
Role di-encode dalam JWT token. Aplikasi klien cukup verifikasi token
dan baca field appRoles untuk mengetahui permission user.
Untuk aplikasi klien yang ingin memverifikasi token SSO:
// Di aplikasi klien β verifikasi token
const response = await fetch('https://sso.yourcompany.com/api/auth/me', {
headers: { 'Authorization': `Bearer ${accessToken}` }
})
const { data: user } = await response.json()
// user.appRoles berisi role user di aplikasi Anda
const myAppRole = user.appRoles.find(r => r.appSlug === 'your-app-slug')
console.log(myAppRole?.roleName) // 'manager', 'developer', dll- IP Address: diambil dari
X-Forwarded-For/X-Real-IPheader - Computer Name: dikirim client via header
X-Computer-Name - User Agent: dari browser
Fingerprint = SHA256(IP + computerName + userAgent_prefix)
relaxed(default): fingerprint disimpan sebagai audit trail, tidak memblokir loginstrict: fingerprint harus cocok persis (bisa false-positive untuk IP dinamis/VPN)
// Browser tidak bisa akses nama komputer secara native.
// Gunakan salah satu:
// Option 1: User-input saat login
const computerName = prompt('Device name:') || navigator.platform
// Option 2: Electron app (akses OS)
const { hostname } = require('os')
fetch('/api/auth/login', {
headers: { 'X-Computer-Name': hostname() }
})
// Option 3: Chrome Extension
chrome.system.network.getNetworkInterfaces(interfaces => {
// ambil hostname
})version: '3.8'
services:
db:
image: postgres:16
environment:
POSTGRES_DB: sso_db
POSTGRES_USER: sso_user
POSTGRES_PASSWORD: your_password
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://sso_user:your_password@db:5432/sso_db
JWT_ACCESS_SECRET: your_secret_here
JWT_REFRESH_SECRET: your_refresh_secret_here
depends_on:
- db
volumes:
pgdata:FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx prisma generate
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]- Tokens disimpan di httpOnly cookies (tidak accessible via JS)
- Refresh token cookie path dibatasi ke
/api/auth/refresh - Token rotation β refresh token langsung direvoke setelah digunakan
- Device fingerprinting (IP + computer name + user agent)
- Bcrypt password hashing (cost factor 12)
- Registration approval workflow
- Session management per device
- Login history audit trail
- Rate limiting recommended (tambahkan upstash/redis)
- Email notifikasi login baru (tambahkan nodemailer/resend)
- 2FA/TOTP (tambahkan otplib)
- CSRF protection (sudah di-handle Next.js via SameSite cookie)
| Script | Description |
|---|---|
npm run dev |
Start development server |
npm run build |
Build for production |
npm run start |
Start production server |
npm run db:generate |
Regenerate Prisma client |
npm run db:push |
Push schema changes |
npm run db:migrate |
Run migrations |
npm run db:seed |
Seed initial data |
npm run db:studio |
Open Prisma Studio |