Secure By Design: Building Secure APIs from Day One
APIs are the backbone of modern applications. As more systems expose their functionality through APIs, attackers increasingly exploit poorly secured endpoints. In this guide, we explore how to build secure APIs using real-world practices and ASP.NET Core examples.
đ Why API Security Matters
Incident | Vulnerability | Description | Impact on Organization |
---|---|---|---|
Facebook (2018) | Access Token Exposure | A flaw in the âView Asâ feature allowed attackers to steal session tokens via the video uploader component. These tokens could be used to impersonate users without logging in. | Exposed ~50 million accounts; triggered global scrutiny; regulatory investigations in the EU and US; significantly damaged user trust and led to tightened platform security policies. |
Venmo | Unprotected Public API | The API allowed access to public transaction feeds without rate limiting or authentication, enabling mass scraping of usersâ payment activity, including sender, receiver, and notes. | Raised serious privacy concerns; journalists demonstrated large-scale scraping; Venmo updated its settings interface but delayed implementing stronger technical controls. |
Panera Bread | Unauthenticated Data Access | Customer informationâincluding names, emails, phone numbers, and birthdaysâwas accessible via an unprotected API endpoint without any form of authentication or authorization. | Estimated exposure of over 37 million records; delayed incident response worsened reputational damage; served as a key industry example of failure in basic API hygiene and disclosure practices. |
đĄď¸ Common API Vulnerabilities
APIs are often targeted due to their accessibility and the sensitive data they handle. Here are some common vulnerabilities:1. Excessive Data Exposure
APIs sometimes return more data than necessary. Attackers can exploit this to access sensitive information. This is a common pitfall in API development where an endpoint returns entire objectsâlike full user profilesâinstead of just the data actually needed by the client.
// đ Insecure: Returns entire user object
return Ok(userService.GetUser(id));
In the insecure code snippet, the API returns the full User object directly from the service layer. This might seem convenient, but it risks exposing sensitive fields like passwords, internal IDs, or metadata unintentionally.
// â
Secure: Return only required fields via DTO
return Ok(new UserDto { Name = user.Name, Email = user.Email });
This pattern explicitly controls what gets serialized and returned, typically through a lightweight class like UserDto that holds only the fields the UI requires. Not only does this minimize risk, but it keeps your response payloads lean and purpose-driven. In essence, itâs about being intentional with data exposureâshow only what the client truly needs, and nothing more. This principle not only enhances security but also helps reduce payload size and improves maintainability.
2. Broken Object-Level Authorization (BOLA)
APIs often expose object IDs (like user or order IDs) without validating ownership, allowing attackers to
access others' data. This is one of the most common and dangerous API flaws: Broken Object-Level
Authorization (BOLA). It happens when APIs expose object identifiersâlike orderId
,
userId
, or invoiceId
, but
fail to confirm whether the requester is authorized to access that object.
// đ Insecure: No ownership check
[HttpGet("/orders/{id}")]
public IActionResult GetOrder(int id) => Ok(orderService.GetById(id));
The API simply retrieves and returns the order object without verifying if the current user owns it. That
means an attacker could manipulate the URL
(e.g., /orders/12345)
and potentially access someone else's dataâa textbook case of insecure
direct object reference (IDOR).
// â
Secure: Check if user owns the order
[HttpGet("/orders/{id}")]
public IActionResult GetOrder(int id)
{
var order = orderService.GetById(id);
if (order.UserId != GetCurrentUserId()) return Forbid();
return Ok(order);
}
Now, unless the UserId on the order matches the ID of the logged-in user, access is denied. This is exactly the kind of defensive pattern the Secure By Design guide emphasizes.
Youâll often find BOLA risks in APIs that expose predictable ID patterns without authentication gatesâlike/users/3
or /invoices/5.
By combining predictable routing with missing access
controls, attackers donât even need complex exploits; they just enumerate IDs.
3. Lack of Input Validation
Without input validation, APIs are prone to injection attacks, such as SQL injection or XSS. This is a
foundational API security principle:
- SQL injection, where attackers inject SQL commands to manipulate the database (e.g.,
'; DROP TABLE Users;--
) - Cross-Site Scripting (XSS), where scripts are injected to run in browsers, potentially stealing user data or tokens
// đ Insecure: Blindly accepts input
public IActionResult Search(string query) => db.Search(query);
In this insecure example, the API directly uses the query
parameter in a database search without
validating or sanitizing it. An attacker could pass a malicious string that executes harmful SQL commands or
scripts.
// â
Secure: Validate and sanitize input
public IActionResult Search([FromQuery] string query)
{
if (string.IsNullOrWhiteSpace(query)) return BadRequest();
return Ok(db.Search(Sanitize(query)));
}
In the secure version, the API checks
-
string.IsNullOrWhiteSpace(query)
ensures that only meaningful input proceeds -
Sanitize(query)
is a method that cleans the input, removing or escaping potentially dangerous characters
Always validate input against a schema or set of rules, and use libraries that automatically handle sanitization for you. This is a core principle of Secure By Designânever trust data from outside your system.
4. No Rate Limiting or Abuse Protection
Without rate limiting, attackers can brute-force or flood your APIs with requests, leading to denial-of-service (DoS). This is a classic availability threat: APIs without rate limiting or abuse protection are like open doors with no bouncerâanyone can walk in, and in large enough numbers, they can knock the entire system offline.
When you donât set limits, attackers can:
- Brute-force login or auth endpoints (e.g. guessing passwords or tokens at scale)
- Flood your API with requests, overwhelming backend servicesâan easy route to a denial-of-service (DoS)
â 1. Rate Limiting Middleware
// Use ASP.NET Core Rate Limiting middleware
app.UseRateLimiter(new RateLimiterOptions {
PermitLimit = 100,
QueueLimit = 10
});
â 2.API Gateway Enforcement:
It also recommends external enforcement through gateways like Azure API Management or AWS API Gateway, where you can:- Set usage quotas per user or IP
- Define burst limits to absorb spikes safely
- Block suspicious IP ranges
Together, these strategies help insulate your APIs from both targeted attacks and accidental overloadsâcritical for uptime, scalability, and basic fairness among clients.
đ§Ş Design-Time Security Principles
These guidelines align with the pageâs broader philosophy: secure your APIs by design, not by reacting to breaches later.- 1. Minimize surface area: Expose only the endpoints that are absolutely necessary. Every extra endpoint increases your attack surface. For example, avoid generic "get all" endpoints unless truly needed.
- 2. Validate all input at the edge: Validate and sanitize all incoming data as soon as it enters your systemâideally at the API gateway or controller level. This blocks common attacks like SQL injection and XSS.
- 3. Donât expose internal identifiers: Use GUIDs or slugs instead of sequential IDs. This prevents attackers from easily guessing valid object references, reducing risks like BOLA (Broken Object-Level Authorization).
- 4. Principle of least privilege: Grant each API client or user only the permissions they needânothing more. Use role-based or claims-based authorization to enforce this.
- 5. Fail securely: When errors occur, return generic error messages and avoid leaking stack traces or sensitive details in API responses.
- 6. Secure defaults: APIs should be secure by defaultârequire authentication, use HTTPS, and deny access unless explicitly allowed.
đŚ Authentication & Authorization
Why It Matters
The surrounding content presents real-world failures (like Facebookâs token leak or Paneraâs exposed customer data) as a warning. Without strong auth controls, even well-coded APIs can become gaping vulnerabilities.
What Itâs Saying
Use OAuth2 and Token-Based Access
The guide recommends modern, token-based mechanisms (like OAuth2 or JWT) rather than session-based or cookie authentication. These tokens include defined scopes (what the token can access) and lifetimes (when it expires), limiting potential misuse.
Compare the Two Code Samples:
đ Insecure:
[HttpGet("/orders")]
public IActionResult GetOrders() => Ok(orderService.GetAll());
This endpoint allows anyone to hit /orders
âno login, no access control. An attacker or script could
grab every order with no friction.
đ Secure:
[Authorize(Roles = "Admin")]
[HttpGet("/orders")]
public IActionResult GetOrders() => Ok(orderService.GetAll());
This version requires the caller to be authenticated and have an "Admin"
role. Thatâs role-based
access control (RBAC) in actionâonly users with the right authority can access sensitive functions.
This example fits neatly into the pageâs broader approach: lock down APIs by default, exposing only whatâs needed, to who truly needs it.
đ§° Input Validation & Output Encoding
Why This Matters
Without input validation, APIs are vulnerable to classic attacks like SQL injection, XSS, or even simple data corruption. The guide references real-world examples to emphasize that blind trust in user data is a major risk.
Whatâs Happening in the Code
đ Insecure Version
public IActionResult CreateUser(User user) => userService.Save(user);
This implementation accepts user input as-is and immediately passes it to the business logic layer. Thereâs no validation, allowing malformed or malicious data to slip through unchecked.
â Secure Version
public class UserDto
{
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Range(18, 100)]
public int Age { get; set; }
}
public IActionResult CreateUser([FromBody] UserDto userDto)
{
if (!ModelState.IsValid) return BadRequest("Invalid data");
return Ok(userService.Save(userDto));
}
This version:
- Defines explicit rules for
Name
,Email
, andAge
using attributes like[Required]
and[EmailAddress]
. - Validates incoming data via
ModelState.IsValid
before processing it. - Rejects bad input early with a
400 Bad Request
response.
This aligns with the guideâs emphasis on treating all input as untrusted and validating it at the boundary. It also ensures downstream components receive well-formed data.
Rate Limiting & Abuse Protection
Why It Matters
Without rate limiting, even legitimate endpoints can become dangerous. The page highlights risks such as brute-force login attempts, bot-driven scraping, or flooding APIs to trigger denial-of-service (DoS) conditions. To prevent this, limiting access by volume and frequency is critical.
Whatâs the Code Doing?
app.UseRateLimiter(new RateLimiterOptions {
PermitLimit = 100, // max 100 requests per minute
QueueLimit = 10 // buffer 10 before rejecting
});
This middleware enforces throttling by:
- Capping request volume (100 per minute in this case)
- Queuing minor bursts (up to 10 extra requests)
- Denying overflow once the buffer is full
Recommended Enhancements
The guide suggests pairing middleware with API gateway rules for a more robust shield. Example configuration with Azure API Management:
<rate-limit-by-key calls="100" renewal-period="60">
<key value="@(context.Request.IpAddress)" />
</rate-limit-by-key>
This enforces a per-IP limit of 100 calls per minute. You can also scope by client ID, subscription key, or token claims. Benefits:
- Prevents overuse before reaching backend services
- Supports custom rate strategies per API or operation
- Enables real-time abuse mitigation and analytics
Together, these form a layered defenseâmiddleware handles local enforcement, while the API gateway manages edge-level control. The page calls this a "Secure by Design" approach: defending APIs before threats manifest.
Logging & Monitoring
Why It Matters
Logging is a critical part of API observability and security. The guide highlights how the absence of meaningful logs leads to blind spots during incidents, while poor logging hygiene can expose sensitive data. Striking the right balance is key.
Best Practices from the Guide
- Use structured logging tools like
Serilog
orSeq
to produce searchable, consistent log output. - Attach correlation IDs to every request to trace activity across services.
- Never log PII, passwords, or secretsâlogs should help you investigate issues, not create new security holes.
Code Sample: Secure Logging in ASP.NET Core (Serilog)
// In Program.cs
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "SecureApi")
.WriteTo.Console()
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
builder.Host.UseSerilog();
// Inside a controller
[HttpPost("/login")]
public IActionResult Login(LoginRequest req)
{
logger.LogInformation("Login attempt for user {Username} with RequestId {RequestId}",
req.Username,
HttpContext.TraceIdentifier); // Do NOT log passwords or tokens!
var result = authService.AttemptLogin(req);
if (!result.Success)
{
logger.LogWarning("Failed login for user {Username}", req.Username);
return Unauthorized();
}
return Ok(result.Token);
}
This example shows how to:
- Use structured logs with enriched metadata
- Log meaningful user actions and trace IDs
- Completely avoid logging sensitive payloads like passwords
The page concludes that smart logging is part of securing APIs âby designââcollect what you need to detect and respond, never more.
đ Secure API Versioning
Why This Matters
As APIs grow, it's natural to roll out v2, v3, and beyond. But older versions often remain in use by internal tools or clients. The guide warns that unmonitored legacy APIs become prime targets for attackersâespecially if security patches or controls arenât maintained.
Key Recommendations
- Protect older versions until deprecated safely: Apply auth, rate limiting, and logging to all supported versions.
- Avoid breaking changes: Design updates to be backward-compatible and gentle to adopt.
- Patch across versions: Backport critical fixes to every active version to prevent patch gaps.
Sample Compatibility & Deprecation Matrix
Version | Status | Auth | Rate Limit | Logging | Security Patching | Deprecation Timeline |
---|---|---|---|---|---|---|
v1 | Legacy | â | â | Limited | â Backported | Phase-out: Q4 2025 |
v2 | Active | â | â | â | â | Sunset planned Q2 2026 |
v3 | Current | â | â | â + Telemetry | â | N/A |
đ Tools for API Security Testing
Why This Matters
Building secure APIs is just the startâtesting them regularly ensures your defenses actually work. The guide encourages adopting attacker-mindset tools to proactively uncover vulnerabilities before theyâre exploited.
Recommended Tools
- OWASP ZAP: A free and powerful scanner that detects API vulnerabilities like injection flaws, broken authentication, and misconfigured headers. Ideal for integration in CI/CD pipelines or staging environments.
- Postman Security Scans: Built into Postman, these scans test API collections for misconfigured security, overly permissive responses, or missing headersâall within a familiar dev workflow.
- Swagger/OpenAPI Security Annotations: Embed auth schemes directly into your API specification to guide tools and gateways. For example:
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
This not only documents your APIâs requirements clearly but also allows automated tools to simulate authenticated requests, strengthening your test coverage and onboarding experience.
đ Conclusion
đ Securing APIs is not optionalâitâs foundational. From validating inputs to enforcing strong authentication and rate limiting, API security must be considered from design through deployment. Secure your APIs by default, not by patching after a breach.
- â Validate all inputs â Never trust client data without validation and sanitization.
- â Enforce authentication & authorization â Use OAuth2, scoped tokens, and role checks.
- â Apply rate limiting â Throttle traffic via middleware and API gateways to prevent abuse.
- â Log securely â Use structured logging with request IDs, and never log PII or secrets.
- â Protect all versions â Maintain security across API versions until theyâre properly deprecated.
- đ§Ş Test proactively â Use tools like OWASP ZAP, Postman, and OpenAPI annotations to catch flaws early.
Built securely from design to deployment. Don't wait for a breachâlock it down by default.