sikaku-auth
v1.0.1
Published
OAuth 2.1 + PKCE (S256) authentication middleware for Node.js — secure, zero-dependency
Maintainers
Readme
sikaku-oauth
OAuth 2.1 + PKCE (S256) authentication module untuk SiKaKu — profesional, aman, bergaya Passport.js.
Fitur
- ✅ OAuth 2.1 + PKCE (S256) — standar keamanan terbaru
- 🔒 Zero credential leak —
clientSecretdisimpan diWeakMap+Symbol, tidak bisa diakses dari luar - ♻️ Auto-refresh token — middleware
protect()otomatis refresh token yang expired - 🧩 Pluggable store — default InMemoryStore, bisa diganti Redis/DB custom
- 📡 EventEmitter — observability via events (
auth_success,token_refreshed, dll.) - 🔷 TypeScript ready — file
.d.tstersedia - ⚡ Zero external dependency — hanya menggunakan Node.js built-in (
crypto,https)
Instalasi
# Salin module ini ke project Anda, atau tambahkan sebagai dependency lokal
npm install express express-sessionPenggunaan Cepat
1. Setup
const express = require('express');
const session = require('express-session');
const SikakuOAuth = require('sikaku-oauth');
const app = express();
// Wajib: express-session sebelum SikakuOAuth
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: true, httpOnly: true, sameSite: 'lax' },
}));
// Inisialisasi OAuth
const oauth = new SikakuOAuth({
clientId: process.env.SIKAKU_CLIENT_ID,
clientSecret: process.env.SIKAKU_CLIENT_SECRET,
redirectUri: process.env.SIKAKU_REDIRECT_URI,
scope: 'openid profile', // default
});2. Routes
// Redirect ke halaman login SiKaKu
app.get('/login', oauth.authenticate());
// Handle callback (setelah user login di SiKaKu)
app.get('/callback', oauth.callback(), (req, res) => {
// req.sikakuToken berisi { access_token, refresh_token, expires_at, ... }
res.redirect('/dashboard');
});
// Proteksi route — redirect ke /login jika tidak auth
// Token di-refresh otomatis jika expired
app.get('/dashboard', oauth.protect(), (req, res) => {
res.json({ token: req.sikakuToken });
});
// Logout
app.get('/logout', oauth.logout({ redirectTo: '/' }));Konfigurasi Lengkap
const oauth = new SikakuOAuth({
// === WAJIB ===
clientId: 'Sikaku-oauth.1854c4ba99b83d60a1f5a1b51f95948a',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'https://domain.dev/callback',
// === OPSIONAL ===
scope: 'openid profile', // default
sessionKey: 'sikaku_oauth', // key di session
authorizeUrl: 'https://api.sikaku.dev/oauth/authorize', // url api sikaku
tokenUrl: 'https://api.sikaku.dev/oauth/token', // url token sikaku
// Custom store (lihat seksi Pluggable Store)
store: myRedisStore,
// Callbacks opsional
onSuccess: (req, res, next, token) => res.redirect('/dashboard'),
onError: (err, req, res, next) => res.redirect('/error'),
});Middleware API
oauth.authenticate([options])
Redirect user ke halaman login SiKaKu. Otomatis:
- Generate PKCE
code_verifier+code_challenge(S256) - Generate CSRF
statetoken - Simpan keduanya di session (server-side only)
app.get('/login', oauth.authenticate({
scope: 'openid profile email', // override scope
}));oauth.callback([options])
Handle redirect callback dari SiKaKu. Otomatis:
- Validasi CSRF
state(timing-safe) - Tukar
codedengan token via POST ke token endpoint - Simpan token di store
- Set
req.sikakuTokenuntuk handler berikutnya
app.get('/callback', oauth.callback(), (req, res) => {
console.log(req.sikakuToken.access_token);
res.redirect('/dashboard');
});oauth.protect([options])
Proteksi route — redirect ke login jika tidak terautentikasi. Otomatis:
- Cek token di session/store
- Auto-refresh jika token expired (menggunakan
refresh_token) - Set
req.sikakuTokenjika valid
app.get('/api/data', oauth.protect({ redirectTo: '/login' }), handler);oauth.logout([options])
Hapus session dan token dari store.
app.get('/logout', oauth.logout({ redirectTo: '/' }));await oauth.isAuthenticated(req)
Cek status autentikasi tanpa redirect.
const isAuth = await oauth.isAuthenticated(req);await oauth.getToken(req)
Ambil token data secara langsung dari store.
const token = await oauth.getToken(req);
// { access_token, refresh_token, expires_in, token_type, expires_at }Events
oauth.on('auth_success', ({ tokenKey }) => { /* ... */ });
oauth.on('auth_fail', ({ error }) => { /* ... */ });
oauth.on('token_refreshed', ({ tokenKey }) => { /* ... */ });
oauth.on('token_refresh_failed',({ error }) => { /* ... */ });
oauth.on('authorize_redirect', ({ url }) => { /* ... */ });
oauth.on('logout', () => { /* ... */ });
oauth.on('error', (err) => { /* ... */ });Pluggable Store
Default menggunakan InMemoryStore. Untuk production (multi-instance), gunakan custom store:
const { validateStoreInterface } = require('sikaku-oauth');
// Custom store (contoh: Redis)
const redisStore = {
async set(key, data, ttlSeconds) { await redis.setex(key, ttlSeconds, JSON.stringify(data)); },
async get(key) { const d = await redis.get(key); return d ? JSON.parse(d) : null; },
async delete(key) { await redis.del(key); },
async has(key) { return (await redis.exists(key)) === 1; },
};
// Validasi interface
validateStoreInterface(redisStore);
const oauth = new SikakuOAuth({
// ...
store: redisStore,
});Keamanan
| Mekanisme | Implementasi |
|-----------|-------------|
| clientSecret protection | WeakMap + Symbol — tidak bisa diakses dari luar instance |
| CSRF protection | State token + crypto.timingSafeEqual() |
| PKCE | S256: BASE64URL(SHA256(verifier)) |
| Token storage | Server-side store, bukan di cookie client |
| Immutability | Object.freeze() pada semua token data |
| Session security | httpOnly, sameSite, secure cookie |
Environment Variables
Salin .env.example → .env:
SIKAKU_CLIENT_ID=Sikaku-oauth.1854c4ba99b83d60a1f5a1b51f95948a
SIKAKU_CLIENT_SECRET=YOUR_CLIENT_SECRET_HERE
SIKAKU_REDIRECT_URI=https://domain.dev/callback
SESSION_SECRET=random-long-stringMenjalankan Tests
npm test # Run + coverage report
npm run test:watch # Watch mode
npm run test:verboseError Classes
const {
SikakuAuthError, // Base error
StateValidationError, // CSRF state mismatch
TokenExchangeError, // Gagal tukar code → token
TokenExpiredError, // Token expired, tidak ada refresh_token
TokenRefreshError, // Gagal refresh token
ConfigurationError, // Konfigurasi tidak valid
UnauthorizedError, // Tidak terautentikasi
} = require('sikaku-oauth');
app.use((err, req, res, next) => {
if (err instanceof StateValidationError) {
return res.status(403).json({ error: 'CSRF detected' });
}
if (err instanceof TokenExpiredError) {
return res.redirect('/login');
}
next(err);
});Contoh Lengkap
Lihat examples/express-basic/index.js
cd examples/express-basic
npm install express express-session dotenv
node index.js
# Buka http://localhost:3000