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.
At a glance
Section titled “At a glance”- 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.
Mental model
Section titled “Mental model”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]
Step-by-step walkthrough
Section titled “Step-by-step walkthrough”- Identify who should be allowed to call each endpoint/component (authentication: who are you; authorization: what are you allowed to do).
- Validate every input against an explicit allow-list of what’s acceptable, rather than trying to block known-bad patterns.
- Apply least privilege: each component should only have the access it strictly needs, not broad access “just in case.”
- Choose secure defaults (e.g. deny by default, encrypted by default) so a missed configuration fails safe.
- Threat-model new features before building them: ask what could go wrong and who would benefit from misusing this.
Worked example
Section titled “Worked example”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.
Common use cases
Section titled “Common use cases”| 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 |
Advanced details
Section titled “Advanced details”- 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.
Quick reference
Section titled “Quick reference”| 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 |
Troubleshooting
Section titled “Troubleshooting”| 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 |
Check your understanding
Section titled “Check your understanding”- What’s the difference between authentication and authorization?
- Why is allow-list validation generally safer than trying to block known-bad input patterns?
Related pages
Section titled “Related pages”Part of Introduction to Software & Application Architecture. See also API Design & Contracts, Performance & Scalability Engineering, Resilience & Observability, and the Glossary.