Securing Web APIs with Azure AD: Designing Authentication Schemes
Posts in this series:
- A Case Study
- Designing Authentication Schemes
- Authorizing Client Applications
- Building the Server
- Enabling Local Development
- Connecting External Clients
- Connecting Azure Clients
In our last post, I walked through my real-world scenario of needing to follow Zero Trust principles in securing microservice-based APIs inside (and outside) Azure. For our sample system, I'll simplify it a bit so that we have three applications:
- Server hosted in Azure
- Client hosted in Azure
- Client outside of Azure
The Server will be a "protected web API", while each client for simplification purposes will not include any authentication. While we could put some authentication in those clients, I'll leave it out for simplicity purposes.
The Azure client will be a simple web API that acts as a sort of "Backend-for Frontend" that merely calls through to the underlying Server API. We'll then use a Swagger UI through a browser to execute this Client -> Server chain.
The other "external" client will be a console application to simulate a daemon application that wouldn't have any kind of user authenticated regardless.
At this point, our choices of libraries and even project templates is highly contextual to our scenarios. There is no one-size-fits-all approach here, we have to understand exactly the needs of each application to determine what to use.
But first, we'll need to determine how our clients should authenticate against our server.
Choosing an OAuth 2.0 Flow
Looking at our authentication needs, we have two main use cases:
- Web API that calls web APIs
- Daemon app that calls web APIs
Luckily, Microsoft's documentation describes solutions for these (and many other) flows. The docs describe each scenario, the OAuth 2.0 flow and grant, and audience:
Scenario | OAuth 2.0 flow and grant | Audience |
---|---|---|
Daemon app that calls web APIs | Client credentials | App-only permissions that have no user and are used only in Azure AD organizations |
Web API that calls web APIs | On-behalf-of | Work or school accounts and personal accounts |
Easy peasy, right? Not quite! The first scenario matches by name and by audience (the clients will all be defined in Azure AD). However, the second doesn't quite match. We said in the previous post that our users don't live in Azure AD. They're either in custom on-prem systems with their own user stores, or moving to Auth0.
In either case, the identity provider is NOT Azure AD, it's Identity Server or Auth0. This means we can't use the On-Behalf-Of flow where the user's identity and permissions are propagated down. If we want our Server API to authorize based on the original user, we'll have to apply some additional authorization scheme on top of the normal OAuth 2.0 flow.
So where does that leave us? Essentially, our Azure client API no longer has a "user" that our server API can use in an authentication flow. This makes our client BFF another Daemon app.
Yes I could have made the Azure Client use Azure AD to show the OBO flow. But this series is a case study of a real scenario I had to design for, not an exhausting promotion of all things Azure AD.
With this in mind, we can mostly simplify (mostly) our authentication choices:
Application | Scenario |
---|---|
Azure Server | Protected web API |
Azure Client | Daemon app that calls web APIs |
External Client | Daemon app that calls web APIs |
Our two clients are now effectively the same OAuth 2.0 scenario - Client Credentials grant. This also simplifies greatly our requirements to protect our web API by removing the OBO flow, not trying to handle both users and daemon apps in the same server endpoints.
Now that we've got our authentication schemes defined, in the next post, we'll look at authorization options (and eventually, start implementing this stuff).