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
- id:
See:
Register App to Azure AD
- Register App to Azure AD in Azure portal
- 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 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
- Dependency:
Install-Package Microsoft.AspNetCore.Authentication.Google
- Links:
- https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins
- https://console.developers.google.com/cloud-resource-manager
- https://console.developers.google.com/projectselector2
- https://developers.google.com/identity/sign-in/web/sign-in#before_you_begin
- Google auth in blazor app: https://www.freecodecamp.org/news/how-to-implement-google-authentication-and-authorization-in-server-side-blazor-app/
- FYI, credentials used in creating demo app (screenshots) will be deleted (don’t try to use it, LoL)
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
Scaffold project and add Google login
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
- Prerequisite: IdentityServer4 project with in-memory store
- This is for demo purpose only & we are gonna run both IdentityServer4 project and MVC Client app in same solution
- Right click on Solution > Properties > Startup Project > Multiple Start Up projects
- Change ‘None’ to ‘Start’
- You might get error:
- Error loading external login information.
System.Net.Http.HttpRequestException: No connection could be made because the target machine actively refused it
- https://stackoverflow.com/questions/50992544/identityserver4-with-asp-net-core-2-1-identity-error-loading-external-login-in
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
- Open:
- 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:
- https://github.com/IdentityServer/IdentityServer4/tree/main/samples/Clients/src/MvcCode
- https://github.com/IdentityServer/IdentityServer4/tree/main/samples/Quickstarts/3_AspNetCoreAndApis/src/MvcClient
- https://github.com/IdentityServer/IdentityServer4/tree/main/samples/Quickstarts/2_InteractiveAspNetCore/src/MvcClient
More OpenID Connect Providers
- Others (list of providers: Github, Amazon, Apple, LinkedIn, ….): https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
- Okta: https://developer.okta.com/blog/2019/11/15/aspnet-core-3-mvc-secure-authentication