Author : HASSAN MD TAREQ | Updated : 2020/06/22

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
Create tenant

https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-access-create-new-tenant

Switch tenant and create user

Switch Azure AD tenant and create user Step 1

Switch Azure AD tenant and create user Step 2

Switch Azure AD tenant and create user Step 3

Switch Azure AD tenant and create user Step 4

Switch Azure AD tenant and create user Step 5

Switch Azure AD tenant and create user Step 6

Switch Azure AD tenant and create user Step 7

Register App to Azure AD - automatic registration by VS 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

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

Register App to Azure AD - manual registration in Azure AD Portal

Notes:

  • unless the Web App calls a Web API, no certificate or secret is needed
  • if we register app in Azure AD Portal:
    • scaffold project (create app) with no authentication
    • copy Azure AD info (Domain, TenantId, ClientId etc.) and put it in user secrets
    • get Azure AD info from user secrets and configure WebApp in Startup class

Register WebApp in Azure AD portal Step 1

Register WebApp in Azure AD portal Step 2

Register WebApp in Azure AD portal Step 3

Register WebApp in Azure AD portal Step 4

Register WebApp in Azure AD portal Step 5

Register WebApp in Azure AD portal Step 6

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 Project 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": "https://login.microsoftonline.com/",
        "Domain": "hovermvc.onmicrosoft.com",
        "TenantId": "ec28698b-34de-4b7b-aa76-5a96dd63a0a4",
        "ClientId": "8d0943e1-2b98-41d0-baf3-4e250a7005f3",
        "CallbackPath": "/signin-oidc",
        "SignedOutCallbackPath ": "/signout-oidc"
    },

}

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 Microsoft Identity Web 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": "https://login.microsoftonline.com/",
        "Domain": "hovermvc.onmicrosoft.com",
        "TenantId": "ec28698b-34de-4b7b-aa76-5a96dd63a0a4",
        "ClientId": "8d0943e1-2b98-41d0-baf3-4e250a7005f3",
        "CallbackPath": "/signin-oidc",
        "SignedOutCallbackPath ": "/signout-oidc"
    },
	
    // ... ... ...
}

Now, add following code in Startup.cs

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Http;


public class Startup
{
	// ... ... ...
	
	public void ConfigureServices(IServiceCollection services)
	{
		//services.Configure<CookiePolicyOptions>(options =>
		//{
		//    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
		//    options.CheckConsentNeeded = context => true;
		//    options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
		//    // Handling SameSite cookie according to https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1
		//    options.HandleSameSiteCookieCompatibility();
		//});

		// Sign-in users with the Microsoft identity platform
		services.AddSignIn(Configuration);
		
		// Uncomment the following lines if you want your Web app to call a downstream API
		// services.AddWebAppCallsProtectedWebApi(Configuration, 
		//                                        new string[] { "user.read" }, 
		//                                        "AzureAd")
		//         .AddInMemoryTokenCaches();
		

		services.AddControllersWithViews(options =>
		{
			var policy = new AuthorizationPolicyBuilder()
				.RequireAuthenticatedUser()
				.Build();
			options.Filters.Add(new AuthorizeFilter(policy));
		});
		
        services.AddRazorPages().AddMicrosoftIdentityUI();
	}


	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		
        // ... ... ...
		
		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.

Login With Google

User secrets (Secret manager)

{
  "Authentication:Google": {
    "ClientId": "475096194844-l2j1b79g863nvk4584eo8nerbfh59jrs.apps.googleusercontent.com",
    "ClientSecret": "UtgK_ArtjbP8mLc8_6xKJ9Oj"
  }
}

Settings in Startup.ConfigureServices()

services.AddAuthentication().AddGoogle(options =>
{
	IConfigurationSection googleAuthNSection =
		Configuration.GetSection("Authentication:Google");

	options.ClientId = googleAuthNSection["ClientId"];
	options.ClientSecret = googleAuthNSection["ClientSecret"];
});

Create a Google API Console project and client ID

Google authentication - create client id and secret Step 1

Google authentication - create client id and secret Step 2

Google authentication - create client id and secret Step 3

Google authentication - create client id and secret Step 4

Google authentication - create client id and secret Step 5

Google authentication - create client id and secret Step 6

Google authentication - create client id and secret Step 7

Google authentication - create client id and secret Step 8

Google authentication - create client id and secret Step 9

Google authentication - create client id and secret Step 10

Google authentication - create client id and secret Step 11

Google authentication - create client id and secret Step 12

Google authentication - create client id and secret Step 13

Scaffold project and add Google login

Google authentication - adding Google login to project Step 1

Google authentication - adding Google login to project Step 2

Google authentication - adding Google login to project Step 3

Google authentication - adding Google login to project Step 4

Google authentication - adding Google login to project Step 5

Google authentication - adding Google login to project Step 6

Google authentication - adding Google login to project Step 7

Google authentication - adding Google login to project Step 8

Google authentication - adding Google login to project Step 9

Google authentication - adding Google login to project Step 10

Google authentication - adding Google login to project Step 11

Google authentication - adding Google login to project Step 12

Google authentication - adding Google login to project Step 13

Google authentication - adding Google login to project Step 14

Login With Facebook

https://jakeydocs.readthedocs.io/en/latest/security/authentication/sociallogins.html

Login With IdentityServer4

Resources: IdentityServer4 Login in dev-handy-sites#identity-server

Online temporary IS4 for testing: https://demo.identityserver.io/

Locally running IdentityServer4 project

Create MVC Client App

