Full Stack Development

API Security



by Tomas Trescak t.trescak@westernsydney.edu.au

API

/

Security

  • Vulnerable
  • Hackable
  • No inherent security
  • Problems :
    • Data leakage
    • Unauthorized access
    • Injection attacks
    • DDoS attacks

API

/

JWT Authentication

    import express, { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import cookieParser from 'cookie-parser';

const app = express();
app.use(cookieParser());
const SECRET_KEY = 'my-super-secret-key';

interface UserPayload {
  username: string;
}

// Issue JWT on login
app.post('/login', (req: Request, res: Response) => {
  const token = jwt.sign({ username: 'admin' }, SECRET_KEY, { expiresIn: '1h' });
  res.cookie('authToken', token, { httpOnly: true, secure: true });
  res.json({ message: 'Logged in!' });
});

// Middleware to verify JWT
const verifyToken = (req: Request, res: Response, next: NextFunction) => {
  const token = req.cookies.authToken;
  if (!token) return res.status(401).json({ message: 'Access denied' });

  try {
    const verified = jwt.verify(token, SECRET_KEY) as UserPayload;
    (req as any).user = verified; // Add user to request
    next();
  } catch (err) {
    res.status(400).json({ message: 'Invalid token' });
  }
};

// Protected route
app.get('/admin/posts', verifyToken, (req: Request, res: Response) => {
  res.json({ posts: ['Draft Post 1', 'Draft Post 2'] });
});

app.listen(3000, () => console.log('Server running'));
  
  • Who’s knocking?
  • Json Web tokens

API

/

HTTPS

  • Lock your data
  • TLS in action

API Security

/

Rate Limiting

  • Stop the flood
  • Throttle Request
  • Example: 100 requests every 15 minutes.
    import express from 'express';
import rateLimit from 'express-rate-limit';

const app = express();

// Rate limiting middleware
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per IP
  message: 'Too many requests, please try again later!',
});

app.use('/api', limiter);

// Public route for blog posts
app.get('/api/posts', (req: Request, res: Response) => {
  res.json({ posts: ['Post 1', 'Post 2'] });
});

app.listen(3000, () => console.log('Server running'));
  

API Security

/

Validation and Sanitisation

  • Validate
  • Sanitise
  • zod ...
    // app/api/admin/post/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

// Define the schema for a blog post with Zod
const postSchema = z.object({
  title: z.string().min(1, 'Title must not be empty').max(100, 'Title too long'),
  content: z.string().min(10, 'Content too short'),
  tags: z.array(z.string()).optional(), // Optional tags array
}).strict(); // No extra fields allowed

// POST handler for creating a blog post
export async function POST(req: NextRequest) {
  try {
    // Parse and validate the incoming JSON body
    const body = await req.json();
    const validatedData = postSchema.parse(body);

    // Simulate saving to a database (in a real app, you'd use Prisma or similar)
    const newPost = {
      title: validatedData.title,
      content: validatedData.content,
      tags: validatedData.tags || [],
    };

    return NextResponse.json({ message: 'Post created', post: newPost }, { status: 201 });
  } catch (error) {
    if (error instanceof z.ZodError) {
      // Return validation errors
      return NextResponse.json({ errors: error.errors }, { status: 400 });
    }
    return NextResponse.json({ message: 'Server error' }, { status: 500 });
  }
}

// Example client-side fetch to test it
/*
fetch('/api/admin/post', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'My First Post', content: 'Hello, world! This is a test post.' })
}).then(res => res.json()).then(console.log);
*/
  

API Security

/

Conclusions

  • Key takeaway: APIs must be secured
  • Start early
  • Employ more rather than less