Software developers have been dealing with the concept of users in their apps for many years, and many of us have implemented simple schemes for computing a hash of the password and rolling our own mechanisms and identity stores to support this. I don't think I have seen anyone recommend as a best practice that you should build your own identity system, and yet this is what people have been doing. Not saying it is impossible to do so, but even though you know what SQL injection is and have guarded your id database against those attacks there are probably a range of other vulnerabilities you aren't able to keep on top of unless you have more resources available to you than the average developer.
On the bright side, I have observed a push towards using Azure AD as the identity system many places in the past couple of years. Of course - this could be just me and the people/companies I run into. But in general people want SSO which requires some centralized component to be involved, and for corporate use Active Directory and/or Azure Active Directory frequently comes into play.
The basic single Azure AD tenant authentication scenario is something most developers should be able to implement fairly easily based on the samples provided by Microsoft. Call it the happy path scenario if you will. Unfortunately a lot of the use cases out there are not the basic single tenant setup. Maybe you want multi-tenant support. Maybe you need support for social identities like Facebook and Google. And maybe you want to provide different frontends for consumers, partners, etc. That's when things start to get more complicated :)
Even though I have my Microsoft goggles on I have no problem admitting there are great tools out there, made by other companies, for managing identities and securing resources. However since I have invested time and effort into the Azure technology stack it makes sense for me to try to implement this with MS tech, so I thought I would take a stab at building out my own sample using Azure AD B2C for Identity as a Service (IdaaS).
I have done a couple of posts on AAD B2C before, and of course you will also find info from Microsoft themselves on what Azure AD B2C is so I'm not going to do an introductory part on what the service can do. The name of the service does it no favors though since it seems to imply it's all about consumers. The identity teams in Redmond are now more often referring to "external identities" which is a better term in my opinion. (I would love to see it being a more generic "Azure AD Dev Edition" without a distinction between B2C and non-B2C, but it's not quite there yet. And maybe it needn't be either; that is however an entirely different discussion we will not go into now.)
If you want to learn the more basic parts of AAD B2C, and for that sake some more advanced things too, Microsoft has a thorough ebook you can go through:
And thus there is no need for further explanations right?
Thing is - before you start hacking the necessary pieces of code into your solution you need a strategy of sorts so that is where we will start.
Which identity providers (IdPs) will you support?
Do you want to support primarily social identities like Facebook, Google, etc. or do you want to primarly support corporate identities? Or a mix? Only Azure AD tenants or other corporate-based ones as well?
The adding of IdPs is entirely independent of your code, so that should be done in the AAD B2C portal before you start. You don't need to add all at once, but with none defined things are not going to work so add at least one.
For adding corporate identities I would recommend using the OpenID Connect option if you can. Both Azure AD and ADFS can be added this way, and it is easier than using custom policies. (More on that topic later on.)
The third of the major options is local accounts - these are identities defined locally in Azure AD B2C and provisioned by the AAD B2C service natively. Some users will want to silo their different logins for various reasons. Should you cater for these users as well? Personally I like having this option, but this is a design point you need to consider specifically for your use case.
Technically speaking the local accounts are also AAD accounts, but in a B2C version. In a regular AAD tenant users are assumed to be working for the same company - this could of course include external consultants as well, but users are aware of each other and who the company entity owning the identities is. This means that you have things like a Global Address List (GAL) where you can look up the phone number of other users and similar things. In an system where users are all external and independent of each other this would not be ok, and would probably violate several privacy laws, so there are restrictions in place backend that prevents one user from looking up the details of another user.
Being AAD accounts means you as the tenant owner and app developer can still use things like the Graph API to query the records and other actions, so it's still better than rolling your own database for local accounts. (As a side note - the MS Graph is not supported yet, and you will need to use the Azure AD Graph.)
How will the landing page of the SignUp and/or SignIn experience be?
Will you leave the choice of identity entirely up to the user, or will you guide (with more or less force), towards specific options?
For instance you could have a page asking "Are you a partner or consumer" and direct partners towards corporate identities whereas consumers will only get to choose between different social identities. At the same time it could be you have one-man shops as partners that aren't using Office 365 and Azure AD, and prefer a Google account instead.
You could also have an automated flow once you know the email address of the user through a process called home realm discovery. In this case you could have users type in their email address first, and if it's
send them directly to the Contoso login page. Do keep in mind to that there are some open-ended scenarios - just because an email address ends in gmail.com doesn't mean the identity has to be a Google account.
What needs to be customized, and what will work out of the box?
This is somewhat AAD B2C-specific since custom policies are an important part of achieving more advanced scenarios. You could however argue that some of the considerations would be valid even without AAD B2C. More specialized use cases will require more custom work with or without AAD B2C.
Take something like resetting passwords. This is a fairly common use case on web pages that rely on logging in. But if the user logs in with Facebook it's not like you can perform a reset - that would be something Facebook would need to provide. If you enable local accounts in AAD B2C you are responsible for that experience. Sure, you would "outsource" the task to Azure AD, but you would need to account for it in your UI. Luckily this is a simple feature that can be implemented in a standard policy in B2C, and as long as you send the user to the right page in the UI you might not need all the extras custom policies bring to the table.
This brings out a key point with Azure AD B2C - if the standard template provides what you need you should not build a custom policy "just because you can".
What would be a valid use case for a custom policy then? Maybe you need to validate some of the inputs the user types in when signing up and that validation needs to be done through an API call. That would require a custom policy. Maybe the user needs a code you have provided in advance to be able to sign up. That would require a custom policy. Basically, if you don't find it in the UI in the Azure Portal you need to think custom. Let's be realistic though - there are of course limits to what you can do in the custom space as well. You can do more, but you can't do everything :)
Authentication is the easy part. Well, easy might be stretching it; there are as you can see plenty of considerations on how to acquire your golden ticket (aka token). But once the user has a token then what? If you have a setup with partners and end-users it's probably fair to assume that they should not be able to access the same content. You will probably need to have different roles, and use that as a filtering mechanism. Plan this out before you hack along. (Figuring out details like whether you need two roles on the partner level, say "Partner Admin" and "Partner User", isn't needed. You can do that later on.)
With those considerations out of the way let's spec out what we want to implement.
We will build a web app that will provide services to employees, partners, and end-users/customers. For this we will support both Azure AD, social accounts, and local accounts. The web app will be implemented in .NET Core 2.1.
Make sure that you have
listed in the
The tooltip explains that you can choose between
for these accounts - I will go with email.
Let's create a very basic set of policies. We just want to make sure that we can sign up and sign in first. Move down to
are the parameters we want to collect from the user when they create their account. You can check every one of them if you like, but that means more typing when testing :)
Application claims are the parameters you want to be part of the token you issue when someone signs in:
What is the difference? Well, some attributes does not make sense to collect from the user, but makes sense for the app. Other attributes are only relevant to have on file backend. The sign up attributes get persisted to the user object, but you can also have ephemeral attributes that only live in the context of a session.
You can add additional custom attributes if you like, but you're still somewhat limited until you go for custom polices. For now we don't need any extras.
We don't need
Page UI customization
for now either so hit
on the bottom of the page.
To use policies, or B2C in any way, you need to create an application. Move to
to it being a Web App / Web API, and use
as the reply url.
Go back to the
section and click the policy you created a couple of moments ago. Select the application you just created, and make sure
is the reply url before clicking the
Now you get to sign up for an account in your tenant:
After creating the account you're automatically redirected to a page that shows you your token and the contents of it. (When you troubleshoot policies you will appreciate this feature.)
Wasn't that easy? You're creating identities and testing them without a single line of code written!
Next you should create a
, and a
Password reset policy
. I made the Sign-in more or less similar to Sign-up. With the password reset you don't really have that many options. Note that password reset only works with local accounts.
Actually, let's pull one more trick with the AAD B2C built-in policies. Head to
Sign-up or sign-in policies
and fill the forms out the same way. A "SuSi" policy produces a screen where the user chooses if they want to sign up or sign in thus sparing you a couple of calories as a coder when you're implementing your login links.
Configuring authentication in your web app
Now we can try to implement this in our own app. Go back to the app you registered in the B2C Portal. Copy the application id to Notepad or something.
We also need to register our reply url for our app - so check with port your app runs on:
Mine is running on port 44315 as you can see. So register
as a reply url:
What I didn't show you when we created the web app was that there is actually a wizard very suited for Azure AD B2C in Visual Studio 2017:
However you wouldn't be able to actually fill it out had you not gone through the AAD B2C portal first. Let's add the code that would have been generated the manual way.
First you need to add the following NuGet package:
The authentication is configured in
Add two usings:
Add the following to the ConfigureServices method:
.AddAzureADB2C(options => Configuration.Bind("AzureADB2C", options));
Add one line to the
Time to add the settings for our AAD B2C tenant - these can be found in
We also need a UI with a button for signing in.
Add a file called
with the following contents:
@inject IOptionsMonitor<AzureADB2COptions> AzureADB2COptions
var options = AzureADB2COptions.Get(AzureADB2CDefaults.AuthenticationScheme);
If you hit F5 now you will see a
button, and clicking it will take you to AAD B2C and return you to your web site afterwards. Nice and simple.
Hold on, it just says "Hello !" - there is no name there. Yes, due to how I configured my policies it doesn't line up with the claims expected to print out properly. This is purely a cosmetic issue that we will not care about right now. You are logged in.
In the web app as it is now this sign in action does not serve any purpose. You can browse everything whether you're authenticated or not.
While it is a silly example let's say only authenticated users can view our contact info.
Configuring authorization in your web app
and add an Authorize attribute to your Contact action:
public IActionResult Contact()
along with the other usings.
If you try to click Contact without being logged in you will be taken to the sign-in page to type in your credentials.
Ok, so now we have a very basic model for authentication (AuthN) and authorization (AuthZ) in our web app. We are however still a bit off with regards to our original plan - what now?
I'll add views for partners, customers and employees to my app.
Add methods in
public IActionResult Partner()
ViewData["Message"] = "Howdy partner!";
public IActionResult Customer()
ViewData["Message"] = "Welcome dear customer. How may we help you today.";
public IActionResult Employee()
ViewData["Message"] = "Get back to work!";
I use scaffolding to create views (right-click method, select
, and use the
Add a few menu items to
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Partner">Partner</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Customer">Customer</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Employee">Employee</a></li>
How to make sure only partners can access the partner page and so on?
This approach will work, in the sense that you will get access denied even if you sign in. Having role checks doesn't work if you do not have a way to get users into those roles which sort of breaks the basic setup…
If we can add the right claim we can make a policy for it. There doesn't seem to be a ready-made attribute for this, but luckily AAD B2C lets us customize this with little effort by adding custom attributes so go back to the AAD B2C Portal.
Navigate to the
section and click
. Add an attribute called
Edit your policy to include this attribute as both a
This will not retroactively set a value for existing users, so when testing the easy thing to do is to delete your user account in the
section, and run the sign up again.
Notice how there is a new textbox there? Type in "Partner" and create the user.
Since this is a custom attribute the name in the claim will be
will be applied as prefix to claims that are not built-in.
Go back to your code and create the following policy in
.AddAzureADB2C(options => Configuration.Bind("AzureADB2C", options));
For customers I chose to add Google and Microsoft, but of course you can register others too if you like.
The partner logins are a different beast to tackle. Sure, we have covered social identities already, but what about Azure AD? We have already added one tenant, but we definitely don't want to add all our partner's tenants manually.