Until now, this blog was entirely a read-only site. Content is stored in a database, and the app reads from that DB, but there was no code for writing to the DB. In order to update content, I''d manually write SQL queries. I needed to be able to secure some features of the site so I could get away from that practice.
Here were my goals for this:
I worked off this helpdoc at docs.microsoft.com. It links to a sample project I could use as a reference, which was useful while troubleshooting.
Step 1 was to install Nuget package Microsoft.AspNet.Core.Authentication.AzureAD.UI.
To use Azure AD auth, you need to create an App Registration. I did mine point-and-click in the Azure Portal.
More info on how to do this at docs.microsoft.com. By the end of it, I had a TenantId and a ClientId. I also had to make sure my redirect urls were correct - I needed danschnau.com/oidc-login for prod and localhost:8888/oidc-login for local development.
In my ASP.NET Core 3.1 app, I plugged in a few settings into my appsettings.json. These were copied from the sample project.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
"TenantId": "TENANT_ID_GOES_HERE",
"ClientId": "CLIENT_ID_GOES_HERE",
"CallbackPath": "/signin-oidc"
},
Startup.csConfigure ChangesIn Configure, I added app.UseAuthorization() and app.UseAuthentication().
// This method gets called by the runtime. Use this method to configure
// the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// snip
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { /* snip */ });
}
ConfigureServices ChangesIn ConfigureServices, I added a few things.
I changed services.ConfigureMvc() to instead to a bit of Authorization configuration:
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
I also had to call services.AddAuthentication():
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
Then I had to call services.AddAuthorization, specifying that the only way to be authorized is to have a claim of my email address.
services.AddAuthorization(options =>
{
options.AddPolicy("adminpolicy", o =>
{
o.RequireAssertion(authorizationHandlerContext =>
authorizationHandlerContext.User
.HasClaim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "dsschnau@gmail.com"));
});
});
It took a bit of trial and error to get that working.
[AllowAnonymous] To Public ControllersThis blog is a pretty vanilla MVC app. All the Controllers that service public-facing pages got the [AllowAnonmyous] tag put on their clases.
[AllowAnonymous]
public class HomeController : Controller
Then I could force a Controller to lock down access to only me:
[Authorize("adminpolicy")]
public class AdminController : Controller
That was it! All done.
This all worked well, taking 2-3 hours to implement this. Having examples, well-written documentation, and helpful error messages, I was never stuck troubleshooting for too long.
There''s more I'd like to do around Authentication/Authorization on Dan Schnau Dot Com in the future - adding the ability to leave comments comes to mind.
If you have feedback, questions or advice - email me at dsschnau@gmail.com.