Production engineering guide for security architecture review covering patterns, implementation strategies, and operational best practices.
A Security Architecture Review is a structured evaluation of a system’s security design, conducted early in the development lifecycle or before a major release. It ensures that security is not an afterthought but embedded in the architecture: from data flows to trust boundaries, from identity models to runtime protections. It matters most when onboarding a new service, planning a major cloud migration, or responding to a security incident that reveals systemic weaknesses.
Before the review meeting, the architecture team must compile a set of artifacts that define the system’s security posture. These should be accessible, versioned, and consistent across teams.
A diagram showing external actors, system components, and data flows. Use standard notation: boxes for services, arrows for data, and labels for security-sensitive interactions. Include trust boundaries: zones like "Internet," "DMZ," "Internal Network," and "Secure Data Center."
For each service or subsystem, provide a DFD showing data inputs, processing, storage, and outputs. Mark security-relevant data: PII, credentials, session tokens, audit logs. Label security controls at each stage: encryption, authentication, integrity checks.
A list of security requirements, each with a unique ID, priority (must-have, should-have, could-have), and acceptance criteria. Examples:
SR-001: All user data must be encrypted at rest using AES-256. SR-007: API endpoints must validate and sanitize all input payloads.A documented process identifying threats using STRIDE or DREAD. Include:
security/architecture/) diagrams/ threat-model.md using Markdown security/ directories with config.yaml, policies.json, audit-trail.mdThe review is a collaborative session focused on three core activities: validating the architecture, identifying gaps, and aligning on decisions.
Begin with a 20-minute presentation of the SCD and DFD. The presenter should walk through the journey of a user request, highlighting security-sensitive steps.
curl -I https://api.example.com/v1/users
# Output:
HTTP/2 200
content-type: application/json
strict-transport-security: max-age=31536000; includeSubDomains; preload
# Check that all services expose a health endpoint with security metadata
curl https://api.example.com/health
# Response:
{
"status": "OK",
"version": "1.2.3",
"security": {
"tls": {
"version": "TLSv1.3",
"cipher": "TLS_AES_256_GCM_SHA512",
"certificates": 2,
"chain_length": 3
},
"auth": {
"type": "JWT",
"audience": "api.example.com",
"issuer": "https://auth.example.com"
}
}
}
Review the threat model document with a focus on high-risk threats.
An attacker sends a malicious username parameter: ' OR 1=1-- This bypasses the login form and gains admin access.
users table has a created_at timestamp, but last_login is not indexed.-- Check if prepared statements are used
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM users
WHERE username = $1 AND password = $2;
Seq Scan on users (cost=0.00..12.34 rows=1 width=400)Filter: ((username = $1) AND (password = $2))Buffers: shared hit=5The query plan shows a sequential scan, not an index scan. The username column is not indexed.
# Verify rate limiting via Redis
redis-cli GET "login:rate:ip:192.168.1.1"
# Response:
"12"
The key exists, but the value is a string, not a JSON object with count, last_attempt, and window.
Review each security requirement against the architecture and implementation.
-- In PostgreSQL, check table encryption
SELECT
schemaname,
tablename,
hasindexes,
relpages,
pg_size_pretty(pg_total_relation_size('users')) AS total_size
FROM pg_tables
WHERE tablename = 'users';
-- Output:
schemaname | tablename | hasindexes | relpages | total_size
-----------|-----------|------------|----------|------------
public | users | t | 142 | 11 MB
# Check application-level encryption
curl -H "Authorization: Bearer <JWT>" https://api.example.com/v1/users/123
# Response:
{
"id": 123,
"name": "Alice Smith",
"email": "[email protected]",
"address": {
"street": "123 Main St",
"city": "Boston",
"zip": "02101"
},
"created_at": "2023-10-05T14:30:00Z",
"encrypted": true,
"encryption_key_id": "key-456"
}
The address field is encrypted, but name and email are not. The encrypted flag is set, but the actual data is stored in plaintext.
The encryption key is fixed and never rotated. The key is used for 18 months but never updated.
Two services use different key identifiers: key-456 and enc-key-456. No standard naming.
The application uses raw AES-256 keys, but no key derivation (e.g., PBKDF2, scrypt) to derive keys from a master key.
The review ends with a decision session to resolve open issues and set new directions.
Each decision should include:
e.g., "Use JWE for end-to-end encrypted user data"
Why this decision is necessary or better than alternatives.
Which components, teams, and processes will change.
Concrete, measurable conditions for success.
Person responsible for implementation and documentation.
Title: Use JWT with JWE for secure API payloads
Rationale: Current API responses contain sensitive user data in JSON. The JWT is signed but not encrypted. A compromised API gateway exposes the full payload to attackers.
Impact:
Acceptance Criteria:
application/jose+json responses.jwe key in response contains a JWE object.protected header with alg and encciphertext encrypted with AES-256-GCMrecipient info with kidOwner: Backend team lead, with support from platform team.
# Generate a JWE-protected JWT
curl -X POST https://auth.example.com/jwe \
-H "Content-Type: application/json" \
-d '{
"aud": "api.example.com",
"iss": "https://auth.example.com",
"exp": 1700000000,
"sub": "user-123",
"jwe": {
"protected": {
"alg": "RSA-OAEP",
"enc": "A256GCM"
},
"recipients": [
{
"header": {
"kid": "key-456"
},
"encrypted_key": "base64url-encoded-encrypted-key"
}
],
"ciphertext": "base64url-encoded-ciphertext",
"tag": "base64url-encoded-authentication-tag"
}
}'
alg or enc values: alg: A256GCM instead of A256GCM (typo), causing clients to fail to decrypt.
kid in recipients header: Client expects kid to identify the key, but kid is missing — leads to key mismatches.
Gateway receives JWE but only validates the JWT signature, not the encryption. Data is accessible even if the JWE key is invalid.
The jwe object is present, but the ciphertext field is missing or misnamed (ciphertext vs ct).
The review is not complete until decisions are documented and actions are tracked.
security/architecture/README.md with decisions and outcomes security/architecture/decisions/ directory with one file per decision security/architecture/decisions/001-jwe-encryption.md# Decision: Use JWE for API Payloads
## Status
- **Status:** Approved
- **Date:** 2023-11-02
- **Owner:** [email protected]
## Context
Current API responses use JWTs with signatures but no encryption. Sensitive data is exposed in plaintext.
## Decision
All API responses must be JWE-protected. The JWE must use RSA-OAEP for key encryption and A256GCM for content encryption.
## Consequences
- Increased latency due to JWE processing.
- Client libraries must handle JWE decoding.
- New audit trail for key rotation and JWE validation.
## Implementation
- Update `api-service` to generate JWEs.
- Update `api-gateway` to validate and decrypt JWEs.
- Add `jwe` field in all API response schemas.
implemented, partially-implemented, not-implementedTLS is not enough; additional mechanisms like mutual TLS, token validation, and endpoint authentication are often overlooked.
JWTs are used for authentication but not for authorization or encryption. They are signed but not encrypted, leading to exposed claims.
Keys are created but not rotated, backed up, or audited. No process for key retirement or revocation.
Services log to files, but audit logs are not centralized. No timeline of security-relevant events across services.
user_id, userId, userID, user_id, user_id, user_id — all used interchangeably.
The Security Architecture Review is not a formality. It is a diagnostic, a design workshop, and a commitment to security as a core architectural discipline.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy