Skip to content

Security by Design (Application-Level)

Build the locks and alarms into the blueprint — don’t bolt them onto the front door after the house is built.

  • What it is: embedding authentication, authorization, input validation, secure defaults, and least privilege into the application’s design from the start.
  • Why it matters: fixing a security flaw after release is expensive and risky; designing it in avoids whole classes of vulnerabilities from ever existing.
  • For developers: for every new input your code accepts, ask who is allowed to call this, and how do I validate what they send?
  • When to use it: from the first design of any endpoint or component that accepts input or handles sensitive data — not as a pre-launch checklist item.
  • When not to use it: N/A — security by design applies to every system; the depth of controls scales with sensitivity and exposure, not whether to apply it at all.
  • Prerequisites: API Design & Contracts — most controls attach to the contract boundary.
flowchart LR
    A[Request arrives] --> B{Authenticated?}
    B -- No --> R[Reject]
    B -- Yes --> C{Authorized for this action?}
    C -- No --> R
    C -- Yes --> D[Validate input]
    D -- Invalid --> R
    D -- Valid --> E[Process with least privilege]
  1. Identify who should be allowed to call each endpoint/component (authentication: who are you; authorization: what are you allowed to do).
  2. Validate every input against an explicit allow-list of what’s acceptable, rather than trying to block known-bad patterns.
  3. Apply least privilege: each component should only have the access it strictly needs, not broad access “just in case.”
  4. Choose secure defaults (e.g. deny by default, encrypted by default) so a missed configuration fails safe.
  5. Threat-model new features before building them: ask what could go wrong and who would benefit from misusing this.

The logistics-tracking API is being made public-facing for Black Friday and currently has no rate limiting or per-caller authentication.

  • Input: a previously internal API is about to accept traffic from external partners with no access control in place.
  • Process: the team adds API-key-based authentication for all callers, rate limits per key to prevent abuse or accidental overload, and validates all tracking-number inputs against an expected format before querying the backend.
  • Output: only recognized, authenticated partners can call the API, a single caller can’t exhaust shared capacity, and malformed input is rejected before it reaches backend systems.
[HttpGet("{trackingNumber}")]
public ActionResult<TrackingStatus> Get(string trackingNumber)
{
if (!TrackingNumberRegex.IsMatch(trackingNumber)) // allow-list validation
return BadRequest("Invalid tracking number format.");
return Ok(_service.GetStatus(trackingNumber));
}

On Azure, API keys and per-key rate limiting are enforced at Azure API Management (a policy validates the subscription key and applies a rate-limit-by-key before the request ever reaches the backend), and the keys themselves are stored in Azure Key Vault rather than in application config.

Use case When to use Notes
Exposing a previously internal API publicly Before making it public-facing Add authentication and rate limiting as part of that change, not after an incident
Accepting user input in any form Every time Validate against an allow-list of acceptable values/formats
Granting a service access to a resource At creation time Grant only the specific access needed (least privilege), not broad/admin access
  • Allow-list over block-list validation: enumerating what’s acceptable is more robust than trying to enumerate every malicious pattern.
  • Defense in depth: no single control should be the only thing standing between an attacker and a sensitive action — layer authentication, authorization, and validation.
  • Secure defaults: a configuration that’s accidentally left unset should fail closed (deny/encrypt) rather than fail open.
Term Meaning
Authentication Confirming who is making the request
Authorization Confirming what the authenticated caller is allowed to do
Least privilege Granting only the minimum access needed to perform a task
Allow-list validation Accepting only explicitly known-good input, rejecting everything else
Threat modeling Systematically asking what could go wrong and who would benefit from misuse
Symptom Likely cause Fix
A single caller exhausts shared capacity No rate limiting per caller Add per-key/per-caller rate limits
Unexpected data reaches backend systems Input validated with a block-list instead of an allow-list Switch to allow-list validation of expected formats
A compromised component has broad access Least privilege wasn’t applied at creation Reduce granted access to only what the component actually needs
  • What’s the difference between authentication and authorization?
  • Why is allow-list validation generally safer than trying to block known-bad input patterns?

Part of Introduction to Software & Application Architecture. See also API Design & Contracts, Performance & Scalability Engineering, Resilience & Observability, and the Glossary.