ASP.NET Core 3.1: IdentityServer4: ASP.NET Identity: Invalid Username or Password - identityserver4

I have an ASP.Net Core 3.1 web application with IdentityServer4 and ASP.Net Identity implemented. I have configured IdentityServer4 to use Sql Server and I am able to seed an admin user into the sql server's AspNetUsers table. All this works fine. When I run my application, IdentityServer4's home page(QuickUI) is shown. When I try to see the claims for my current session, I'm prompted with a login screen. I try using the seeded admin user and password and it keeps telling me it's an invalid username or password.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var connectionString = Configuration.GetConnectionString("IdentityServerDatabase");
var migrationAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddAspNetIdentity<ApplicationUser>()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationAssembly));
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationDbContext context)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
context.Database.Migrate(); //seed an admin user
DatabaseInitializer.PopulateIdentityServer(app); //this just adds clients and their scopes to the IDS4 tables
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
app.UseIdentityServer();
}
ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
this.SeedSuperUser(builder);
}
private void SeedSuperUser(ModelBuilder builder)
{
string ADMIN_ID = "02174cf0–9412–4cfe-afbf-59f706d72cf6";
string ROLE_ID = "341743f0-asd2–42de-afbf-59kmkkmk72cf6";
//seed admin role
builder.Entity<IdentityRole>().HasData(new IdentityRole
{
Name = "SuperAdmin",
NormalizedName = "SUPERADMIN",
Id = ROLE_ID,
ConcurrencyStamp = ROLE_ID
});
//create user
var appUser = new ApplicationUser
{
Id = ADMIN_ID,
Email = "someemail",
EmailConfirmed = true,
UserName = "someemail",
NormalizedUserName = "someemail",
LockoutEnabled = false, NormalizedEmail = "someemail"
};
//set user password
PasswordHasher<ApplicationUser> ph = new PasswordHasher<ApplicationUser>();
appUser.PasswordHash = ph.HashPassword(appUser, "somepassword");
//seed user
builder.Entity<ApplicationUser>().HasData(appUser);
//set user role to admin
builder.Entity<IdentityUserRole<string>>().HasData(new IdentityUserRole<string>
{
RoleId = ROLE_ID,
UserId = ADMIN_ID
});
}
}
I'm not sure where the issue is; the seeded user is in the AspNetUsers table.

Related

Role based authorization policy does not work

I currently have ASP.NET Core Web API back-end. I'm using react google login, it works fine. Now I want to implement role-based authorization to authorize some back-end api.
When I use[Authorize], it works fine.
But I use [Authorize(Policy = "readpolicy")], it can not hit the method.
I tried lots of approaches together, therefore the Program.cs looks terrible. Below What I tried, :
The entire Program.cs, anyone knows and how to fix the problem? Even help me to reorganize the code what I need and don't need and I'm not sure that the sequence of these services functions is correct.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using WTSS_Portal_Middleware.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json")
.Build();
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
}
);
builder.Services.AddDbContext<TSTWTSSPortalContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddDbContext<UserDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("UserDbConnection")));
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "WTSS_Portal_Middleware", Version = "v1" });
});
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer()
.AddCookie()
;
builder.Services.AddTransient<IConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerOptions>();
builder.Services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<UserDbContext>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("readpolicy",
policy => policy.RequireRole("WTSS support", "System owner", "Regular user", "Company owner", "Unauthenticated"));
options.AddPolicy("writepolicy",
policy => policy.RequireRole("WTSS support", "System owner"));
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "WTSS_Portal_Middleware V1");
c.RoutePrefix = string.Empty;
});
}
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseHttpsRedirection();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
assign role to logged in user
ClaimsIdentity identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, payload.Name),
new Claim(ClaimTypes.Role, "Regular user") //default role
}, JwtBearerDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var login = HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, principal);

CORS Policy Not Working in IdentityServer4

