I wrote a tiny library: POP Identity
(This is a repost from my personal blog.)
Many years ago, I decided to take advantage of the third-party "social" logins that were available in conjunction with the evolving ASP.NET frameworks. Being able to sign in to something using a Google or Facebook account is convenient. When I went down that road, the tough part is that it was so heavily baked into the Identity libraries and Entity Framework. After much digging and looking at source code, I was able to decouple it enough to use it in POP Forums, but what a pain. It evolved a little when ASP.NET Core finally shipped, but it was still a lot of magic.
A few months ago I started looking at ways to make the forums run in a multi-tenant environment, and the external login stuff just wasn't compatible, because it has to be configured at start up. That doesn't work when every tenant has different client ID's and secrets. I started to think about this as high level as possible. What was I really after? All I wanted was to get the third-party's ID for the user, and maybe the name and email if I could get it. Most of these services are using OAuth2, which is a pretty simple protocol to use, where you bounce the user off of their server, and exchange the resulting token for the claims you're looking for. All of that complexity seemed pretty unnecessary.
So I wrote a little library to make it work called POP Identity. I put the intention right in the readme:
This is for people who think that the existing ASP.NET Core external login system is too much magic, or too tightly coupled to Identity and/or EntityFramework. It didn't evolve much from the old OWIN days. It has the following goals:
- Be super light-weight, handing off just enough mundane detail to the library.
- Allow code to change client ID's, secrets and other parameters at request time, as opposed to the built-in framework that sets this all at startup. This makes it appropriate for multi-tenant situations.
- Allow the developer to persist the resulting data (external ID's, name, email, etc.) in whatever manner makes sense. This library has no persistence.
- Defer authorization logic to the developer. It doesn't setup any claims identity... that's up to you.
Phil Haack recently made the correct observation that it's pretty rare that you get identity claims from third-parties and make it durable for use in your application (a few days after I started this mini-project... timely!). There really isn't a need for auth schemes and more configuration in startup to enable all of this if you're not using the Identity libraries and the persistence that goes with it. My sample shows how you can get these basics and bake them into whatever you need them for. In POP Forums, I get a little more serious, first with a single controller action that redirects you to the appropriate service, then a callback action that either logs you in based on an ID and provider match, or starts a workflow to associate your social account with a new or existing account in the forum app. No references to the Identity libraries. The only part I'm leveraging from the framework is the sign-in mechanism that creates a cookie to identify the user. From then on, I use simple middleware to check that the user's request is legit and that they are in fact still a user in good standing (and cache the user data for the duration of the request). That might all require a longer post to describe, but the important part is the calls to POP Identity.
I haven't built anything for general use in a long time, and I remember why I don't want to ever write frameworks or generic libraries. Even if you're the clever idiot who figures out how to break it, you have to try and account for those situations. That's exhausting! I figured this was so limited in scope that it would be easy, but I started poking holes in it right away when I added it to the forums. For now though, it supports Google, Facebook, Microsoft and any generic OAuth2 provider that returns JWT's. Twitter still uses old OAuth, so I didn't bother with them.