Author : MD TAREQ HASSAN | Updated : 2021/04/18
Prerequisites
See: web-security/openid-connect
Github repository
Demo projects used in this page: https://github.com/hovermind/AspNetCoreOIDC
OpenIdConnect Middleware
- There is OpenIdConnect Middleware in ASP.Net Core
Startup.ConfigureServices()
:services.AddOpenIdConnect(options => {} )
services.AddAuthorization(options => {} )
- use
options
to configure
Startup.Configure()
app.UseAuthentication()
app.UseAuthorization()
Dependencies (Nuget packages):
Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect
Using (namespaces)
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
Preparing Azure AD
A new tenant and new user will be created for demo purposes
- Demo/Test tenant:
hovermvc
(organization name: Hover) - Demo/Test user
- id:
foo@hovermvc.onmicrosoft.com
- password:
HOV3R.mind
- Test user will be used to login to AzureAD
- id:
See:
Register App to Azure AD
- Register App to Azure AD in Azure portal (do not use implicit flow)
- Automatic App registration to Azure AD by Visual Studio scaffolding (see below)
Automatic App registration to Azure AD by Visual Studio scaffolding
- If we add Azure AD account to Visual Studio (VS), then that Azure AD domain will be available in the domain dropdown list
- While creating WebApp: Authentication > Change
- Work or School Accounts > Domain
- VS will perform following automatically:
- gets TenantId (we added Azure AD account to VS)
- registers WebApp and gets ClientId
- Should do after scaffolding WebApp:
- VS will put Azure AD info in
appsettings.json
appsettings.json
will be commited to Source controll (Git) & therefore we should not keep those sensitive info here- Transfer Azure AD info to user secrets or Azure Key Vault
- VS will put Azure AD info in
Azure AD OIDC
- See: what is active directory and Azure AD
- Azure AD OIDC: replaced by “Microsoft Identity Web” (see below)
- Azure AD:
- Work and school accounts: usually from an organization that has an Azure Active Directory tenant
- Personal accounts (including outlook.com, live.com, and others)
- Azure AD B2C:
- B2C == Business to cuntomer
- Social or Local Accounts
- your customers use their preferred social, enterprise, or local account identities to get single sign-on access to your applications and APIs
The whole idea of relying on Azure AD for user authentication is to avoid user management in your application
Scaffolding AspNetCore WebApp with Azure AD OIDC single tenant
Scaffolding AspNetCore WebApp with Azure AD OIDC multi tenant Same as single tenant, select ‘multi tenant’ in Authentication > Change > Work and School Accounts > Multiple Organizations
Microsoft Identity Web
- Replaces Azure AD OIDC
- Microsoft identity platform is an evolution of the Azure Active Directory (Azure AD) developer platform
- Unified Microsoft identity platform
- Links:
- Samples: https://github.com/AzureAD/microsoft-identity-web/wiki/web-app-samples
- Nuget:
Installing Microsoft Identity Web Template
As of June, 2020 Visual Studio scaffolding uses Azure AD OIDC (v1) not Microsoft Identity Web (v2)
- To use Microsoft Identity Web, we need to manually install Microsoft Identity Web Template
- Installing template:
- command (dotnet cli):
dotnet new --install Microsoft.Identity.Web.ProjectTemplates::0.1.2
- dotnet new command doc: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new
- Nuget: https://www.nuget.org/packages/Microsoft.Identity.Web.ProjectTemplates/
- command (dotnet cli):
- using template: https://github.com/AzureAD/microsoft-identity-web/wiki/web-app-template
Scaffolding PKCE flow with Microsoft Identity Web
(The whole idea of relying on Azure AD for user authentication is to avoid user management in your application)
- Create empty solution
- Execute dotnet cli command in PMC:
dotnet new mvc2 --name IdentityWeb.Scaffolding --auth SingleOrg --output ./IdentityWeb.Scaffolding
dotnet new mvc2 --name IdentityWeb.Scaffolding --auth MultiOrg --output ./IdentityWeb.Scaffolding
- Project will be created in solution folder
- Right click on solution > Add > Existing project…
- Browse to solution folder > Select project
- Open
appsettings.json
> provide Azure AD information - For simplicity, Azure AD info is in
appsettings.json
, should put sensitive info into user secrets. See: </aspnet-core/app-secrets>
Azure AD info example:
{
"AzureAd": {
"Instance": "http://login.microsoftonline.com/",
"Domain": "xyz.onmicrosoft.com",
"ClientId": "xxxxxxxxx",
"ClientSecret": "xxxxxxxxx",
"TenantId": "xxxxxxxxx",
"CallbackPath": "/signin-oidc",
"RequireHttpsMetadata": "false",
"ResponseType": "code",
"usepkce": "true"
},
}
Notes:
- template might not provide SignedOutCallbackPath, if so add
"SignedOutCallbackPath ": "/signout-oidc"
- If SignedOutCallbackPath is absent in
appsettings.json
then sign out would not work
Adding PKCE flow to existing project
(The whole idea of relying on Azure AD for user authentication is to avoid user management in your application)
Existing WebApp:
- Assuming: the app was created (Scaffoldded) with no authentication
- Result: after adding Microsoft Identity Web SSO, when user will try to access the app, AzureAD login will pop-up
Dependencies (install both packages):
Install-Package Microsoft.Identity.Web -Version 0.1.2-preview
Install-Package Microsoft.Identity.Web.UI -Version 0.1.2-preview
- 0.1.2-preview => check for update and install latest version
For simplicity, we are gonna put Azure AD info in appsettings.json
. Best practice is putting Azure AD info in user secrets or Azure Key Vault.
appsettings.json
(Details: appsettings.json
example)
{
"AzureAd": {
"Instance": "http://login.microsoftonline.com/",
"Domain": "xyz.onmicrosoft.com",
"ClientId": "xxxxxxxxx",
"ClientSecret": "xxxxxxxxx",
"TenantId": "xxxxxxxxx",
"CallbackPath": "/signin-oidc",
"RequireHttpsMetadata": "false",
"ResponseType": "code",
"usepkce": "true"
},
}
Now, add following code in Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Http;
namespace WebApp_OpenIDConnect_DotNet
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// services.AddSignIn(Configuration);
//services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
//services.AddControllersWithViews(options =>
//{
// var policy = new AuthorizationPolicyBuilder()
// .RequireAuthenticatedUser()
// .Build();
// options.Filters.Add(new AuthorizeFilter(policy));
//});
// services.AddRazorPages().AddMicrosoftIdentityUI();
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddRazorPages().AddMicrosoftIdentityUI();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
}
Views/Shared/_LoginPartial.cshtml
(Create if not present)
@using System.Security.Principal
<ul class="navbar-nav">
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<span class="navbar-text text-dark">Hello @User.Identity.Name!</span>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a>
</li>
}
</ul>
Add login partial to Views/Shared/_Layout.cshtml
: <partial name="_LoginPartial" />
// ... ... ...
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<partial name="_LoginPartial" />
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
// ... ... ...
Run the project and it should work.