  • The whole idea of relying on Azure AD for user authentication is to avoid user management in your application
  • Add MVC Client App to the solution
  • Find Port no (need to configure cleint in IdentityServer4 project)
    • Open: Properties/launchSettings.json
    • "sslPort": xxx => RedirectUris = http://localhost:xxx/signin-oidc
  • See: http://docs.identityserver.io/en/3.1.0/topics/clients.html

Configure cleint (MVC Client App) in IdentityServer4 project

Config.cs

// ... ... ...

public static IEnumerable<Client> Clients =>
	new Client[]
	{


	  new Client
		{
			ClientId = "Foo_Client",
			ClientName = "Foo Client",

			AllowedGrantTypes = GrantTypes.Code,
			RequirePkce = true,
			ClientSecrets = { new Secret("hovermind.foo".Sha256()) },

			RedirectUris = { "http://localhost:xxx/signin-oidc" },
			FrontChannelLogoutUri = "http://localhost:xxx/signout-oidc",
			PostLogoutRedirectUris = { "http://localhost:xxx/signout-callback-oidc" },

			AllowOfflineAccess = true,
			AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile },
			AlwaysIncludeUserClaimsInIdToken = true,
		},


	};
	
// ... ... ...

Notes:

  • End points (signin-oidc, signout-oidc, signout-callback-oidc) will be handled by OpenID Middgleware of MVC Client App

Install nuget package to MVC Client App

Package Manager Console > Select MVC Client App

Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect

Add user secrets to FooClient WebApp

Right click on FooClient WebApp > ‘Manage User Secrets’

secrets.json

{
  "IDP_IS4": {
    "ClientId": "Foo_Client",
    "ClientSecret": "hovermind.foo"
  }
}

Notes:

  • IDP: Identity Provider
  • IS4: IndentityServer4

Get client ID and Secret uing IConfiguration

public class Startup
{
    public IConfiguration Configuration { get; }

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }


    public void ConfigureServices(IServiceCollection services)
    {
		var clientId = Configuration["IDP_IS4:ClientId"];
		var clientSecret = Configuration["IDP_IS4:ClientSecret"];
		
        // ... ... ...

    }

    public void Configure(IApplicationBuilder app)
    {
        // ... ... ...
    }
}

Use OpenIdConnect Middleware

Startup.cs

public class Startup
{

    public IConfiguration Configuration { get; }

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

	public void ConfigureServices(IServiceCollection services)
	{
		
		// get client id and secret from user secrets
		var clientId = Configuration["IDP_IS4:ClientId"];
		var clientSecret = Configuration["IDP_IS4:ClientSecret"];
	
	
		services.AddControllersWithViews();

		//JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
		//JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

		services.AddAuthentication(options =>
		{
			options.DefaultScheme = "Cookies";
			options.DefaultChallengeScheme = "oidc";
		})
			.AddCookie("Cookies")
			.AddOpenIdConnect("oidc", options =>
			{
				options.Authority = "https://localhost:5001";
				options.ClientId = clientId;
				options.ClientSecret = clientSecret;
				options.ResponseType = "code";

				options.SaveTokens = true;

				options.Scope.Add(IdentityServerConstants.StandardScopes.Profile);
				options.Scope.Add(IdentityServerConstants.StandardScopes.OpenId);
				options.Scope.Add(IdentityServerConstants.StandardScopes.OfflineAccess);
			});
			
            services.AddAuthorization();
			
			//... ... ...
	}

	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		if (env.IsDevelopment())
		{
			app.UseDeveloperExceptionPage();
		}
		else
		{
			app.UseExceptionHandler("/Home/Error");
		}

		app.UseStaticFiles();

		app.UseRouting();
		
		app.UseAuthentication();
		app.UseAuthorization();

		app.UseEndpoints(endpoints =>
		{
			endpoints.MapDefaultControllerRoute().RequireAuthorization();
		});
	}
}

Now, Start Multiple projects

More Demos:

More OpenID Connect Providers