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

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

See:

Register App to Azure AD

Automatic App registration to Azure AD by Visual Studio scaffolding

Automatic app registration by VS scaffolding Step 1

Automatic app registration by VS scaffolding Step 2

Automatic app registration by VS scaffolding Step 3

Automatic app registration by VS scaffolding Step 4

Automatic app registration by VS scaffolding Step 5

Azure AD OIDC

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

Azure AD SSO - Single Tenant Step 1

Azure AD SSO - Single Tenant Step 2

Azure AD SSO - Single Tenant Step 3

Azure AD SSO - Single Tenant Step 4

Azure AD SSO - Single Tenant Step 5

Azure AD SSO - Single Tenant Step 6

Azure AD SSO - Single Tenant Step 7

Azure AD SSO - Single Tenant Step 8

Azure AD SSO - Single Tenant Step 9

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

Scaffolding AspNetCore WebApp with Azure AD SSO - multi tenant

Microsoft Identity Web

Installing Microsoft Identity Web Template

As of June, 2020 Visual Studio scaffolding uses Azure AD OIDC (v1) not Microsoft Identity Web (v2)

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)

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:

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:

Dependencies (install both packages):

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.