Skip to content

Repository files navigation

SSO Portal β€” Next.js + PostgreSQL

Sistem Single Sign-On (SSO) enterprise dengan fitur multi-account, role per aplikasi, auto-refresh token, dan tema Technology light/dark.


πŸš€ Quick Start

1. Prerequisites

  • Node.js 18+
  • PostgreSQL 14+
  • npm / yarn / pnpm

2. Install dependencies

npm install

3. Setup environment

cp .env.local.example .env.local
# Edit .env.local dengan kredensial database dan secret JWT Anda

Generate JWT secrets yang kuat:

node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"

4. Setup database

# 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:seed

5. Run development server

npm run dev

Buka http://localhost:3000


πŸ” Default Credentials

Username : admin
Password : Admin@SSO2024!

πŸ“ Project Structure

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

πŸ”‘ Token Security Architecture

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

Auto-refresh flow:

  • 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

πŸ‘€ Multi-Account System (Google-style)

  • 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

🎭 Role-per-Application System

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.


🌐 Integrasi Aplikasi Lain (OAuth2 / JWT)

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

πŸ–₯️ Computer Name / IP Fingerprinting

Cara kerja:

  1. IP Address: diambil dari X-Forwarded-For / X-Real-IP header
  2. Computer Name: dikirim client via header X-Computer-Name
  3. User Agent: dari browser

Fingerprint = SHA256(IP + computerName + userAgent_prefix)

Mode:

  • relaxed (default): fingerprint disimpan sebagai audit trail, tidak memblokir login
  • strict: fingerprint harus cocok persis (bisa false-positive untuk IP dinamis/VPN)

Cara kirim computer name dari browser:

// 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
})

πŸ“¦ Deployment

Docker Compose

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:

Dockerfile

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"]

πŸ”’ Security Checklist

  • 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)

πŸ› οΈ Available Scripts

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

About

SSO Multi Account, Multi Access, Multi Roles (Do Not Copy until Release)

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages