When Azure AD Claims Aren’t Enough: Issuing Your Own JWT for Custom Authorization

Modern applications often start with Azure AD (now Microsoft Entra ID) for authentication—and for good reason. It’s secure, battle-tested, and integrates seamlessly with Azure-native services.

But as systems grow, teams frequently hit a wall:

“We can authenticate users, but we can’t express our authorization logic cleanly using Entra ID claims alone.”

At that point, one architectural pattern comes into focus:

Validating the Azure AD token, then issuing your own application-specific JWT.

This post explains when this strategy makes sense, when it doesn’t, and how to implement it responsibly.


The Problem: Identity vs Authorization Drift

Azure AD excels at answering one question:

Who is this user?

It does this using:

  • App roles
  • Group membership
  • Scopes
  • Optional claims

However, real-world authorization often depends on things Azure AD cannot evaluate:

  • Data stored in your application database
  • Tenant-specific permissions
  • Feature flags or subscription tiers
  • Time-bound or contextual access rules
  • Row-level or domain-specific authorization logic

Trying to force this logic into Entra ID frequently leads to:

  • Role explosion
  • Overloaded group membership
  • Fragile claim mappings
  • Slow iteration cycles

This is where many systems start to creak.


The Strategy: Token Exchange with an Application-Issued JWT

Instead of overloading Azure AD, you introduce a clear trust boundary.

High-level flow

  1. User authenticates with Azure AD
  2. Client receives an Azure-issued access token
  3. Your API fully validates that token
  4. Your API issues a new, short-lived JWT containing:
    • Application-specific claims
    • Computed permissions
    • Domain-level roles
  5. Downstream services trust your issuer, not Azure AD directly

This is often referred to as:

  • Token exchange
  • Backend-for-Frontend (BFF) token
  • Application-issued JWT

It’s a well-established pattern in enterprise and government systems.


Why This Works Well

1. Azure AD handles authentication; your app handles authorization

This separation keeps responsibilities clean:

  • Azure AD → Identity proof
  • Your API → Business authorization

You avoid pushing business logic into your identity provider, where it doesn’t belong.


2. You can issue dynamic, computed claims

Your API can:

  • Query databases
  • Apply complex logic
  • Evaluate tenant state
  • Calculate effective permissions

Azure AD cannot do this—and shouldn’t.


3. Downstream services stay simple

Instead of every service needing to understand:

  • Azure tenants
  • Scopes vs roles
  • Group semantics

They simply trust:

  • Your issuer
  • Your claim contract

This dramatically simplifies internal service authorization.


4. Identity provider portability

If you later introduce:

  • Entra B2C
  • A second tenant
  • External identity providers

Your internal JWT remains the stable contract.
Only the validation layer changes.


When This Is Overkill

This pattern is not always the right choice.

Avoid it if:

  • App roles and groups already express your needs
  • Authorization rules are static
  • You don’t have downstream services
  • You want minimal operational overhead

A second token adds complexity—don’t add it unless it earns its keep.


Security Rules You Must Follow

If you issue your own JWT, there are no shortcuts.

1. Fully validate the Azure token

Always validate:

  • Signature
  • Issuer
  • Audience
  • Expiration
  • Required scopes or roles

If you skip any of these, the pattern collapses.


2. Keep your token short-lived

Best practice:

  • 5–15 minute lifetime
  • No refresh tokens (unless explicitly designed)

Azure AD should remain responsible for session longevity.


3. Protect and rotate signing keys

  • Use a dedicated signing key
  • Store it securely (Key Vault)
  • Rotate it regularly
  • Publish JWKS if multiple services validate your token

Your token is only as trustworthy as your key management.


4. Be disciplined with claims

Only include what downstream services actually need.

Avoid:

  • Personally identifiable information
  • Large payloads
  • Debug or “just in case” claims

JWTs are not data containers.


A Practical Mental Model

A simple way to reason about this pattern:

Azure AD authenticates people.
Your application authorizes behavior.

If that statement matches your system’s reality, this approach is not only valid—it’s often the cleanest solution.


Final Thoughts

