authkit-js
Version:
Express auth toolkit (JWT, Sessions with Redis, Google/GitHub OAuth) in JavaScript
316 lines (238 loc) โข 9.34 kB
Markdown
# AuthKit-JS (Express) โ JWT, Sessions (Redis), Google/GitHub OAuth
A simple, pluggable **authentication library** for **Express.js** applications. Designed to support multiple strategies:
* ๐ **JWT Authentication**
* ๐ **Session Authentication** (Memory / Redis store)
* ๐ **Google OAuth2**
* ๐ **GitHub OAuth2**
* ๐ **Facebook OAuth2**
* ๏ฃฟ **Apple Sign In (OAuth/OIDC)**
* ๐ Middleware helpers (`makeAuthenticate`, `requireAuth`)
* ๐ข Built-in **TOTP 2FA utilities** (secret + QR generation, token verify)
## โจ Features
- Drop-in router: `makeAuthRouter` with `/auth/me`, JWT refresh/logout, session logout, and OAuth callbacks
- `attachAuth` middleware: auto-populates `req.auth` without failing your routes
- JWT and Session strategies with env-aware, secure-by-default cookies
- Optional JWT refresh rotation with reuse detection via pluggable refresh stores (Memory/Redis)
- Lightweight CORS (`makeCors`) and rate limit (`makeRateLimit`) middlewares
- Guards: `requireAuthGuard`, `requireRole`, `requirePermission`
- Standard error helpers with codes: `Errors`
- TypeScript declarations included
## ๐ฆ Installation
```bash
npm install authkit-js
# Required peer dependencies
npm install express cookie-parser jsonwebtoken
# Optional (when you use them)
npm install ioredis google-auth-library node-fetch
```
2FA helpers (`TwoFA`) are included out-of-the-box; no extra install is required.
## โก Quick Usage
```js
const express = require('express');
const cookieParser = require('cookie-parser');
const {
AuthKit, makeAuthRouter, attachAuth,
makeCors, makeRateLimit,
} = require('authkit-js');
const { MemorySessionStore } = require('authkit-js/lib/stores/memory');
const app = express();
app.use(express.json());
app.use(cookieParser());
// CORS (optional)
app.use(makeCors({ origin: ['http://localhost:5173'] }));
// Init authkit
const kit = new AuthKit({
jwt: { secret: 'super-secret' },
session: { store: new MemorySessionStore() },
});
// Attach auth context on every request (non-fatal)
app.use(attachAuth(kit));
// Auth router with simple rate limit (optional)
app.use('/auth', makeRateLimit({ capacity: 100, intervalMs: 60_000 }), makeAuthRouter(kit));
// Your login endpoint (example)
app.post('/login', async (req, res) => {
const user = { id: 1, email: req.body.email };
await kit.jwt.login(res, user);
res.json({ ok: true });
});
// Protected route
app.get('/me', async (req, res) => {
const ctx = await kit.authenticate(req);
if (!ctx || !ctx.user) return res.status(401).json({ error: 'Unauthorized' });
res.json({ user: ctx.user });
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
```
## ๐ Social Login Setup
### Google OAuth2
```js
const { GoogleOAuthStrategy } = require('authkit-js/lib/strategies/oauth-google');
const kit = new AuthKit({
google: new GoogleOAuthStrategy({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: process.env.GOOGLE_REDIRECT_URI,
verify: async (profile) => ({ id: profile.sub, email: profile.email }),
})
});
```
### GitHub OAuth2
### Facebook OAuth2
```js
const { FacebookOAuthStrategy } = require('authkit-js/lib/strategies/oauth-facebook');
const kit = new AuthKit({
facebook: new FacebookOAuthStrategy({
clientId: process.env.FB_CLIENT_ID,
clientSecret: process.env.FB_CLIENT_SECRET,
redirectUri: process.env.FB_REDIRECT_URI,
verify: async (profile) => ({ id: profile.id, name: profile.name, email: profile.email }),
})
});
```
### Apple Sign In (OAuth/OIDC)
```js
const { AppleOAuthStrategy } = require('authkit-js/lib/strategies/oauth-apple');
const kit = new AuthKit({
apple: new AppleOAuthStrategy({
clientId: process.env.APPLE_CLIENT_ID,
// For production, generate a JWT client secret signed with your Apple key
clientSecret: process.env.APPLE_CLIENT_SECRET,
redirectUri: process.env.APPLE_REDIRECT_URI,
verify: async (payload) => ({ id: payload.sub, email: payload.email }),
})
});
```
## ๐ฆ Built-in Router Endpoints
- `GET /auth/me` โ returns `{ user }` when authenticated
- `POST /auth/jwt/refresh` โ refreshes JWT using refresh cookie
- `POST /auth/jwt/logout` โ clears JWT cookies
- `POST /auth/session/logout` โ clears session and CSRF cookies and deletes session
- `GET /auth/oauth/google` and `/auth/oauth/google/callback`
- `GET /auth/oauth/github` and `/auth/oauth/github/callback`
- `GET /auth/oauth/facebook` and `/auth/oauth/facebook/callback`
- `GET /auth/oauth/apple` and `/auth/oauth/apple/callback`
Note: You still implement your own login that calls `kit.jwt.login(res, user)` or `kit.session.login(res, user)`.
## ๐ข Two-Factor Authentication (TOTP) โ Built-in Helpers
Use the `TwoFA` helper to generate a TOTP secret, show a QR code, and verify tokens during login.
```js
const { TwoFA } = require('authkit-js');
// 1) Start 2FA setup โ generate a secret and a QR code Data URL
app.get('/2fa/setup', async (req, res) => {
const username = String(req.query.username);
const secret = TwoFA.generateSecret(`MyApp (${username})`); // { base32, otpauth_url, ... }
const qr = await TwoFA.generateQRCodeDataURL(secret.otpauth_url);
// Persist `secret.base32` temporarily until user verifies a token
res.json({ secret: secret.base32, qr });
});
// 2) Enable 2FA โ verify a token from user's authenticator app
app.post('/2fa/enable', (req, res) => {
const { secretBase32, token } = req.body;
const ok = TwoFA.verifyToken(secretBase32, token, 1); // window=1 for slight clock drift
if (!ok) return res.status(400).json({ error: 'Invalid token' });
// Mark 2FA enabled for the user, persist secretBase32
res.json({ ok: true });
});
// 3) During login โ require a valid TOTP if the user has 2FA enabled
app.post('/login', async (req, res) => {
const { username, password, twoFactorToken } = req.body;
const user = await findUser(username);
if (!user || !(await checkPassword(user, password))) return res.status(401).json({ error: 'Invalid credentials' });
if (user.twoFAEnabled) {
const ok = TwoFA.verifyToken(user.twoFASecretBase32, twoFactorToken || '', 1);
if (!ok) return res.status(401).json({ error: 'Invalid 2FA token' });
}
await kit.jwt.login(res, { id: user.id, name: user.name });
res.json({ ok: true });
});
```
These helpers are strategy-agnostic and work with both JWT and Session flows.
## ๐ Refresh Token Rotation (JWT)
Enable rotation + reuse detection by providing a refresh store:
```js
const { MemoryRefreshStore } = require('authkit-js');
const kit = new AuthKit({
jwt: {
secret: process.env.JWT_SECRET,
refreshStore: new MemoryRefreshStore(),
cookie: { env: process.env.NODE_ENV, crossSite: true }
}
});
```
Use `RedisRefreshStore` for multi-instance deployments.
## ๐ก Guards & Errors
```js
const { attachAuth, requireAuthGuard, requireRole, requirePermission, Errors } = require('authkit-js');
app.use(attachAuth(kit));
app.get('/admin', requireAuthGuard(), requireRole('admin'), (req, res) => res.send('ok'));
app.use((err, _req, res, _next) => res.status(err.status || 500).json({ code: err.code, error: err.message }));
```
## ๐ CORS & Rate Limit
```js
const { makeCors, makeRateLimit } = require('authkit-js');
// Global CORS
app.use(makeCors({ origin: ['https://app.example.com'] }));
// Basic rate limit (per IP)
app.use('/auth', makeRateLimit({ capacity: 100, intervalMs: 60_000 }));
```
## ๐ช Environment-Aware Cookies
Cookies default to `httpOnly` and:
- `secure=true` in production (or when `crossSite=true`)
- `sameSite='lax'` by default, or `'none'` when `crossSite=true`
Configure per strategy:
```js
new AuthKit({
jwt: {
secret: process.env.JWT_SECRET,
cookie: { env: process.env.NODE_ENV, crossSite: true }
},
session: {
store: new MemorySessionStore(),
cookie: { env: process.env.NODE_ENV, crossSite: false }
}
});
```
```js
const { GitHubOAuthStrategy } = require('authkit-js/lib/strategies/oauth-github');
const kit = new AuthKit({
github: new GitHubOAuthStrategy({
clientId: process.env.GH_CLIENT_ID,
clientSecret: process.env.GH_CLIENT_SECRET,
redirectUri: process.env.GH_REDIRECT_URI,
verify: async (profile) => ({ id: profile.id, login: profile.login }),
})
});
```
## ๐ Redis Session Store
```js
const Redis = require('ioredis');
const { RedisSessionStore } = require('authkit-js/lib/stores/redis');
const redis = new Redis(process.env.REDIS_URL);
const kit = new AuthKit({
session: { store: new RedisSessionStore(redis) }
});
```
## ๐งช Example App
A full working demo is in `examples/express-server/`:
```bash
cd examples/express-server
cp .env.example .env
node index.js
```
Routes:
* `GET /auth/google` โ Google login
* `GET /auth/github` โ GitHub login
* `GET /me` โ Protected profile route
## ๐ License
MIT ยฉ 2025 Shashi