This is a post about having two kinds of authentication working at the same time in ASP.Net Core. But choosing which authentication method to use dynamically at runtime; based upon the criteria of your choice.
Already this sounds complicated; let's fix that. Perhaps I should describe my situation to you. I've an app which has two classes of user. One class, let's call them "customers" (because... uh... they're customers). The customers access our application via a public facing website. Traffic rolls through Cloudflare and into our application. The public facing URL is something fancy like https://mega-app.com. That's one class of user.
The other class of user we'll call "our peeps"; because they are us. We use the app that we build. Traffic from "us" comes from a different hostname; only addressable on our network. So URLs from requests that we make are more along the lines of https://strictly4mypeeps.io.
So far, so uncontroversial. Now it starts to get interesting. Our customers log into our application using their super secret credentials. It's cookie based authentication. But for our peeps we do something different. Having to enter your credentials each time you use the app is friction. It gets in the way. So for us we have Azure AD in the mix. Azure AD is how we authenticate ourselves; and that means we don't spend 5% of each working day entering credentials.
Now our delightful little application grew up in a simpler time. A time where you went to the marketplace, picked out some healthy looking servers, installed software upon them, got them attached to the internet, deployed an app onto them and said "hey presto, we're live!".
Way back when, we had some servers on the internet, that's how our customers got to our app. Our peeps, us, we went to other servers that lived on our network. So we had multiple instances of our app, deployed to different machines. The ones on the internet were configured to use cookie based auth, the ones on our internal network were Azure AD.
As I said, a simpler time.
We've been going through the process of cloudifying our app. Bye, bye servers, hello Docker and Kubernetes. So exciting! As we change the way our app is built and deployed; we've been thinking about whether the choices we make still make sense.
When it came to authentication, my initial thoughts were to continue the same road we're travelling; just in containers and pods. So where we had "internal" servers, we'd have "internal" pods, and where we'd have "external" servers we'd have external pods. I had the good fortune to be working with the amazingly talented Robski. Robski knows far more about K8s and networking than I'm ever likely to. He'd regularly say things like "ingress" and "MTLS" whilst I stared blankly at him. He definitely knows stuff.
Robski challenged my plans. "We don't need it. Have one pod that does both sorts of auth. If you do that, your implementation is simpler and scaling is more straightforward. You'll only need half the pods because you won't need internal and external ones; one pod can handle both sets of traffic. You'll save money."
I loved the idea but I didn't think that ASP.Net Core supported it. "It's just not a thing Robski; ASP.Net Core doesn't suppport it." Robski didn't believe me. That turned out to a very good thing. There followed a period of much googling and experimentation. One day of hunting in, I was still convinced there was no way to do it that would allow me to look in the mirror without self loathing. Then Robski sent me this:
It was a link to the amazing David Fowler talking about some API I'd never heard of called
SchemeSelector. It turned out that this was the starting point for exactly what we needed; a way to dynamically select an authentication scheme at runtime.
This API did end up landing in ASP.Net Core, but with the name
ForwardDefaultSelector. Not the most descriptive of names and I've struggled to find any documentation on it at all. What I did discover was an answer on StackOverflow by the marvellous Barbara Post. I was able to take the approach Barbara laid out and use it to my own ends. I ended up with this snippet of code added to my
If you look at this code it's doing these things:
- Registering three types of authentication: Cookie, Azure AD and "WhichAuthDoWeUse"
- Registers the default
Schemeto be "WhichAuthDoWeUse".
"WhichAuthDoWeUse" is effectively an
if statement that says, "if this is an external
Alongside this mechanism I added these extension methods:
Finally, I updated the
SpaController.cs (which serves initial requests to our Single Page Application) to cater for having two types of Auth in play:
The code above allows anonymous requests to land in our app through the
AllowAnonymous attribute. However, it checks the request when it comes in to see if:
- It's an internal request (i.e. the Request URL starts "https://strictly4mypeeps.io/")
- The current user is not authenticated.
In this case the user is redirected to the https://strictly4mypeeps.io/login-with-azure-ad route which is decorated with the
Authorize attribute. This will trigger authentication for our unauthenticated internal users and drive them through the Azure AD login process.
I'm so surprised that this approach hasn't yet been better documented on the (generally superb) ASP.Net Core docs. It's such a potentially useful approach; and in our case, money saving too! I hope the official docs feature something on this in future. If they do, and I've just missed it (possible!) then please hit me up in the comments.