Issuing your own JWT after validating an Azure AD token is not a workaround or an anti-pattern. It’s a deliberate architectural choice used in:

  • Regulated environments
  • Multi-tenant SaaS platforms
  • Government and municipal systems
  • Complex internal platforms

Like all powerful patterns, it should be applied intentionally, with strong security discipline and a clear boundary of responsibility.

Used correctly, it restores simplicity where identity systems alone fall short.

Architecture Diagram (Conceptual)

Actors and trust boundaries:

  1. Client (Web / SPA / Mobile)
    • Initiates sign-in using Microsoft Entra ID
    • Receives an Azure AD access token
    • Never handles application secrets or signing keys
  2. Microsoft Entra ID (Azure AD)
    • Authenticates the user
    • Issues a standards-based OAuth 2.0 / OpenID Connect token
    • Acts as the external identity authority
  3. Authorization API (Your Backend)
    • Validates the Azure AD token (issuer, audience, signature, expiry, scopes)
    • Applies application-specific authorization logic
    • Queries internal data sources (database, feature flags, tenant configuration)
    • Issues a short-lived, application-signed JWT
  4. Downstream APIs / Services
    • Trust only the application issuer
    • Validate the application JWT using published signing keys (JWKS)
    • Enforce authorization using domain-specific claims

Token flow:

  • Azure AD token → proves identity
  • Application-issued JWT → encodes authorization

This design creates a clean boundary where:

  • Identity remains centralized and externally managed
  • Authorization becomes explicit, testable, and owned by the application

IDesign Method: An Overview

Software projects often start small and cute, but can quickly become unmanageable as requirements change. This transformation is usually due to the lack of an appropriate architecture, or an architecture that is not designed for future change.

The IDesign Method: An Overview
The IDesign method, developed by Juval Löwy, provides a systematic approach to creating a software architecture that will stand the test of time. Let’s explore its key principles.

Avoid functional decomposition
The first principle of IDesign is to avoid functional decomposition – the practice of translating requirements directly into services. For example, if you’re building an e-commerce platform, don’t create separate services for “user management”, “product catalogue” and “order processing” just because those are your main requirements. Instead, IDesign advocates a more thoughtful approach based on volatility.

Volatility based decomposition
IDesign focuses on identifying areas of volatility – aspects of the system that are likely to change over time. For example, in our e-commerce example, payment methods might be an area of volatility, as you may need to add new payment options in the future.

The three-step process:
Identify 3-5 core use cases
What your system does at its most basic level. For our e-commerce platform, these might be:

Browse and search for products
Manage shopping cart
Completing a purchase

Identify areas of volatility
Identify aspects of the system that are likely to change. In our e-commerce example:
Payment methods
Shipping options
Product recommendation algorithms

Define services
IDesign defines five types of services:
Client: Handles user interaction (e.g. web interface)
Manager: Orchestrates business use cases
Engine: Executes specific business logic
Resource Access: Handles data storage and retrieval
Utility: Provides cross-cutting functionality

For our e-commerce platform example we might have:

A ShoppingManager – to orchestrate the shopping process
A PaymentEngine – to handle different payment methods
A ProductCatalogAccess – to manage product data

Design Principles and Patterns

Great software is not written.
It’s designed.

Most systems don’t fail because of bad developers.
They fail because of bad design decisions made early — and scaled blindly.

This is the foundation every serious engineer and tech leader must master 👇

Design Principles & Patterns

🔹 SOLID

SRP – One class, one reason to change
OCP – Extend, don’t modify
LSP – Substitutions must be safe
ISP – Small, focused interfaces
DIP – Depend on abstractions, not concretes

SOLID isn’t theory. It’s how you avoid rewriting your system every 6 months.

🔹 GoF Design Patterns

1) Creational → Control how objects are created (Factory, Builder, Singleton)
2) Structural → Control how objects are composed (Adapter, Facade, Proxy)
3) Behavioral → Control how objects communicate (Strategy, Observer, Command)

Patterns are not “fancy code.”
They are battle-tested solutions to recurring problems.

🔹 DRY – Don’t Repeat Yourself
Duplication is a silent killer.
It multiplies bugs and slows teams.

