Evolution of AuthN/AuthZ Architecture (4): Authorization Server Internals and Reliability
This series consists of five parts.
- Part 1: From Monolith to Identity Platform
- Part 2: API Gateway and Microservices
- Part 3: Advanced Client Authentication and Incremental Migration
- Part 4 (this article): Authorization Server Internals and Reliability
- Part 5: Data Model and Operations for a Home-Grown Authorization Server
Parts 1 through 3 were written from the perspective of the consumer of the authorization server (AS). The AS was treated as a single box, and the discussion focused on how to arrange Gateways and services around it. In Part 4, we open the box. From the standpoint of someone who selects, builds, and operates the AS itself—an identity platform team, or someone doing a technical evaluation of an IDaaS—we examine how there is design latitude in how you structure the AS internally, and each choice solves a different problem. This is the final piece of this series' guiding question: "which server should perform which processing?"
Should the authentication server and authorization server be separated?
Let's start with a common question: "I was taught that authentication (AuthN) and authorization (AuthZ) are distinct things, so why do Keycloak and Auth0 do both in a single server? Shouldn't they be separated?"
At the protocol level, a unified design is the standard form
OIDC's OpenID Provider is defined as an extension of the OAuth 2.0 authorization server. That is, the specification is designed on the premise that "the server that issues tokens also governs the authentication that precedes it." Having the ID token and access token issued by the same issuer (iss) makes session consistency easier to maintain, and commercial and OSS products are almost all unified. The first answer, then, is that it is common not to separate them at the level of product or deployment unit.
But there are clearly two distinct "concerns"
Even though a unified design is the standard, if you look closely at what's inside the AS, two concerns of different nature coexist.
| Concern | Content | Rate of change |
|---|---|---|
| Authentication | Credential verification, MFA, risk-based authentication, login/consent UX | Fast. Methods evolve on the scale of a few years: password → WebAuthn/passkey → identity verification integration |
| Token issuance | OAuth/OIDC protocol processing, token issuance/revocation, key management | Slow. Bound by RFCs, with correctness and backward compatibility as top priorities |
When things that change at different rates coexist, the desire to draw a boundary is a general principle of architecture. And indeed, separation patterns as internal structure are well established.
Pattern 1: Externalizing login/consent (the Ory Hydra model)
Specialize the AS in protocol processing and token issuance, and delegate the "who is the user" determination to an external component. The OSS project Ory Hydra explicitly adopts this design, so we'll call it the Hydra model here.
Problems it solves:
- The evolution of authentication methods (passkey support, adding risk-based authentication) can be decoupled from the release of the protocol implementation. You can apply separate change management to each: "harden and stabilize" the token issuance side, "iterate fast" on the authentication side.
- The login screen's UX and branding can be freed from product constraints. This is also a structural answer to the problem, raised in Part 1, of the login screen's customization becoming "dependent on the platform team's priorities" once you adopt an IdP.
- The origin for stamping authentication strength (by what method the user authenticated) into the token as
acr/amrclaims becomes clear. This becomes the foundation for step-up authentication, such as "allow this operation only within a session that has completed multi-factor authentication."
Trade-offs:
- You take on the design of a new attack surface: the handoff between the AS and the login app (verifying the challenge, preventing impersonation).
- The number of parties involved in logout consistency increases. In Part 1 we raised "dual session management between the IdP and the app"; here, yet another session boundary appears inside the IdP.
- Failure modes increase. When the login app goes down, only new authentication dies while token refresh survives—this kind of partial failure has both an upside (clearer separation of concerns) and a downside (more complex operations).
Pattern 2: Federation-based separation (delegating authentication upstream)
The AS does not hold authentication itself, and delegates it to an upstream IdP via OIDC/SAML (authentication federation). The AS confines itself to the role of "receiving the upstream authentication result and issuing tokens for its own domain."
This is effective when the authentication assets already exist somewhere else: setting up only an API-facing AS while leveraging the company's existing identity platform; multiple IdPs coexisting after a corporate acquisition; accepting a customer company's IdP in a B2B context (enterprise SSO)—in all of these, the division of labor is "authentication belongs elsewhere, token issuance belongs to us." In the context of the incremental migration in Part 3, it can also be used as an intermediate stage that modernizes only token issuance first, while keeping legacy authentication alive.
The trade-off is that authentication strength and account state become dependent on the upstream. Since the AS cannot detect when MFA is removed upstream, you need to design for propagating acr and for re-authentication requirements (max_age). A chain of trust carries a chain of verification responsibility as long as the chain you connected.
Pattern 3: The boundary of authorization decisions — don't absorb authorization into the AS
The third is less about separation and more about defending a boundary. The "authorization" the AS handles should stay at the coarse-grained level—issuing scopes and managing consent—and you should not bring fine-grained decisions like "can this user operate on this resource" into the AS.
When you operate an AS, requests to "put permission information in the token" gather from every service, the token bloats, and there's a gravitational pull toward the AS becoming a repository of domain knowledge. This is the same shape of failure as Pattern C in Part 2 (concentrating all authorization in the Gateway): the more convenient a central component is, the more it absorbs domain logic. It's worth deciding the discipline up front: put only "stable facts" (user ID, tenant, organizational roles) on the token, and place resource-level decisions in the RS or PDP (Part 2).
So, what should you do?
My view is: "start unified as a process, but draw the module boundary from the outset." If you make explicit the interface between the authentication module and the token issuance module (an internal contract that passes "who authenticated, when, and at what strength"), you keep open the path to later separating out to the Hydra model. If you separate processes from the start, you pay all of the trade-offs above (handoff design, logout, failure modes) before the problems they solve have even surfaced. The value is not in separation itself, but in a structure that lets you separate when you come to want change management separated.
Reliability design for the AS
For the operator of an AS, the greatest non-functional requirement is availability. In Part 1 I wrote that "the IdP becomes a single point of failure," but from the operator's side, this can be mitigated by design.
Designing so that not everything dies when it goes down — classifying state
If you classify the AS's functions by "does it hold state," the survivability at failure time changes.
| Function | State required | When the AS is down |
|---|---|---|
| Token verification (JWT signature verification on the RS side) | JWKS cache only | Survives. As long as the public key is at hand, verification continues |
| Token refresh | Refresh token store | Dies. But the remaining lifetime of access tokens buys grace |
| New login | Session and authorization code store | Dies |
| Introspection | Token store | Dies. An opaque-token configuration spreads this dependency to all APIs (Part 2) |
A design guideline emerges from this table. Decide first "when the AS is down for 30 minutes, what must keep working," and work backward from there to choose the token format and lifetime. With JWT + JWKS cache, you can reduce it to a partial failure: "authenticated users' API usage continues, only new login is unavailable." Conversely, in a configuration where all APIs depend on introspection, the availability of the AS literally becomes the availability of the entire system. The token-format trade-offs discussed in Parts 1 and 2 look like this when translated into the language of availability design.
Key lifecycle
The signing key is the root of the AS's trust, and if it leaks, an attacker can mint arbitrary tokens. There are two things to design (key rotation).
- The rotation procedure: "Add the new key to JWKS → wait for the RS-side cache to propagate → switch issuance to the new key → remove it from JWKS after all tokens issued with the old key have expired." Breaking this order causes a storm of verification errors. The purpose of associating tokens and keys via the
kid(Key ID) is precisely to enable this procedure. Emergency rotation (when a leak is suspected) comes as a set with immediate revocation of all tokens, so it should be drilled as a procedure separate from routine rotation. - Key storage: In domains with high assurance levels (finance, government), you go so far as to place the private key in an HSM/KMS and carve out only the signing operation, so that the AS process itself never touches the plaintext of the private key. Once "even if the AS is compromised, the key does not leak" holds, the worst-case scenario during an incident stays at "halting fraudulent issuance" and does not reach "loss of trust in the entire token platform."
If you build from scratch — external yardsticks for quality
In Part 1 I wrote that "building your own is almost never recommended." That principle stands, but there are cases where building your own is justified: the identity platform itself is the product you offer, or there are business or data-sovereignty requirements that off-the-shelf products cannot satisfy. In such cases, what matters is not to invent for yourself "what to build to be safe." Fortunately, external yardsticks are available.
- FAPI 2.0 Security Profile: A narrowed-down profile of OAuth/OIDC established for financial-grade APIs. It solidifies as a specification the "correct combination" of the parts that appeared through Part 3: PAR (Pushed Authorization Requests, RFC 9126—registering the authorization request in advance via the back channel, eliminating tampering and leakage of the authorization request from the front channel), client authentication being private_key_jwt or mTLS only, the flow being authorization code + PKCE only, and the
issresponse parameter as a countermeasure against mix-up attacks. Even if you're not in finance, it's worth reading as a reference for the state of the art in security design. - OpenID Conformance Suite: A conformance test suite provided by the OpenID Foundation. It can mechanically verify compliance with the OIDC/FAPI specifications and functions as a regression test for your own implementation. The distance between "we think we comply with the spec" and "the tests pass" is especially large for authentication protocols.
- Don't build your own JOSE/JWT library: Exclude cryptographic processing even from the scope of building from scratch. Vulnerabilities in JWT processing—
algconfusion attacks, signature verification bypasses—have historically arisen from implementation bugs. Leave this to proven libraries, and confine what you build yourself to the protocol orchestration above it.
To sum up these three another way: building from scratch is not "deciding for yourself how to interpret the specification," but "accurately assembling the established body of specifications to fit your own requirements." The place to exercise creativity is not the protocol, but around it—the data model, operability, and integration with existing systems.
Summary: the internal-structure options and their value
| Choice | Problem it solves | Cost you pay |
|---|---|---|
| Unified (standard) | Simplicity, session consistency | Evolution of authentication UX is bound to the release of the protocol implementation |
| Externalizing login/consent | Separated change management, fast evolution of authentication methods, UX freedom | Internal handoff design, logout consistency, more failure modes |
| Federation-based separation | Leveraging existing authentication assets, incremental migration, B2B SSO | Authentication strength and account state become dependent on the upstream |
| Discipline of not absorbing authorization | Preventing the AS from becoming domain logic and the token from bloating | Distributing the authorization implementation to each service (back to the discussion in Part 2) |
| Availability design premised on JWT + JWKS cache | Partial survival during an AS failure | Accepting relaxed immediacy of revocation |
| HSM/signing separation | Structural prevention of key leakage | Infrastructure cost and signing latency |
Just like the evolution of "the outside of the AS" covered in Parts 1 through 3, there is no universal solution for the internal structure either—there is only the correspondence with the problems. Even for the consumer, knowing these internal-structure options becomes an evaluation axis for selecting an IDaaS or OSS: "how far can this product externalize the login flow?" and "to what level does it support key management?" are exactly the questions that probe the design latitude discussed here.
