Security & Governance for Architects: OAuth, JWT, API Security & Data Privacy
Architect-level security guide ā OAuth 2.0 flows for real systems (including service-to-service), JWT internals and validation pitfalls, API gateway security patterns, mTLS, zero trust, and data privacy controls for production APIs.
Security is not a feature added at the end. For architects, it is a first-class design constraint that shapes topology, service boundaries, data flows, and deployment decisions. This guide covers security the way a system design interview expects: mechanistically correct, trade-off aware, and grounded in production realities.
Authentication vs Authorisation
Before protocols: get the vocabulary precise.
Authentication (AuthN): "Who are you?" ā verifying identity.
Authorisation (AuthZ): "What are you allowed to do?" ā verifying permissions.
OAuth 2.0 is an authorisation framework. OpenID Connect (OIDC) is the authentication layer built on top of it. They are frequently conflated ā in interviews, always clarify which problem you are solving.
OAuth 2.0: "I authorise this app to read my calendar"
ā access token (capability)
OIDC: "I am Alice, authenticated via Google"
ā ID token (identity claim)OAuth 2.0: The Four Flows ā When to Use Each
Flow 1: Authorization Code + PKCE (Web Apps, SPAs, Mobile)
The most important flow. Used whenever a user is present who needs to authenticate.
SPA/Mobile App Auth Server Your API
ā ā ā
āāā 1. Redirect to /authorize āāāŗā ā
ā (with code_challenge) ā ā
ā User logs in ā
āāāā 2. Redirect back āāāāāāāāāāā ā
ā ?code=AUTH_CODE ā ā
āāā 3. POST /token āāāāāāāāāāāāāāŗā ā
ā code + code_verifier ā ā
āāāā 4. access_token āāāāāāāāāāāā ā
ā refresh_token ā ā
āāā 5. GET /api/orders āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāŗ ā
ā Authorization: Bearer ā PKCE (Proof Key for Code Exchange) prevents code interception attacks. The app generates a random code_verifier, hashes it to produce code_challenge, sends the challenge to the auth server, and proves it holds the original verifier when exchanging the code for tokens. Without PKCE, a malicious app that intercepts the redirect URI could steal the auth code.
Why not Implicit flow? Deprecated. It returned tokens in the URL fragment (visible in browser history, referer headers, server logs) and had no refresh token. PKCE-enabled Authorization Code replaced it entirely.
Flow 2: Client Credentials (Service-to-Service, No User)
When a backend service calls another backend service ā no user is present. This is the dominant flow in microservices architectures.
Order Service Auth Server Inventory Service
ā ā ā
āāā POST /token āāāāāāāāāāāāāāāāāŗā ā
ā client_id + client_secret ā ā
āāāā access_token (short-lived) āā ā
ā ā
āāā GET /api/stock āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāŗ ā
ā Authorization: Bearer ā The client_id + client_secret should never be in environment variables in plaintext. They belong in a secrets manager (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault) with automatic rotation.
Scopes for service-to-service: use fine-grained scopes. inventory:read is better than all:access. Scope is an authorisation constraint ā the auth server validates that this client is allowed this scope.
Flow 3: On-Behalf-Of / Token Exchange (Delegated Identity in Microservices)
When Service A calls Service B on behalf of a user. Service B needs to know which user's data to return, not just that Service A is authenticated.
User ā API Gateway ā Order Service ā Inventory Service
ā ā
access_token access_token
(user: alice) (user: alice, via OrderService)In On-Behalf-Of (OBO), Order Service exchanges the incoming user token for a new token (with the same user identity but issued to Order Service as the actor). The auth server validates that Order Service is allowed to impersonate users before issuing the downstream token.
This preserves user identity through the call chain ā Inventory Service can apply per-user authorisation policies.
Flow 4: Device Code (IoT, CLI Tools)
For input-constrained devices. Device displays a short code; user authenticates on a separate device (phone/browser). Server polls for completion. Less common in integration contexts but worth knowing for IoT architectures.
JWT Internals: What Architects Must Know
A JWT (JSON Web Token) is a self-contained, verifiable claim. Understanding its internals prevents the most common security mistakes.
Structure
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 ā Header (base64url)
.
eyJzdWIiOiJ1c2VyLTEyMyIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxNjI0MDAwMH0= ā Payload
.
[SIGNATURE] ā Cryptographic proofHeader: algorithm and token type.
Payload: claims ā sub (subject/user ID), iss (issuer), aud (audience), exp (expiry), iat (issued at), plus custom claims.
Signature: HMAC-SHA256 (symmetric) or RS256/ES256 (asymmetric). The signature proves the token was issued by the auth server and has not been tampered with.
Validation ā Every Step Matters
An API receiving a JWT must validate all of the following:
// ASP.NET Core JWT validation ā what AddAuthentication does under the hood
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://login.microsoftonline.com/{tenantId}/v2.0";
options.Audience = "api://my-service"; // MUST validate aud
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // iss must match Authority
ValidateAudience = true, // aud must be THIS service
ValidateLifetime = true, // exp must be in the future
ValidateIssuerSigningKey = true, // signature must verify
ClockSkew = TimeSpan.FromSeconds(30)
};
});The critical security mistake: skipping audience (aud) validation. If Service A issues a token for Service B and you don't validate aud, a client can present a token meant for a different service. This is a real attack vector ā known as a "confused deputy."
RS256 vs HS256
| | HS256 (HMAC) | RS256 (RSA asymmetric) | |--|-------------|----------------------| | Key type | Shared secret | Private key (signs) + Public key (verifies) | | Key distribution | Everyone who verifies needs the secret | Public key is safe to distribute (JWKS endpoint) | | Attack surface | Secret compromise = total compromise | Private key never leaves auth server | | Use | Internal, single-service | Cross-service, public APIs |
Always use RS256 or ES256 for JWTs crossing service boundaries. The auth server publishes its public keys at /.well-known/jwks.json ā services fetch and cache them, rotating on key ID (kid) change.
JWT Claims Design for Authorisation
Do not overload JWTs with all permissions. JWTs are signed at issuance ā if permissions change mid-session, the token still carries the old claims until expiry.
Pattern: keep tokens short-lived (5ā15 min), with broad scope claims. Do fine-grained permission checks at the resource server using the token as identity.
// Good JWT payload ā identity + coarse scope
{
"sub": "user-123",
"oid": "azure-object-id",
"tid": "tenant-id",
"roles": ["orders.write", "products.read"],
"exp": 1716240900,
"iat": 1716240000
}// Resource server does fine-grained check from its own policy store
[Authorize(Policy = "CanApproveOrder")]
public IActionResult ApproveOrder(string orderId) { ... }
// Policy registered at startup:
options.AddPolicy("CanApproveOrder", policy =>
policy.RequireRole("orders.write")
.AddRequirements(new OrderOwnershipRequirement()));API Security Architecture
API Gateway as the Security Perimeter
The API Gateway is the single entry point for all external traffic. It is where you enforce:
Internet āāāŗ API Gateway āāāŗ Internal Services
ā
āāā TLS termination (HTTPS only)
āāā Authentication (validate JWT/OAuth token)
āāā Authorisation (scope/role check before routing)
āāā Rate limiting (per client, per endpoint)
āāā IP allowlisting / geo-blocking
āāā Request schema validation
āāā Audit logging (who called what, when)What the gateway should NOT do: business logic. Gateways that contain routing rules based on business data (e.g., "route premium customers to premium backend") become bottlenecks and deployment nightmares. Business routing belongs in services.
Rate Limiting ā Beyond "add rate limiting"
Architects are expected to know rate limiting strategies:
| Strategy | How | Trade-off | |----------|-----|-----------| | Token bucket | Tokens added at fixed rate, consumed per request | Allows burst up to bucket size | | Fixed window | Count requests per time window (per minute) | Edge case: 2Ć rate at window boundary | | Sliding window | Rolling count over past N seconds | Accurate, higher memory | | Leaky bucket | Requests processed at fixed output rate | Smooth, queues excess |
In Azure API Management:
<rate-limit-by-key calls="100" renewal-period="60"
counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization",""))" />Key the rate limit by the client identity (JWT sub or client_id), not the IP. IP-based rate limiting is trivially bypassed with multiple IPs and breaks legitimate clients behind NAT.
Mutual TLS (mTLS): Service-to-Service Authentication
OAuth client credentials work well for service-to-service auth. For the highest-security environments (financial, healthcare), mTLS adds a second layer ā both sides present certificates.
Order Service Inventory Service
ā ā
āāāāā TLS ClientHello āāāāāāāāāāāāāāāāāāŗā
āāāāā ServerHello + Server Certificate āā
āāāāā Client Certificate āāāāāāāāāāāāāāāŗā ā mTLS: client proves identity
āāāāā Verify + TLS established āāāāāāāāāā
āāāāā GET /stock (JWT in header) āāāāāāāāŗā ā app-level auth still presentCertificates are rotated automatically by a service mesh (Istio, Linkerd) or Azure Container Apps. Do not manage certificate rotation manually in production.
mTLS + JWT is the defence-in-depth pattern for high-security service meshes: mTLS proves the caller is a known service (transport layer), JWT proves the user context being acted on (application layer).
OWASP API Security Top 10 ā Architect View
The five most architecturally significant:
1. Broken Object Level Authorisation (BOLA) ā API returns another user's data because the resource ID in the URL is not validated against the authenticated user.
// Vulnerable
GET /api/orders/ORD-999 ā returns any order by ID
// Fixed
GET /api/orders/ORD-999 ā validates orderId belongs to context.User.Sub2. Broken Authentication ā weak token validation (not checking aud, accepting expired tokens, using HS256 with a weak secret).
3. Excessive Data Exposure ā returning the full domain object and relying on the client to hide sensitive fields. Return only what the caller needs (projection, not the entity).
4. Mass Assignment ā allowing clients to set fields they should not (e.g., {"role": "admin"} in a user update request). Use explicit DTOs, never bind directly to domain objects.
5. Security Misconfiguration ā default credentials, verbose error messages exposing stack traces, CORS configured as *, HTTP allowed alongside HTTPS.
Zero Trust Architecture
Zero Trust is a security model: "never trust, always verify." The traditional perimeter model assumed everything inside the corporate network was safe. Zero Trust assumes the network is already compromised.
Traditional perimeter:
External āā[Firewall]āāāŗ Internal (trusted) āāāŗ Everything
ā once inside, free movement
Zero Trust:
Every request āāāŗ Authenticate identity
āāāŗ Verify device health
āāāŗ Authorise specific resource access
āāāŗ Encrypt all traffic (even internal)
āāāŗ Log all accessZero Trust in practice for APIs
- Every service call is authenticated (no "trusted internal" exemptions)
- Short-lived tokens only ā credentials that expire limit breach windows
- Least privilege scopes ā each service token has the minimum required permissions
- Audit log every access ā not just failures
- mTLS for service mesh ā encrypts and authenticates all east-west traffic
- Private endpoints for PaaS ā Service Bus, Cosmos DB, Key Vault on private IPs (not public internet)
Azure Entra ID (formerly Azure AD) as the Zero Trust foundation
// Managed Identity ā no credentials, ever
// The service identity is the Azure resource itself
var credential = new DefaultAzureCredential();
var secretClient = new SecretClient(
new Uri("https://my-vault.vault.azure.net/"), credential);
// Conditional Access Policy (configured in Entra):
// Require MFA for users from outside corporate network
// Require compliant device for admin access
// Block access from high-risk sign-in locationsManaged Identity is the Zero Trust credential for Azure workloads: no secrets to rotate, no credentials to leak, identity tied to the Azure resource itself.
Data Privacy Controls
Defence-in-Depth for Sensitive Data
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Layer 1: Access Control ā who can see what (authZ) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Layer 2: Encryption at Rest ā Azure Storage SSE, TDE ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Layer 3: Encryption in Transit ā TLS 1.2+ (mTLS ideal) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Layer 4: Data Masking ā PII redacted in logs/responses ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Layer 5: Audit Logging ā all access to PII recorded ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāPII in Logs: The Silent Violation
Logging frameworks are a common vector for GDPR/HIPAA violations. A carelessly structured log event includes email addresses, patient IDs, or credit card numbers.
// Bad ā PII in structured log
logger.LogInformation("User {Email} requested order {OrderId}", user.Email, orderId);
// Good ā pseudonymise at the logging boundary
logger.LogInformation("User {UserId} requested order {OrderId}",
_pseudonymiser.Hash(user.Email), orderId);In Serilog, use destructuring policies to strip PII before it reaches the sink:
Log.Logger = new LoggerConfiguration()
.Destructure.ByTransforming<UserDto>(u => new { u.UserId, Region = u.Region })
// Email, phone, address are stripped before logging
.WriteTo.Seq("http://seq:5341")
.CreateLogger();Field-Level Encryption for Highly Sensitive Data
For fields that must be stored but are rarely queried (SSN, patient MRN, credit card last 4):
public class EncryptedPatientRecord
{
public Guid Id { get; set; }
// Stored as encrypted bytes; decrypted only in the service layer
[Encrypted]
public string NationalHealthId { get; set; }
// Non-sensitive ā stored plaintext for querying
public string TreatmentRegion { get; set; }
public DateTime AdmissionDate { get; set; }
}Key management: use Azure Key Vault with automatic key rotation. The encryption key is never stored in the database or application config ā it is fetched from Key Vault at runtime using Managed Identity.
Data Residency in Distributed Systems
For healthcare (HIPAA), financial (PCI-DSS), or EU personal data (GDPR):
GDPR: EU personal data must not leave the EU without adequate protection
HIPAA: PHI must remain within compliant infrastructure
PCI-DSS: cardholder data must be isolated in a defined cardholder data environment
Architecture implications:
- Deploy EU tenants to Azure West Europe / North Europe only
- Geo-DR must replicate within region (Azure paired regions within jurisdiction)
- Message bus (Service Bus) must be in the same region as the data
- Logs containing PII must route to EU Log Analytics workspace
- Third-party integrations must have Data Processing AgreementsSecurity Architecture Interview Questions
"How would you secure an API in a microservices architecture?" API gateway validates tokens (JWT/OAuth) at the perimeter. Each service re-validates the token for audience and scope ā gateways can be bypassed. mTLS encrypts and authenticates service-to-service calls. Managed Identity for all service-to-service credentials. Secrets in Key Vault. Rate limiting at gateway per client identity.
"What is the difference between OAuth and OIDC?" OAuth is an authorisation framework ā it issues access tokens representing what the client is allowed to do. OIDC is an authentication layer built on OAuth ā it adds the ID token, a signed JWT containing the authenticated user's identity. Use OAuth when you need delegated access. Use OIDC when you need to know who the user is.
"What are the risks of long-lived JWT tokens?" You cannot revoke a JWT without a revocation list (which makes token validation stateful ā defeating the purpose). If a token is stolen, it remains valid until expiry. Use short-lived access tokens (5ā15 min) and short-lived refresh tokens with rotation. For immediate revocation, maintain a token revocation list or use opaque tokens checked against a session store.
"What is the confused deputy problem?"
A service receives a token intended for a different audience and acts on it. Prevented by validating the aud claim in every JWT. Every service must verify the token was issued specifically for it.
"How do you handle PII in an event-driven system?" Two patterns: (1) Reference pattern ā events contain only entity IDs; consumers fetch PII directly from the service that owns it. PII never in the event log. (2) Encryption ā PII in events is encrypted with a per-customer key stored in Key Vault. To "delete" a customer's data, delete the key ā their events become undecryptable (crypto-shredding, used for GDPR right-to-erasure in immutable logs).
Related: OAuth 2.0 Flows Explained
Related: JWT Deep Dive
Related: Integration Governance and Security
Related: Azure Cloud Integration
Enjoyed this article?
Explore the System Design learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.