🔹 KISS – Keep It Simple
Complexity is not intelligence.
Simplicity is.

🔹 MVC + Repository + Unit of Work
Clean separation of concerns.
Predictable codebases.
Scalable teams.

Reality check:

Frameworks change.
Languages change.
Trends change.

Principles don’t.

If you want to build:

Systems that scale
Teams that move fast
Products that survive years

Master the fundamentals.

Everything else is noise.

Azure Function Flex Consumption Plan and Key Vault

When we use the Azure Functions Flex Consumption plan, the platform dynamically manages the underlying infrastructure. This means the outbound IP addresses are not static or predictable in the same way they are with dedicated plan like App Service Environment.

The Private IP Address (172.25.1.187) is an internal, virtual network address within the Azure infrastructure and not a public, internet-routable IP address. Key vault’s firewall is designed to filter based on public IP addresses or specific virtual network rules. It wouldn’t accept, non-routable IP address in its allow list.

The correct way to solve this is to use a Virtual Network (VNet) service Endpoint or an Azure Private Endpoint. This method allows your Azure Function to securely connect to the Key Vault over the Azure backbone network without using Public IP addresses.

The Solution

The correct way to solve this is to use a Virtual Network (VNet) Service Endpoint or an Azure Private Endpoint. This method allows your Azure Function to securely connect to the Key Vault over the Azure backbone network without using public IP addresses.

Here’s how you can implement this:

  1. Integrate Your Azure Function with a Virtual Network
    First, you need to integrate your Azure Function App with a virtual network. This feature allows your function to access resources within a VNet. Since your function app is on a Flex Consumption plan, you’ll need to use the Regional VNet Integration feature.
  2. Configure a VNet Service Endpoint for Key Vault
    Once your function app is integrated into a VNet, you can configure a VNet Service Endpoint on your Key Vault. This feature extends the VNet’s private address space and identity to the Azure Key Vault. When a resource (like your function app) in that VNet attempts to access the Key Vault, the traffic stays on the Azure backbone network instead of going over the public internet.

Steps to configure the VNet Service Endpoint:

  • Go to your Azure Key Vault.
  • Navigate to the Networking blade.
  • Under the Firewalls and virtual networks tab, select Allow public access from specific virtual networks and IP addresses.
  • Click + Add existing virtual networks.
  • Select the virtual network and the subnet that your Azure Function is integrated with.
  • Enable the Service endpoint for Microsoft.KeyVault on the subnet.

(Alternative) Use a Private Endpoint
A more secure and private alternative is to use an Azure Private Endpoint. This creates a private network interface for your Key Vault in your VNet, assigning it a private IP address from your VNet’s address space. This makes the Key Vault accessible only from within your VNet.

    Steps to configure the Private Endpoint:

    • Go to your Azure Key Vault.
    • Navigate to the Networking blade.
    • Select the Private endpoint connections tab.
    • Click + Private endpoint.
    • Follow the wizard to create the private endpoint, linking it to your VNet and a specific subnet.
    • Update your function app’s code or configuration to use the private endpoint DNS name for the Key Vault.

    Recommendation: The VNet Service Endpoint approach is generally simpler to implement and is the standard solution for this scenario. The Private Endpoint offers a higher level of network isolation and is often preferred for more sensitive applications.

    This approach resolves the issue by bypassing the public IP address limitation of the Key Vault firewall and establishing a secure, private connection between your Azure Function and the Key Vault.

    Natural Language AI-Powered Smart UI

    Looking for real-world AI examples is a challenge and part of this challenge comes from Generative AI (GenAI) news dominating the media. It feels like every AI demo involves chatting with GenAI to produce content. The obligatory chat completion demo has started to become the to-do list of AI demo apps, and, to make matters worse, it’s selling AI short. GenAIs rely on large language models (LLMs), which are the brain behind natural language processing tasks. In this article, I’ll explore the opportunities presented by LLMs using a real-world research-and-development experiment. This experiment is part of on-going research into AI-enabled user interface components (aka .NET Smart Components) by Progress Software and Microsoft.

    Read more on code site