Today I want to dive into one of the most critical stages of microservices development: authorization. This stage can bring significant complexity. Each service is now a separate HTTP server with its own endpoints, and the client (bot, frontend, mobile app) must have simultaneous access to all of them.
Theory: Authentication vs Authorization in Microservices
In microservices architecture, it's crucial to distinguish between:
- Authentication — who is this?
- Authorization — what is this subject allowed to do?
Authentication is typically handled by a dedicated Identity Provider (IdP) or Auth service, while microservices accept already-validated tokens and decide what the client can access.
Main Authorization Approaches in Microservices
- Session-based authorization (cookies + server-side session store)
- Token-based: -> JWT (JSON Web Token) -> Opaque tokens
- OAuth 2.0 / OpenID Connect (OIDC) on top of tokens (usually JWT)
- API Keys
- mTLS (mutual TLS) — mutual TLS authentication between services
- Combinations (e.g., OIDC+JWT for users + mTLS between services)
🔐 JWT — compact signed token with claims (sub, roles, scopes, etc.) that can be validated locally without accessing shared state.
🌐 OAuth2/OIDC — protocols defining:
- How clients obtain tokens
- How SSO is implemented
- Token format (JWT or opaque) is a separate layer
📈 Approach Features & 2026 Trends
Key trend: Moving authentication (and part of authorization) to a centralized IdP/Auth-hub, while microservices become "resource servers" that trust validated tokens.
Centralized IdP/Auth-hub + Resource Servers Pattern
Centralized IdP:
- Stores user accounts, login methods, MFA, social login, etc.
- Implements OAuth2/OIDC protocols and issues tokens (access, refresh, ID token)
- Serves as the single source of truth for authentication and basic roles/groups
Resource Servers (microservices):
- Host protected resources
- Accept only requests with valid tokens (OAuth2 terminology)
Simultaneous zero-trust hardening: Even inside the cluster, services explicitly authenticate (mTLS, certificates, SPIFFE).
Quick Overview of Approaches
| Approach | Key Characteristics |
|---|---|
| Sessions | Simpler for classic web apps/monoliths. Requires shared session store or sticky sessions in microservices (scaling complexity). |
| JWT | Stateless approach, perfect for microservices + API gateway. De facto standard for OAuth2/OIDC access tokens in 2026. |
| OAuth2/OIDC | Industry standard for external clients, mobile, SPA, B2B. Easy SSO, MFA, social login via IdP. |
| API Keys | Simple keys for machine-to-machine access/integrations. Often combined with IP limits, rate limiting, gateway policies in 2026. |
| mTLS | Standard service-to-service auth in service mesh (Istio, Linkerd, Consul). Guarantees "this pod is exactly that service" but carries no user rights. |
Modern production standard: API Gateway/Ingress + OAuth2/OIDC + JWT + mTLS inside mesh.
1️⃣ Sessions (Cookies + Server-Side Sessions)
How It Works
- - User logs in on server (password, MFA)
- - Server generates unique session ID and stores full session in store (Redis, SQL, Memcached)
- - Session ID written to cookie (typically HttpOnly, Secure, SameSite=Strict/Lax)
- - Subsequent requests → server reads session ID from cookie → fetches session data from store
- - If session found and not expired → user authenticated
Microservices variants:
- Sticky sessions: Load balancer binds user to one pod/service (by cookie)
- Shared session store: All microservices read from central store (Redis cluster)
✅ Advantages
- Implementation simplicity: Built into ASP.NET Core, Spring, Rails etc. No protocols to learn
- Centralized control: Easy to revoke/end session in store (e.g., suspected compromise)
- Security: Cookie contains no sensitive data (just ID), forgery-resistant (proper HttpOnly, Secure)
- Perfect for SSR/traditional web: Browser auto-sends cookies
❌ Disadvantages & Microservices Issues
- Scaling: Requires resilient session store → central point of failure
- Redis outage = mass logout of ALL users
- Sticky sessions limit flexibility: Can't freely scale/restart pods, hard multi-region migration
- Performance: Every request = store read (network roundtrip), accumulates across services
- Poor API/mobile/SPA fit: Cookie issues (CORS, cross-domain, native apps)
- CSRF/XSS: Requires extra measures (CSRF tokens, SameSite)
When to Use
✅ Classic SSR web apps without complex API architecture
✅ Internal admin panels where simplicity > scale
✅ Monolith-to-microservices transition period
❌ Pure REST APIs, SPAs, mobile clients
2026 status: Rarely used in pure microservices, survives in legacy/BFF for web UI.
2️⃣ JWT (JSON Web Token)
How It Works
JWT consists of three parts:
- Header – signing algorithm
-
Payload – claims (
sub,roles,scopes, etc.) - Signature – signed with a private key
Flow:
- After login, the Auth service generates a JWT with the required claims and signs it with a private key.
- The client attaches the JWT to every request in the header:
Authorization: Bearer <jwt_token> - Each microservice validates locally:
- Signature (using IdP public key from JWKS endpoint)
-
exp(expiration),iss(issuer),aud(audience),nbf(not before) - Claims (roles, scopes, etc.)
- If everything is valid, the service uses claims for authorization.
Signing options:
- Symmetric (shared secret) — simpler, but worse for distributed systems
- Asymmetric (RS256/ECDSA) — standard; public key exposed via JWKS
✅ Advantages
- Stateless: No central session store, each service verifies tokens independently — perfect for microservices
- Scalable: To add a new service, you just give it the IdP public key
- Flexible claims: You can embed roles, tenant, permissions, feature flags — the service sees everything it needs immediately
- Standard: Native support in all major frameworks (ASP.NET Core JwtBearer, Spring Security, etc.)
- Cross-platform: Works equally well for browsers, mobile apps, CLI tools, partners
❌ Disadvantages
-
Revocation is hard: Until the token expires (usually 15–60 minutes), it remains valid
- Workarounds: short TTL + refresh tokens, blacklists, introspection endpoints (but this hurts statelessness)
- Token size: Can be large with many claims, increasing HTTP header size
- Claims leakage: If a token is captured, the attacker can read roles/rights (though cannot forge signature)
-
Crypto pitfalls: Incorrect validation (not checking
iss/aud/exp) is a common vulnerability
When to Use
- Primary format for access tokens in microservice architectures
- Always in combination with OAuth2/OIDC
- Standalone — for simple internal APIs
2026 status: De facto standard access token format in OAuth2/OIDC, typically with short-lived access tokens + refresh tokens.
3️⃣ OAuth2 / OIDC
How It Works
- OAuth2 is an authorization protocol (how a client obtains a token with specific access rights).
- OIDC (OpenID Connect) is an identity layer on top of OAuth2 (who is the user).
Roles:
- Resource Owner — the user
- Client — SPA, mobile app, BFF
- Authorization Server — IdP (Auth server)
- Resource Server — microservice (API)
Main Flows Used with Microservices
-
Authorization Code Flow + PKCE (for SPA/mobile):
- Client redirects the user to the IdP
- After login, IdP returns an authorization
code - Client exchanges the
codeforaccessandrefreshtokens
-
Client Credentials Flow (service-to-service):
- Service uses
client_id/client_secretto obtain a token on its own behalf
- Service uses
Implicit Flow (legacy, for old SPA apps — considered obsolete)
Device Code Flow (for TV/CLI and limited-input devices)
Token Types
- Access Token — carries access rights (what the client can do)
- Refresh Token — used to obtain new access tokens
- ID Token (OIDC) — contains user identity information (who the user is)
✅ Advantages
- Standardized: Works with any modern IdP (Keycloak, Auth0, Azure AD, Google, etc.)
- SSO & federation: One login for all services, easy integration with external identity providers
- Supports many client types: SPA, native apps, backend services, partners
-
Scopes & delegated access: Clients receive only the permissions they actually need (
read:orders,write:payments, etc.) - Security features: PKCE against code injection, short-lived tokens, audit trails
❌ Disadvantages
- Complexity: Many moving parts (redirect_uri, scopes, client_id, PKCE), easy to misconfigure
- IdP dependency: IdP must be highly available and secure
- Frontend complexity: SPA/mobile must correctly handle redirects, token storage, and silent refresh
When to Use
- Any public REST APIs with external clients
- Microservice systems that require SSO
- B2B integrations and partner access
2026 status: The mandatory standard for enterprise and public-facing APIs.
4️⃣ API Keys
How It Works
- A unique key is generated (typically a 32–64 character string, sometimes with a prefix).
- The client (service, script, partner) sends the key in a header, for example:
-
X-API-Key: abc123... - or
Authorization: ApiKey abc123...
-
- The gateway/microservice validates the key against a database or cache (e.g., Redis).
- The key is usually associated with:
- IP restrictions
- Rate limits
- Scopes
- Expiration time
✅ Advantages
- Maximum simplicity: No complex protocols, redirects, or crypto handshakes
- Fast to implement: Great for internal tools, CI/CD pipelines, partner systems
- Flexible policies: Rate limiting, IP whitelists, daily/monthly quotas
- Easy revocation: Just remove or disable the key in the database
❌ Disadvantages
- No user identity: The key is tied to an application/service, not to an individual user
- Coarse-grained access: Often “all or nothing” or very rough scopes
- Key management overhead: Secure issuing, rotation, and audit are required
- No inherent encryption: Keys can appear in logs/traffic — TLS is mandatory
When to Use
- Internal services and scripts
- Partner integrations without complex per-user permissions
- Prototypes and proof-of-concept APIs
2026 status: Commonly used in combination with API gateways (rate limiting + IP checks), but not the primary method for user-facing authorization.
5️⃣ mTLS (Mutual TLS)
How It Works
With regular TLS, only the server presents a certificate to the client.
With mTLS, the verification is mutual:
- The client service presents a client certificate (with SAN/pod name).
- The server service verifies the client certificate (trusted CA, not expired, correct subject).
- The server also presents its own certificate.
- The connection is encrypted, and both sides know exactly who is on the other end.
mTLS in a Service Mesh (e.g., Istio)
- A sidecar proxy (Envoy) automatically handles certificates (via Citadel/SPIRE).
- Policies in
AuthorizationPolicy/PeerAuthenticationdefine rules like: > "OrderService is allowed to call PaymentService, but not the other way around."
✅ Advantages
- Zero trust: You do not trust the network; every piece of traffic is authenticated
- Automatic encryption inside the cluster
- Observability: Easy to audit which service talked to which
- Policy enforcement: Mesh-level RBAC based on certificates/identities
❌ Disadvantages
- PKI complexity: Certificate rotation, CA management, revocation
- Config sensitivity: One bad policy can break all traffic
- Overhead: TLS handshakes on connections (though modern hardware handles this well)
- Not about users: mTLS identifies services, not users — user permissions still need JWT/OAuth2/etc.
When to Use
- Service-to-service communication inside a cluster
- Zero-trust, enterprise environments, regulated industries
- Always together with a service mesh
2026 status: The standard for production microservices running in a mesh, often as part of the “golden combo”:
API Gateway + OAuth2/JWT + mTLS.
🔄 Authorization Flows: Client → Gateway → Microservices
1️⃣ Client → IdP → API Gateway → Microservices (OAuth2/OIDC + JWT)
A typical modern setup: browser/SPA/mobile client → API Gateway/BFF → dozens of microservices.
🔐 Authentication at IdP (SSO)
- The client opens the frontend, which redirects the user to the IdP using Authorization Code Flow (OIDC).
- The user logs in (password, MFA, etc.).
- The IdP creates a session and returns an authorization code to the client.
- The client exchanges this code for an access token (often JWT) and a refresh token.
📥 Client → API Gateway
- The client sends requests to the API Gateway with:
Authorization: Bearer <access_token> - The gateway validates:
- JWT signature
-
exp,iss,aud -
scopes/rolesusing the IdP’s JWKS endpoint.
📡 Gateway → Microservices
The gateway can either:
- Proxy the original JWT downstream as-is, or
- Issue a lightweight internal token for services (token exchange to an internal JWT).
Each microservice then either:
- Validates the token itself; or
- Trusts the gateway’s validation and uses the provided claims for authorization.
🧩 Authorization Inside Microservices
-
Basic (RBAC): Check roles/scopes from the token (
admin,user,order.read,order.write, etc.). -
Advanced (ABAC / policy-based):
- Fetch additional data from other services (fetch/replicate patterns).
- Delegate the decision to a centralized Authorization service.
2️⃣ Service-to-Service (S2S) Authorization
Pattern 1: Propagation (User Token Forwarding)
- Service A receives a request with the user’s JWT.
- When calling Service B, A forwards the same JWT.
- Service B validates the token and checks the user’s permissions.
Use when you need end-to-end user identity across services.
Pattern 2: Service Account / Client Credentials
- Each service gets its own
client_id/client_secret/ certificate. - A service uses Client Credentials Flow to obtain a machine-to-machine token.
- It then calls other services on its own behalf, not the user’s.
Use when:
- There is no end-user (batch jobs, schedulers), or
- You need service-level permissions (e.g. “BillingService can call PaymentService”).
Pattern 3: mTLS in the Mesh
- Inside the mesh, each pod/SAN is identified by a certificate.
- Mesh policies (
AuthorizationPolicy, etc.) define which service can call which.
User identity can:
- Be propagated in JWT alongside mTLS, or
- Be omitted completely for batch/technical processes where no end-user is involved.
⚙️ Resilience: How Painful Are Failures?
Let’s look at failure points for each approach. Key chain links:
- IdP/Auth service
- API Gateway
- Session store / PKI / mesh control plane
- Individual microservice
1️⃣ OAuth2/OIDC + JWT
IdP/Auth service down:
- New logins and refresh token flows stop working
- Existing access tokens continue to be accepted until they expire
- Pain is delayed — the system degrades as tokens expire
API Gateway down:
- External clients almost always lose access completely
- Pain is acute, so the gateway must be scaled and protected aggressively
Single microservice down:
- Only that service’s functionality breaks
- The authorization mechanism as a whole still works
2️⃣ JWT Without a Central IdP
- If each service validates JWT locally using cached keys:
- Auth service outage only affects new logins / key rotation
- If services call Auth service on every validation (fetch strategy):
- Auth service outage makes all requests unauthenticated/unauthorized
2026 recommendation: avoid hard runtime dependency on the Auth service; always validate JWT locally.
3️⃣ Sessions
Session store (Redis, SQL) down:
- All sessions become unavailable → users are massively logged out
- Pain is usually maximal, since sessions are central shared state
Auth service that creates sessions down:
- New logins are impossible
- Existing sessions keep working as long as the session store is alive
4️⃣ API Keys
Key management service down:
- You can’t issue/revoke keys
- Existing keys continue to work
Gateway down:
- Same as other schemes: very painful, as it’s usually the single entry point
5️⃣ mTLS
Control plane / PKI / mesh down:
- Existing connections may work for a while
- New certificates aren’t issued, old ones aren’t rotated
- As certificates expire, more and more S2S calls start failing with TLS errors
Mesh policy misconfig / incompatible mTLS config:
- Can instantly break a large portion of internal traffic ### 6️⃣ Resilience Summary Table
| Approach | Central Component Failure (IdP/Auth/Session/PKI) | Pain Level |
|---|---|---|
| OAuth2 + JWT | No new logins/refresh, existing tokens work until exp
|
Delayed, manageable |
| JWT (local validation) | No new tokens issued, existing tokens keep working | Delayed |
| JWT (fetch to Auth) | Auth service unavailable → all validations fail | Acute, often critical |
| Sessions | Session store down → mass logout | Very painful |
| API Keys | Cannot manage keys, but existing keys still work | Usually moderate |
| mTLS | PKI/mesh down → gradual degradation of S2S traffic as certs expire | Growing, depends on DevOps quality |
🧩 Authentication & Authorization Patterns in Microservices
Patterns are proven architectural solutions to recurring authorization problems. They help deal with distributed data, scale, and complexity.
As of 2026, the focus is on combining:
- OAuth2/JWT for users
- mTLS for service-to-service traffic
- Delegated authorization for complex permissions
1️⃣ API Gateway / BFF as the Authentication Entry Point
Description:
An API Gateway (or Backend for Frontend, BFF) is the single entry point where:
- Authentication happens
- Coarse-grained authorization is enforced (basic checks: roles, scopes)
The token is then forwarded to microservices.
How it works:
- Client → Gateway (with credentials or an existing token)
- The gateway:
- Validates the token
- Checks roles/scopes
- Enriches the request with user context (headers/claims)
- Gateway → Microservices (with token or extracted claims)
- Microservices perform fine-grained authorization using their own data
Pros:
- Single, well-defined entry point for external clients
- Per-tenant isolation, rate limiting, traffic management
- Minimizes duplicated auth logic across services
Cons:
- Gateway becomes a single point of failure → must be highly available
- Potential tight coupling if services depend heavily on the gateway’s claim format
Examples:
- Ocelot / YARP (for ASP.NET)
- Kong, KrakenD, NGINX, Traefik
2️⃣ Token Propagation
Description:
The user’s token (JWT) is passed from the client through the gateway to every microservice in the call chain. Each service validates the token and enforces its own access rules.
Variants:
End-user propagation:
The original user JWT is propagated end-to-end through all services.Service token exchange:
The gateway exchanges the user JWT for a service token, which is then used internally.
Pros:
- End-to-end user identity available in every microservice
- Each service can independently enforce its own authorization rules (RBAC/ABAC)
Cons:
- Token is validated in every service (CPU overhead)
- Large tokens (many claims) increase request size across the entire call chain
When to use:
- User-centric flows where you need full user context in downstream services
- Scenarios requiring per-resource checks (RBAC/ABAC) based on user identity and attributes
3️⃣ Service Account / Client Credentials
Description:
For service-to-service (S2S) calls, each service has its own client_id / client_secret and obtains a token via the Client Credentials Flow.
How it works:
-
Service A → IdP with
client_id/client_secret→ receives a service JWT -
Service A → Service B using this token in the
Authorizationheader - Service B validates the token and checks the permissions of Service A (not the end-user)
Pros:
- Permissions are assigned to services, not users
- Well-suited for batch jobs, background workers, daemons
Cons:
- No end-user context (often needs to be combined with token propagation)
- Storing secrets in services introduces secrets management risk
When to use:
- Background jobs, schedulers, cron
- Integrations between services where no end-user is directly involved
4️⃣ Authorization Patterns with Distributed Data (4 Strategies)
From Chris Richardson (microservices.io): used for fine-grained authorization when permissions are spread across multiple services.
| Strategy | Description | Pros | Cons |
|---|---|---|---|
| Provide | All required claims (roles, tenant, relationships) are embedded into the JWT by IdP | Fast, fully stateless | JWT becomes huge, revocation is hard |
| Fetch | Service fetches permissions from other services on each request (REST/gRPC) | Always up-to-date data | Latency (N+1 calls), coupling to others |
| Replicate | Service keeps a local replica of foreign data (events/CQRS-based sync) | Fast local checks | Eventual consistency, complex replication |
| Delegate | Authorization is delegated to an external Authorization Service (OPA, Cerbos, Oso) | Centralized policies, handles complex logic | Dependency on AuthZ service, added latency |
Delegate example:
A microservice calls the AuthZ service:
isAllowed(user=alice, action=delete, resource=order123) → YES/NO
The AuthZ service returns yes or no, and the microservice enforces the result.
5️⃣ Edge vs Service-Level Authorization
Description:
Two-layer authorization model:
- Edge (Gateway): Coarse-grained checks: roles, scopes, rate limiting, tenant isolation
- Service Level:Fine-grained checks: concrete permissions on specific resources
Pros:
- Defense in depth — multiple layers of protection
- The gateway filters out most invalid/unauthorized requests early
Cons:
- Duplication of checks between gateway and services
When to use:
- For all public APIs, Internet-facing systems, and multi-tenant platforms
6️⃣ Centralized Authorization Service
Description:
A separate service stores policies (e.g. Rego/OPA, Cedar/Cerbos) and answers questions like: allow?(user, action, resource) → true/false
Pros:
- Can handle complex logic (ABAC, ReBAC, tenant isolation) in one place
- Supports auditing, A/B testing of policies, GitOps workflows
Cons:
- Adds latency to each authorization decision
- Another single point of failure if not deployed in HA
When to use:
- Enterprise systems with thousands of rules and users
- Regulated industries where auditability and consistency of policies are critical
🔁 Delegate Pattern (Authorization Delegation) + OPA
Theory: The Distributed Permissions Problem
In a microservices architecture, permissions for a single resource are often spread across multiple services. For example, user Alice might be allowed to:
- ✅ Read her own orders (Order Service)
- ✅ Create payments for her own orders (Payment Service)
- ❌ Modify other users’ orders (Order Service)
- ✅ As a manager, see her team’s orders (User Service + Order Service combined)
A JWT can carry only static claims (roles, groups, basic attributes) — it does not know about:
- Concrete relationships (owner of this order, member of this team)
- Dynamic state (order status, project membership, org structure changes)
Solution: The microservice delegates the authorization decision to an external Authorization Service that knows all the rules and data needed.
How the Delegate Pattern Works
Flow:
- Client → Order Service
DELETE /orders/123
Authorization: Bearer
- Order Service extracts key claims from the JWT: { "userId": "alice", "roles": ["user"], "tenant": "acme" } 3.Order Service → AuthZ Service (delegated check): POST /authorize Content-Type: application/json
{
"principal": { "id": "alice", "roles": ["user"] },
"action": "delete",
"resource": { "type": "order", "id": "123" },
"context": { "tenant": "acme" }
}
- AuthZ Service → response: { "allow": true, "reason": "user is owner of order" } or { "allow": false, "reason": "user is not owner and not manager" }
- Order Service either performs or rejects the operation based on the AuthZ decision.
OPA (Open Policy Agent) as a Delegate Implementation
OPA is a Policy-as-Code engine that evaluates authorization policies written in the Rego language.
✅ Benefits of Delegate + OPA
- ✅ Complex logic: Handles ABAC, ReBAC, tenant isolation, workflow-dependent rules
- ✅ Centralized policies: One Rego policy set used by many services
- ✅ Auditability: You can log every authorization decision
- ✅ A/B testing of policies: Roll out policy changes without redeploying services
- ✅ GitOps-friendly: Policies live in Git and are deployed via CI/CD
- ✅ Polyglot support: OPA can be used from Go, Java, .NET, Node and more
❌ Drawbacks
- ❌ Latency: Adds ~50–200 ms per authorization call over the network
- ❌ Single point of failure: OPA must run in a highly available setup
- ❌ Complexity: Rego is a new language the team has to learn
- ❌ Data synchronization: Business data must be synced or exposed to OPA for decisions
🔁 Alternatives to OPA
- Cerbos — managed/embedded policy engine, often simpler to integrate
-
OpenFGA (Google Zanzibar model) — ReBAC (relationships:
user → document → viewer) - Permify — Zanzibar-style auth on PostgreSQL
- Oso — policy language and engine with Python/Rust SDKs
🧭 When to Use the Delegate Pattern
Use Delegate + Policy-as-Code when:
- ✅ You have 10+ microservices with non-trivial permissions
- ✅ You are building a multi-tenant platform
- ✅ You work in regulated industries (fintech, healthcare, gov)
- ✅ You need strong audit/compliance guarantees
Avoid it when:
- ❌ You have simple CRUD apps with basic role-based access only
2026 status: Policy-as-Code (OPA/Cerbos) is considered an enterprise standard, especially in environments using GitOps and service meshes.
7️⃣ 2026 “Golden Standard”: Layered Approach
High-Level Architecture
External Clients
↓
API Gateway / BFF
(OAuth2/OIDC + JWT validation, coarse-grained auth)
↓
Microservices
(token propagation or service tokens)
↕
Microservice ↔ Microservice
(mTLS in a service mesh + fine-grained auth)
External clients → API Gateway
Gateway handles OAuth2/OIDC, validates JWTs, and enforces coarse-grained authorization (roles, scopes, tenant).
Gateway → Microservices
Uses token propagation (forwarding user JWT) or service tokens (Client Credentials) for internal calls.
Microservice ↔ Microservice
Runs over mTLS inside a service mesh, with fine-grained authorization:
Local RBAC/ABAC in each service, or
Delegated checks via a centralized AuthZ service (OPA/Cerbos/etc.).
✅ Pros
- Covers all major scenarios (user-facing, S2S, batch, partners)
- Enables zero-trust networking inside the cluster
- Scales well horizontally with stateless tokens and mesh-based security
❌ Cons
- Increased infrastructure complexity
- Requires a mature DevOps/SRE culture and good observability This is effectively what companies like Netflix, Uber, DoorDash and other large-scale players run in production today.
💻 Minimal C# Example (ASP.NET Core + JWT)
Below is a minimal JWT authentication setup for a microservice built with ASP.NET Core 8/10 Web API (acting as an OAuth2 resource server behind a gateway).
Minimal JWT Setup in ASP.NET Core (8/10)
1️⃣ Program.cs — JWT Bearer Configuration
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
builder.Services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// URL of your IdP (e.g. https://idp.example.com)
options.Authority = configuration["Jwt:Authority"];
// Audience = this API's identifier (aud claim)
options.Audience = configuration["Jwt:Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
// If you are not using Authority/JWKS and rely on a symmetric key:
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!))
};
});
builder.Services.AddAuthorization();
builder.Services.AddControllers();
var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
2️⃣ Controller with Role-Based Authorization
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
// Any authenticated user
[HttpGet]
[Authorize]
public IActionResult GetMyOrders()
{
var userId = User.FindFirst("sub")?.Value;
// Load orders by userId...
return Ok(/* orders */);
}
// Only users with the "admin" role
[HttpDelete("{id:int}")]
[Authorize(Roles = "admin")]
public IActionResult DeleteOrder(int id)
{
// Delete order...
return NoContent();
}
}
In a real production system, these APIs would typically sit behind an API Gateway, which:
Validates tokens at the edge
Optionally enriches requests with user context
Forwards the token (or an internal token) to microservices
The tokens themselves are issued by an IdP such as Duende IdentityServer, Auth0, Azure AD B2C, or Keycloak.
🎯 Which Approach to Use When (Practical Guidance)
Decision Table for 2026
| Scenario | Recommended Approach |
|---|---|
| SPA / mobile client + many microservices | OAuth2/OIDC + JWT via IdP and API Gateway |
| Public REST API for external clients | OAuth2 (auth code / client credentials) + JWT or opaque tokens |
| Internal service-to-service calls | mTLS in a service mesh + either user JWT propagation or service tokens |
| Small web app / semi-monolith | Sessions (cookies + session store), or JWT if microservices migration is planned |
| Simple batch / CLI integrations | API Keys or OAuth2 Client Credentials |
| Fintech, government, critical infrastructure | Combo: OIDC+JWT for users, mTLS for services, short-lived tokens, MFA, audit logging |
For modern microservices with external clients, your default choice should be:
IdP + OAuth2/OIDC + JWT + API Gateway, with mTLS and short-lived tokens inside the cluster.Keep sessions for simple / legacy web apps and internal admin panels, where microservices aren’t really needed yet.
Use API keys sparingly, for those services and scripts where a full OAuth2 setup would be overkill.
When designing your solution, consider not only security, but also resilience:
how painful will it be if IdP, gateway, session store, or PKI/mesh go down?
In 2026, this is just as important a design criterion as developer convenience.





Top comments (1)
Also check out SpiceDB for centralized authorization using ReBAC. It's open source and has an official .net library github.com/authzed/authzed-dotnet