I am using IdentityServer4 (IS4) to connect to AzureAD for authentication. I have created the app on AzureAD and used the correct ClientID and Tenant ID as well.
I am getting the following error while signing in:
[15:13:04 Information] Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler
AuthenticationScheme: OpenIdConnect was challenged.
[15:13:06 Debug] IdentityServer4.Hosting.CorsPolicyProvider
CORS request made for path: /signin-oidc from origin: https://login.microsoftonline.com but was ignored because path was not for an allowed IdentityServer CORS endpoint
[15:13:06 Information] Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler
AuthenticationScheme: Identity.External signed in.
Please, I requesting to guide me, as in what is going wrong here.
Here's how my entire Startup.cs looks like:
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using IdentityServerHost.Quickstart.UI;
using System.Reflection;
using IdentityServer.Models;
using Microsoft.AspNetCore.Identity;
using IdentityServer.Data;
using IdentityServer4.Configuration;
using System;
using Microsoft.AspNetCore.Authentication;
using IdentityServer4.Services;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
namespace IdentityServer
{
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("DefaultConnection");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddControllersWithViews();
services.AddDbContext<IdentityServerContext>(options =>
options.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly))
);
services.AddDbContext<Data.ConfigurationDbContext>(options => options.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedEmail = false;
//New added
options.Password.RequiredLength = 4;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireDigit = false;
options.Password.RequireNonAlphanumeric = false;
options.Lockout.AllowedForNewUsers = true;
options.Lockout.DefaultLockoutTimeSpan = new TimeSpan(0, 15, 00);
options.Lockout.MaxFailedAccessAttempts = 5;
})
.AddEntityFrameworkStores<IdentityServerContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";
options.Authentication = new IdentityServer4.Configuration.AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
CookieSlidingExpiration = true
};
})
//.AddTestUsers(TestUsers.Users)
// this adds the config data from DB (clients, resources, CORS)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder => builder.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
})
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<IdentityProfileService>();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
var autBuilder = services.AddAuthentication();
//Azure AD
autBuilder.AddAzureAd(options => Configuration.Bind("AzureAd", options));
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
/*
services.AddAuthentication()
.AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
// register your IdentityServer with Google at https://console.developers.google.com
// enable the Google+ API
// set the redirect URI to https://localhost:5001/signin-google
options.ClientId = "copy client ID from Google here";
options.ClientSecret = "copy client secret from Google here";
});
*/
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
}
And the Azure Extension.cs
using System;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
{
public static class AzureAdAuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
=> builder.AddAzureAd(_ => { });
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
builder.AddOpenIdConnect();
return builder;
}
private class ConfigureAzureOptions: IConfigureNamedOptions<OpenIdConnectOptions>
{
private readonly AzureAdOptions _azureOptions;
public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
{
_azureOptions = azureOptions.Value;
}
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
}
public void Configure(OpenIdConnectOptions options)
{
Configure(Options.DefaultName, options);
}
}
}
}
And the appsettings.json
Appsettings.json
{
"ConnectionStrings": {
//"DefaultConnection": "connectiong_string"
},
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "",
"TenantId": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"ClientId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"CallbackPath": "/signin-oidc"
},
"TimeSettings": {
"AbsoluteRefreshTokenLifetime": 15552000,
"SlidingRefreshTokenLifetime": 1296000,
"IdentityTokenLifetime": 300,
"AccessTokenLifetime": 300,
"AuthorizationCodeLifetime": 300
}
}
IdentityServer has its own CORS settings for requests made by its clients.
You use the AllowedCorsOrigins collection on the client configuration to set this up. Simply add the origin of the client to the collection and the default configuration in IdentityServer will consult these values to allow cross-origin calls from the origins.
new Client
{
...
AllowedCorsOrigins = new List<string>
{
"http://www.myclient.com"
}
}
Hard to tell, one thought is if the use of "CallbackPath": "/signin-oidc" should be changed to some other URL to not conflict with other things that uses /signin-oidc.
For example in the code here they use:
.AddOpenIdConnect("aad", "Sign-in with Azure AD", options =>
{
...
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.ResponseType = "id_token";
options.CallbackPath = "/signin-aad";
options.SignedOutCallbackPath = "/signout-callback-aad";
options.RemoteSignOutPath = "/signout-aad";

.Net Core Identity seed data: unable to login using credentials seeded

I have my Initialiser setup and everything seems to run correctly and all the details are saved to the database but when I try to log in it via the webapp it fails everytime. When I run the debugger in the login controller it returns {Failed} after this is hit:
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
Initialiser:
public class DbInitialiser : IDbInitialiser
{
private readonly ApplicationDbContext _db;
private readonly UserManager<IdentityUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public DbInitialiser(UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, ApplicationDbContext db)
{
_db = db;
_userManager = userManager;
_roleManager = roleManager;
}
public void Initialise()
{
try
{
if (_db.Database.GetPendingMigrations().Count() > 0)
{
_db.Database.Migrate();
}
}
catch (Exception ex)
{
}
if (_db.Roles.Any(r => r.Name == "Admin")) return;
_roleManager.CreateAsync(new IdentityRole("Admin")).GetAwaiter().GetResult();//makes sure this executes before proceceding with anything else
_roleManager.CreateAsync(new IdentityRole("Manager")).GetAwaiter().GetResult();
_userManager.CreateAsync(new Employee
{
UserName = "Admin",
Email = "admin#gmail.com",
EmailConfirmed = true,
TwoFactorEnabled = false,
PhoneNumberConfirmed = true
//can set other properties, this is for the initial setup
}, "Abc123!Abc123!").GetAwaiter().GetResult();
IdentityUser user = _db.Users.Where(u => u.Email == "admin#gmail.com").FirstOrDefault();
_userManager.AddToRoleAsync(user, "Admin").GetAwaiter().GetResult();
}
(My Employee class extends IdentityUser)
I have checked all my password requirements as mentioned in other similar posts so I know it isnt to do with that and when I check in SSMS all the data for the user is there in aspnetusers so I am not sure why it wont let me login to the admin user that is seeded
When I run the debugger in the login controller it returns {Failed}
In the source code of SignInManager<TUser>.PasswordSignInAsync method, we can find it would check the user based on the provided userName.
public virtual async Task<SignInResult> PasswordSignInAsync(string userName, string password,
bool isPersistent, bool lockoutOnFailure)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
}
In your code, you set UserName with "Admin" that is not same as Email with "admin#gmail.com". If user login with email account, the code snippet var user = await UserManager.FindByNameAsync(userName); would return null, which cause the issue.
To fix it, you can try to set UserName with same value of Email (admin#gmail.com). Or modify the login code logic to find user by the Email, then sign in that user with password, like below.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var user = await _userManager.FindByEmailAsync(Input.Email);
//var istrue = await _userManager.CheckPasswordAsync(user, Input.Password);
var result = await _signInManager.PasswordSignInAsync(user, Input.Password, Input.RememberMe, lockoutOnFailure: true);
//var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);

Getting ArgumentOutOfRangeException error Idenity Server4

We are using Entity framework core with Identity server4 and getting the ArgumentOutOfRangeException when calling the token endpoint via code. Here is our ConfigurationServices method in Identity server project:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
string connectionString = Configuration.GetConnectionString("IdentityServer");
var rsaProvider = new RSACryptoServiceProvider(512);
SecurityKey key = new RsaSecurityKey(rsaProvider);
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(key, SecurityAlgorithms.RsaSha512Signature);
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddIdentityServer()
.AddSigningCredential(credentials)
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
}) // this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
// options.EnableTokenCleanup = true;
// options.TokenCleanupInterval = 30;
});
// Add S3 to the ASP.NET Core dependency injection framework.
services.AddAWSService<Amazon.S3.IAmazonS3>();
}
Here is our client application that calling identity server project("http://localhost:3000/"):
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Get(string client, string secret)
{
IActionResult result = null;
//discover endpoints from metadata
var disco = await DiscoveryClient.GetAsync("http://localhost:3000/");
if (disco.IsError)
{
result = NotFound(disco.Error);
return result;
}
//request token
var tokenClient = new TokenClient(disco.TokenEndpoint, client, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync(scope: "sup");
if (tokenResponse.IsError)
{
result = NotFound(tokenResponse.Error);
}
result = Ok(tokenResponse.Json);
return result;
}
Here is error we have got:

Connecting IdentityServer4 with Azure Active Directory

As one of my requirements, I am supposed to connect the IdentitySever with an Active Directory with existing users and claims. So far I managed to create an App Registration in the Azure Portal. So I have an Appication ID and also configured an API Key. Further, I have a list of Endpoints:
https://login.windows.net/{ad_guid}/federationmetadata/2007-06/federationmetadata.xml
https://login.windows.net/{ad_guid}/wsfed
https://login.windows.net/{ad_guid}/saml2
https://login.windows.net/{ad_guid}/saml2
https://graph.windows.net/{ad_guid}
https://login.windows.net/{ad_guid}/oauth2/token
https://login.windows.net/{ad_guid}/oauth2/authorize
I can get the OpenID configuration with
https://login.windows.net/{ad_guid}/.well-known/openid-configuration
According to the documentation from Microsoft I should now configure the endpoint like this:
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
var uri = "https://login.windows.net/{0}";
var instance = configuration["AzureAD:Instance"];
var authority = string.Format(CultureInfo.InvariantCulture, uri, instance);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
DisplayName = "Azure Active Directory",
AuthenticationScheme = "AzureAD",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = configuration["AzureAD:AppId"],
Authority = authority,
Scope = {"openid", "email"}
});
For some reason this is not working. Any ideas what I might have missed?
apparently, I had it almost right. here is my solution:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme =
IdentityServerConstants.DefaultCookieAuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
public static OpenIdConnectOptions CreateAzureAdOptions(X509Certificate2 certificate2, IConfiguration configuration)
{
return new OpenIdConnectOptions
{
DisplayName = "Azure Active Directory",
AuthenticationScheme = "Azure",
ClientId = configuration["OpenId:AzureAD:AppId"],
Authority = string.Format(CultureInfo.InvariantCulture, "https://login.windows.net/{0}", configuration["OpenId:AzureAD:Instance"]),
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
},
// https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-token-and-claims
Scope = {"openid", "email", "roles", "groups"},
Events = new OpenIdConnectEvents
{
OnRemoteFailure = context => HandleRemoteFailure(context)
},
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme
};
}
private static Task HandleRemoteFailure(FailureContext context)
{
Log.Error(context.Failure, "Azure AD Remote Failure");
context.Response.Redirect("/accessdenied");
context.HandleResponse();
return Task.FromResult(0);
}

Resources