SWC Authentication: JWT, Sessions, and OAuth Integration
Authentication is one of the most critical components of any modern web application. When building applications with SWC (Speedy Web Compiler) as your TypeScript/JavaScript transpilation layer, you gain significant performance benefits during development and build times. However, authentication logic itself runs at runtime, and implementing it correctly requires a solid understanding of JWT, session-based auth, and OAuth flows. This tutorial walks you through all three approaches in a single, cohesive application.
What Is SWC Authentication?
SWC is an extensible Rust-based platform for compilation, bundling, and minification. While SWC itself is a build-time tool, "SWC authentication" refers to the patterns and practices for implementing authentication in applications that rely on SWC for transpilation — typically Node.js backends, Next.js applications, or custom server setups. The authentication layer operates independently of the compiler, but leveraging SWC's fast transforms allows you to write modern TypeScript with decorators, async/await, and latest ECMAScript features without build-time penalties.
In this tutorial, we will build an Express.js API server — transpiled with SWC via @swc-node/register or @swc/core — that supports three authentication strategies: stateless JWT tokens, server-side sessions, and third-party OAuth integration.
Why It Matters
- Performance: SWC compiles up to 20x faster than Babel, meaning faster CI/CD pipelines and quicker development feedback loops.
- Security: Choosing the right authentication strategy (JWT vs. sessions vs. OAuth) directly impacts your application's security posture and scalability.
- Developer Experience: Writing authentication logic in TypeScript with SWC gives you type safety and modern syntax without sacrificing build speed.
- Flexibility: Many real-world applications need a combination of all three strategies — for example, OAuth for login, sessions for web clients, and JWT for API consumers.
Project Setup
Let's start by setting up a Node.js project with SWC as the TypeScript compiler. Create a new directory and initialize the project:
mkdir swc-auth-tutorial
cd swc-auth-tutorial
npm init -y
npm install express cors cookie-parser dotenv
npm install jsonwebtoken express-session bcryptjs
npm install passport passport-google-oauth20 passport-github2
npm install -D @swc/core @swc-node/register typescript @types/express @types/node @types/jsonwebtoken @types/express-session @types/cookie-parser @types/bcryptjs @types/passport @types/passport-google-oauth20 @types/passport-github2
Create a .swcrc configuration file at the project root:
{
"$schema": "https://swc.rs/schema.json",
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"legacyDecorator": true
},
"target": "es2020",
"loose": false
},
"module": {
"type": "commonjs"
},
"minify": false
}
Create a tsconfig.json for type checking (SWC handles transpilation, TypeScript handles types):
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"types": ["node"]
},
"include": ["src/**/*"]
}
Add scripts to your package.json:
{
"scripts": {
"dev": "node -r @swc-node/register src/index.ts",
"build": "swc src -d dist",
"start": "node dist/index.js",
"typecheck": "tsc --noEmit"
}
}
Application Structure
Our project will follow this directory structure:
src/
├── index.ts
├── config.ts
├── middleware/
│ ├── auth.ts
│ └── error.ts
├── strategies/
│ ├── jwt.strategy.ts
│ ├── session.strategy.ts
│ └── oauth.strategy.ts
├── routes/
│ ├── auth.routes.ts
│ └── protected.routes.ts
└── types/
└── express.d.ts
Configuration and Environment
Create src/config.ts to centralize configuration:
import dotenv from 'dotenv';
dotenv.config();
export const config = {
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
// JWT configuration
jwt: {
secret: process.env.JWT_SECRET || 'change-this-in-production',
accessExpiry: '15m',
refreshExpiry: '7d',
refreshSecret: process.env.JWT_REFRESH_SECRET || 'refresh-secret-change-me',
issuer: 'swc-auth-tutorial',
audience: 'swc-auth-users',
},
// Session configuration
session: {
secret: process.env.SESSION_SECRET || 'session-secret-change-me',
name: 'swc.sid',
maxAge: 1000 * 60 * 60 * 24, // 24 hours
secure: process.env.NODE_ENV === 'production',
},
// OAuth configuration
oauth: {
google: {
clientID: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
callbackURL: '/auth/google/callback',
},
github: {
clientID: process.env.GITHUB_CLIENT_ID || '',
clientSecret: process.env.GITHUB_CLIENT_SECRET || '',
callbackURL: '/auth/github/callback',
},
},
// CORS
cors: {
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
credentials: true,
},
} as const;
Create a .env file (do not commit this to version control):
PORT=3000
NODE_ENV=development
JWT_SECRET=your-super-secret-jwt-key-here
JWT_REFRESH_SECRET=your-super-secret-refresh-key-here
SESSION_SECRET=your-session-secret-here
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
CORS_ORIGIN=http://localhost:5173
Extending Express Types
Create src/types/express.d.ts to augment Express with our custom user type:
import { Express } from 'express';
import { Session } from 'express-session';
declare module 'express' {
interface Request {
user?: AuthUser;
}
}
declare module 'express-session' {
interface Session {
userId?: string;
user?: AuthUser;
oauthState?: string;
}
}
export interface AuthUser {
id: string;
email: string;
name: string;
provider: 'local' | 'google' | 'github';
providerId?: string;
role: 'user' | 'admin';
}
export interface JwtPayload {
sub: string;
email: string;
name: string;
role: 'user' | 'admin';
iat?: number;
exp?: number;
iss?: string;
aud?: string;
}
JWT Authentication Strategy
JWT (JSON Web Tokens) provide a stateless authentication mechanism. The server issues a signed token after login, and the client sends it with each request. The server verifies the token without needing to look up session state in a database or memory store.
Create src/strategies/jwt.strategy.ts:
import jwt, { SignOptions, VerifyOptions } from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import { config } from '../config';
import type { AuthUser, JwtPayload } from '../types/express';
// In a real application, replace this with a database
const usersDB = new Map<string, { passwordHash: string; user: AuthUser }>();
export class JwtAuthService {
/**
* Register a new user with email and password
*/
async register(email: string, password: string, name: string): Promise<AuthUser> {
const existingUser = Array.from(usersDB.values()).find(
(entry) => entry.user.email === email
);
if (existingUser) {
throw new Error('User already exists with this email');
}
const passwordHash = await bcrypt.hash(password, 12);
const user: AuthUser = {
id: crypto.randomUUID(),
email,
name,
provider: 'local',
role: 'user',
};
usersDB.set(user.id, { passwordHash, user });
return user;
}
/**
* Login with email and password, returns access and refresh tokens
*/
async login(email: string, password: string): Promise<{
user: AuthUser;
accessToken: string;
refreshToken: string;
}> {
const entry = Array.from(usersDB.values()).find(
(e) => e.user.email === email
);
if (!entry) {
throw new Error('Invalid credentials');
}
const valid = await bcrypt.compare(password, entry.passwordHash);
if (!valid) {
throw new Error('Invalid credentials');
}
const accessToken = this.generateAccessToken(entry.user);
const refreshToken = this.generateRefreshToken(entry.user);
return { user: entry.user, accessToken, refreshToken };
}
/**
* Generate a short-lived access token
*/
generateAccessToken(user: AuthUser): string {
const payload: Omit<JwtPayload, 'iat' | 'exp'> = {
sub: user.id,
email: user.email,
name: user.name,
role: user.role,
};
const signOptions: SignOptions = {
expiresIn: config.jwt.accessExpiry,
issuer: config.jwt.issuer,
audience: config.jwt.audience,
};
return jwt.sign(payload, config.jwt.secret, signOptions);
}
/**
* Generate a long-lived refresh token
*/
generateRefreshToken(user: AuthUser): string {
const payload = { sub: user.id, type: 'refresh' };
const signOptions: SignOptions = {
expiresIn: config.jwt.refreshExpiry,
issuer: config.jwt.issuer,
audience: config.jwt.audience,
};
return jwt.sign(payload, config.jwt.refreshSecret, signOptions);
}
/**
* Verify an access token and return the decoded payload
*/
verifyAccessToken(token: string): JwtPayload {
const verifyOptions: VerifyOptions = {
issuer: config.jwt.issuer,
audience: config.jwt.audience,
};
return jwt.verify(token, config.jwt.secret, verifyOptions) as JwtPayload;
}
/**
* Verify a refresh token and issue a new access token
*/
refreshAccessToken(refreshToken: string): string {
const verifyOptions: VerifyOptions = {
issuer: config.jwt.issuer,
audience: config.jwt.audience,
};
const decoded = jwt.verify(
refreshToken,
config.jwt.refreshSecret,
verifyOptions
) as { sub: string; type: string };
if (decoded.type !== 'refresh') {
throw new Error('Invalid token type');
}
const entry = usersDB.get(decoded.sub);
if (!entry) {
throw new Error('User not found');
}
return this.generateAccessToken(entry.user);
}
/**
* Find or create a user from OAuth provider data
*/
findOrCreateUser(oauthUser: Partial<AuthUser>): AuthUser {
const existing = Array.from(usersDB.values()).find(
(e) =>
e.user.provider === oauthUser.provider &&
e.user.providerId === oauthUser.providerId
);
if (existing) {
return existing.user;
}
const user: AuthUser = {
id: crypto.randomUUID(),
email: oauthUser.email || '',
name: oauthUser.name || '',
provider: oauthUser.provider || 'local',
providerId: oauthUser.providerId,
role: 'user',
};
usersDB.set(user.id, { passwordHash: '', user });
return user;
}
}
export const jwtAuthService = new JwtAuthService();
Session-Based Authentication Strategy
Session-based authentication stores user state on the server (in memory, Redis, or a database) and sends a session ID to the client via a cookie. This is the traditional approach and works well for server-rendered applications.
Create src/strategies/session.strategy.ts:
import bcrypt from 'bcryptjs';
import { config } from '../config';
import type { AuthUser } from '../types/express';
import { jwtAuthService } from './jwt.strategy';
// Shared in-memory user store (in production, use a real database)
// We reuse the same store from JwtAuthService for simplicity
export class SessionAuthService {
/**
* Authenticate a user and store their info in the session
*/
async loginWithSession(
email: string,
password: string,
session: Express.Session
): Promise<AuthUser> {
// Reuse the JWT service's user lookup
const result = await jwtAuthService.login(email, password);
// Store minimal user info in the session
session.userId = result.user.id;
session.user = result.user;
return result.user;
}
/**
* Create a session for an OAuth-authenticated user
*/
createSessionForOAuthUser(user: AuthUser, session: Express.Session): void {
session.userId = user.id;
session.user = user;
}
/**
* Get the current user from the session
*/
getCurrentUser(session: Express.Session): AuthUser | null {
if (!session.userId || !session.user) {
return null;
}
return session.user;
}
/**
* Destroy the session (logout)
*/
destroySession(session: Express.Session): Promise<void> {
return new Promise((resolve, reject) => {
session.destroy((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
/**
* Register a new user (delegates to JWT service for user creation)
*/
async register(email: string, password: string, name: string): Promise<AuthUser> {
return jwtAuthService.register(email, password, name);
}
}
export const sessionAuthService = new SessionAuthService();
OAuth Integration with Passport
OAuth allows users to authenticate using third-party providers like Google and GitHub. We use Passport.js strategies to handle the OAuth flow. After successful authentication, we can either create a JWT token or establish a server-side session.
Create src/strategies/oauth.strategy.ts:
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import { Strategy as GitHubStrategy } from 'passport-github2';
import { config } from '../config';
import { jwtAuthService } from './jwt.strategy';
import type { AuthUser } from '../types/express';
/**
* Configure Passport with Google and GitHub OAuth strategies
*/
export function configureOAuthStrategies(): void {
// Serialize user for session storage
passport.serializeUser((user: AuthUser, done) => {
done(null, user.id);
});
// Deserialize user from session
passport.deserializeUser((id: string, done) => {
// In production, look up user from database by ID
// For this tutorial, we store the full user in the session
done(null, null);
});
// Google OAuth Strategy
if (config.oauth.google.clientID && config.oauth.google.clientSecret) {
passport.use(
new GoogleStrategy(
{
clientID: config.oauth.google.clientID,
clientSecret: config.oauth.google.clientSecret,
callbackURL: config.oauth.google.callbackURL,
},
async (accessToken, refreshToken, profile, done) => {
try {
const user = jwtAuthService.findOrCreateUser({
email: profile.emails?.[0]?.value || '',
name: profile.displayName || profile.username || '',
provider: 'google',
providerId: profile.id,
});
return done(null, user);
} catch (error) {
return done(error as Error, undefined);
}
}
)
);
}
// GitHub OAuth Strategy
if (config.oauth.github.clientID && config.oauth.oauth.github.clientSecret) {
passport.use(
new GitHubStrategy(
{
clientID: config.oauth.github.clientID,
clientSecret: config.oauth.github.clientSecret,
callbackURL: config.oauth.github.callbackURL,
scope: ['user:email'],
},
async (accessToken: string, refreshToken: string, profile: any, done: any) => {
try {
const user = jwtAuthService.findOrCreateUser({
email: profile.emails?.[0]?.value || '',
name: profile.displayName || profile.username || '',
provider: 'github',
providerId: profile.id,
});
return done(null, user);
} catch (error) {
return done(error as Error, undefined);
}
}
)
);
}
}
export { passport };
Authentication Middleware
Create src/middleware/auth.ts to protect routes with either JWT or session authentication:
import { Request, Response, NextFunction } from 'express';
import { jwtAuthService } from '../strategies/jwt.strategy';
import { sessionAuthService } from '../strategies/session.strategy';
import type { AuthUser } from '../types/express';
/**
* Extract bearer token from Authorization header
*/
function extractBearerToken(req: Request): string | null {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return null;
}
return authHeader.substring(7);
}
/**
* Middleware that requires JWT authentication
*/
export function requireJwt(req: Request, res: Response, next: NextFunction): void {
const token = extractBearerToken(req);
if (!token) {
res.status(401).json({ error: 'No authentication token provided' });
return;
}
try {
const payload = jwtAuthService.verifyAccessToken(token);
req.user = {
id: payload.sub,
email: payload.email,
name: payload.name,
provider: 'local',
role: payload.role,
};
next();
} catch (error) {
res.status(401).json({ error: 'Invalid or expired token' });
}
}
/**
* Middleware that requires an active session
*/
export function requireSession(req: Request, res: Response, next: NextFunction): void {
const user = sessionAuthService.getCurrentUser(req.session);
if (!user) {
res.status(401).json({ error: 'No active session' });
return;
}
req.user = user;
next();
}
/**
* Middleware that accepts either JWT or session authentication
*/
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
// Try JWT first
const token = extractBearerToken(req);
if (token) {
try {
const payload = jwtAuthService.verifyAccessToken(token);
req.user = {
id: payload.sub,
email: payload.email,
name: payload.name,
provider: 'local',
role: payload.role,
};
next();
return;
} catch {
// Token invalid, fall through to session check
}
}
// Try session
const user = sessionAuthService.getCurrentUser(req.session);
if (user) {
req.user = user;
next();
return;
}
res.status(401).json({ error: 'Authentication required' });
}
/**
* Middleware that requires admin role
*/
export function requireAdmin(req: Request, res: Response, next: NextFunction): void {
if (!req.user) {
res.status(401).json({ error: 'Authentication required' });
return;
}
if (req.user.role !== 'admin') {
res.status(403).json({ error: 'Admin access required' });
return;
}
next();
}
Create src/middleware/error.ts for centralized error handling:
import { Request, Response, NextFunction } from 'express';
export function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
): void {
console.error(`[ERROR] ${err.message}`, err.stack);
if (err.message.includes('Invalid credentials') || err.message.includes('already exists')) {
res.status(400).json({ error: err.message });
return;
}
res.status(500).json({ error: 'Internal server error' });
}
Auth Routes
Create src/routes/auth.routes.ts with endpoints for all three authentication strategies:
import { Router, Request, Response, NextFunction } from 'express';
import { jwtAuthService } from '../strategies/jwt.strategy';
import { sessionAuthService } from '../strategies/session.strategy';
import { passport } from '../strategies/oauth.strategy';
import { requireAuth, requireAdmin } from '../middleware/auth';
const router = Router();
// ============================================
// JWT Authentication Routes
// ============================================
router.post('/jwt/register', async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password, name } = req.body;
if (!email || !password || !name) {
res.status(400).json({ error: 'email, password, and name are required' });
return;
}
const user = await jwtAuthService.register(email, password, name);
res.status(201).json({ message: 'User registered', user: { id: user.id, email: user.email, name: user.name } });
} catch (err) {
next(err);
}
});
router.post('/jwt/login', async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
if (!email || !password) {
res.status(400).json({ error: 'email and password are required' });
return;
}
const { user, accessToken, refreshToken } = await jwtAuthService.login(email, password);
res.json({
message: 'Login successful',
user: { id: user.id, email: user.email, name: user.name, role: user.role },
accessToken,
refreshToken,
});
} catch (err) {
next(err);
}
});
router.post('/jwt/refresh', (req: Request, res: Response, next: NextFunction) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
res.status(400).json({ error: 'refreshToken is required' });
return;
}
const accessToken = jwtAuthService.refreshAccessToken(refreshToken);
res.json({ accessToken });
} catch (err) {
res.status(401).json({ error: 'Invalid refresh token' });
}
});
// ============================================
// Session Authentication Routes
// ============================================
router.post('/session/register', async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password, name } = req.body;
if (!email || !password || !name) {
res.status(400).json({ error: 'email, password, and name are required' });
return;
}
const user = await sessionAuthService.register(email, password, name);
res.status(201).json({ message: 'User registered', user: { id: user.id, email: user.email, name: user.name } });
} catch (err) {
next(err);
}
});
router.post('/session/login', async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
if (!email || !password) {
res.status(400).json({ error: 'email and password are required' });
return;
}
const user = await sessionAuthService.loginWithSession(email, password, req.session);
res.json({ message: 'Login successful', user: { id: user.id, email: user.email, name: user.name, role: user.role } });
} catch (err) {
next(err);
}
});
router.post('/session/logout', (req: Request, res: Response, next: NextFunction) => {
sessionAuthService
.destroySession(req.session)
.then(() => {
res.clearCookie('swc.sid');
res.json({ message: 'Logged out successfully' });
})
.catch(next);
});
router.get('/session/me', (req: Request, res: Response) => {
const user = sessionAuthService.getCurrentUser(req.session);
if (!user) {
res.status(401).json({ error: 'Not authenticated' });
return;
}
res.json({ user });
});
// ============================================
// OAuth Routes (Google)
// ============================================
router.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
router.get(
'/google/callback',
passport.authenticate('google', { session: false, failureRedirect: '/login?error=oauth_failed' }),
(req: Request, res: Response) => {
const user = req.user as any;
if (!user) {
res.redirect('/login?error=no_user');
return;
}
// Option A: Create a session for the OAuth user
sessionAuthService.createSessionForOAuthUser(user, req.session);
// Option B: Issue JWT tokens for API access
const accessToken = jwtAuthService.generateAccessToken(user);
const refreshToken = jwtAuthService.generateRefreshToken(user);
// Redirect with tokens (in production, use secure httpOnly cookies)
res.redirect(`/auth/success?token=${accessToken}&refresh=${refreshToken}`);
}
);
// ============================================
// OAuth Routes (GitHub)
// ============================================
router.get('/github', passport.authenticate('github', { scope: ['user:email'] }));
router.get(
'/github/callback',
passport.authenticate('github', { session: false, failureRedirect: '/login?error=oauth_failed' }),
(req: Request, res: Response) => {
const user = req.user as any;
if (!user) {
res.redirect('/login?error=no_user');
return;
}
sessionAuthService.createSessionForOAuthUser(user, req.session);
const accessToken = jwtAuthService.generateAccessToken(user);
const refreshToken = jwtAuthService.generateRefreshToken(user);
res.redirect(`/auth/success?token=${accessToken}&refresh=${refreshToken}`);
}
);
// ============================================
// Shared Protected Route
// ============================================
router.get('/me', requireAuth, (req: Request, res: Response) => {
res.json({ user: req.user });
});
export default router;
Protected Routes
Create src/routes/protected.routes.ts to demonstrate protected endpoints:
import { Router, Request, Response } from 'express';
import { requireJwt, requireSession, requireAuth, requireAdmin } from '../middleware/auth';
const router = Router();
// JWT-only protected route (for API consumers, mobile apps, etc.)
router.get('/api/data', requireJwt, (req: Request, res: Response) => {
res.json({
message: 'This data is protected by JWT authentication',
user: req.user,
timestamp: new Date().toISOString(),
});
});
// Session-only protected route (for browser-based web apps)
router.get('/dashboard', requireSession, (req: Request, res: Response) => {
res.json({
message: 'Welcome to your dashboard (session-protected)',
user: req.user,
timestamp: new Date().toISOString(),
});
});
// Route accessible via either JWT or session
router.get('/profile', requireAuth, (req: Request, res: Response) => {
res.json({
message: 'Your profile (accessible via JWT or session)',
user: req.user,
});
});
// Admin-only route
router.get('/admin', requireAuth, requireAdmin, (req: Request, res: Response) => {
res.json({
message: 'Admin panel access granted',
user: req.user,
});
});
export default router;
Wiring It All Together
Create src/index.ts to bootstrap the application:
import express from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import passport from 'passport';
import { config } from './config';
import { configureOAuthStrategies } from './strategies/oauth.strategy';
import authRoutes from './routes/auth.routes';
import protectedRoutes from './routes/protected.routes';
import { errorHandler } from './middleware/error';
const app = express();
// CORS
app.use(
cors({
origin: config.cors.origin,
credentials: true,
})
);
// Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Cookies
app.use(cookieParser());
// Session middleware
app.use(
session({
name: config.session.name,
secret: config.session.secret,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: config.session.secure,
sameSite: 'lax',
maxAge: config.session.maxAge,
},
})
);
// Passport initialization
configureOAuthStrategies();
app.use(passport.initialize());
app.use(passport.session());
// Routes
app.use('/auth', authRoutes);
app.use('/', protectedRoutes);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Error handler (must be last)
app.use(errorHandler);
// Start server
app.listen(config.port, () => {
console.log(`🚀 Server running on http://localhost:${config.port}`);
console.log(`📦 Environment: ${config.nodeEnv}`);
console.log(`🔐 Auth endpoints available at /auth/*`);
});
Running and Testing the Application
Start the development server using SWC's fast transpilation:
npm run dev
You should see output like:
🚀 Server running on http://localhost:3000
📦 Environment: development
🔐 Auth endpoints available at /auth/*
Test the JWT flow with curl:
# Register a new user
curl -X POST http://localhost:3000/auth/jwt/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"SecurePass123!","name":"Test User"}'
# Login to get tokens
curl -X POST http://localhost:3000/auth/jwt/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"SecurePass123!"}'
# Access protected route with JWT
curl http://localhost:3000/api/data \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"
# Refresh token
curl -X POST http://localhost:3000/auth/jwt/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken":"YOUR_REFRESH_TOKEN_HERE"}'
Test the session flow:
# Register
curl -X POST http://localhost:3000/auth/session/register \
-H "Content-Type: application/json" \
-d '{"email":"session@example.com","password":"SecurePass123!","name":"Session User"}'
# Login (save cookies)
curl -X POST http://localhost:3000/auth/session/login \
-H "Content-Type: application/json" \
-d '{"email":"session@example.com","password":"SecurePass123!"}' \
-c cookies.txt
# Access session-protected route
curl http://localhost:3000/dashboard \
-b cookies.txt
# Check current session user
curl http://localhost:3000/auth/session/me \
-b cookies.txt
# Logout
curl -X POST http://localhost:3000/auth/session/logout \
-b cookies.txt
Test the OAuth flow by navigating to these URLs in your browser:
# Google OAuth
http://localhost:3000/auth/google
# GitHub OAuth
http://localhost:3000/auth/github
Best Practices
- Use HTTPS in production: Never transmit authentication tokens or session cookies over HTTP. Set
secure: trueon cookies and use TLS termination at your reverse proxy or load balancer. - Separate access and refresh tokens: Keep access tokens short-lived (15 minutes) and refresh tokens long-lived (7 days). Store refresh tokens in httpOnly, secure cookies or a secure storage mechanism — never in localStorage.
- Use strong secrets: Generate JWT and session secrets using cryptographically secure random values. Use at least 256-bit secrets. Never hardcode them — always use environment variables.
- Implement token revocation: JWTs are stateless by design, which means they cannot be revoked before expiry. Maintain a blacklist of revoked tokens or use short expiry times with a refresh token rotation strategy.
- Use Redis for session storage in production: The default in-memory session store is not suitable for production. Use
connect-redisor a similar store for scalable session management. - Validate OAuth state parameters: Always use and verify the OAuth state parameter to prevent CSRF attacks during the OAuth flow. Passport handles this automatically, but be aware of it when customizing flows.
- Hash passwords with bcrypt: Never store plaintext passwords. Use bcrypt with a cost factor of at least 12. The
bcryptjslibrary used in this tutorial is pure JavaScript — for even better performance, use the nativebcryptpackage. - Rate-limit authentication endpoints: Protect login, registration, and refresh endpoints with rate limiting to prevent brute-force attacks. Use libraries like
express-rate-limit. - Set appropriate CORS policies: Only allow credentials from trusted origins. Never use
origin: '*'withcredentials: true. - Log authentication events: Log successful and failed login attempts, token refreshes, and OAuth callbacks for audit and security monitoring purposes.
Building for Production with SWC
To compile your TypeScript to JavaScript for production using SWC, install the SWC CLI:
npm install -D @swc/cli
Then build and run:
# Compile TypeScript to JavaScript
npm run build
# Run the compiled output
npm start