Htttp 400 Bad Request Request Header is too long - asp.net-core

I am currently developing an application using Asp.net core 5.0 and Identity server 4.My OIDC authentication flow handled by Microsoft.AspNetCore.Authentication.OpenIdConnect. I deployed my application into IIS and I am getting my login screen. But after login I got the Http 400 Bad request error.Error. I checked my application cookie it contains many AspNetCore.OpenIdConnect.Nonce cookies. I deleted the cookies but doesn't solve my issue. What is the proper solution to handle this solution. I tried this but doesn't help me. I will share the code below
MVC Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddScoped<RenewToken>();
services.AddAuthorization(options =>
{
options.AddPolicy("CreatePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RC", "UC")));
options.AddPolicy("ReadPolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RR", "UR")));
});
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.Cookie.Name = "Cookies";
options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
options.SlidingExpiration = true;
}).AddOpenIdConnect("oidc", options =>
{
options.BackchannelHttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = delegate { return true; } };
options.Authority = Configuration.GetSection("API:IDS4").Value;
options.SignInScheme = "Cookies";
options.SignedOutRedirectUri = Configuration.GetSection("API:WebClient").Value + "/signout-callback-oidc";
options.RequireHttpsMetadata = true;
options.ClientId = "mvc";
options.ClientSecret = "*****";
options.ResponseType = "code";
options.UsePkce = true;
options.Scope.Add("profile");
options.Scope.Add("mcApi");
options.Scope.Add("Api1");
options.Scope.Add("Api2");
options.Scope.Add("Api3");
options.Scope.Add("Api4");
options.Scope.Add("Api5");
options.Scope.Add("Api6");
options.Scope.Add("Api7");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events.OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Prompt = "login";
return Task.CompletedTask;
};
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = (context) =>
{
context.Response.Redirect("/");
context.HandleResponse();
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = JwtClaimTypes.Role
};
});
services.AddHttpClient<IAdminService, AdminService>();
services.AddSingleton<DataProtectionPurposeStrings>();
services.AddSingleton<GlobalConstants>();
}
private bool AuthorizeAccess(AuthorizationHandlerContext context, string roleClaim, string userClaim)
{
return context.User.HasClaim(claim => claim.Type == roleClaim && claim.Value == "True") &&
context.User.HasClaim(claim => claim.Type == userClaim && claim.Value == "True") ||
context.User.IsInRole("MyAdmin");
}
// 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");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Identity Server Startup.cs
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
/****Register asp.net core Identity DBConetexts***/
var idenConnectionString = Configuration["DbContextSettings:IdentityConnectionString"];
var dbPassword = Configuration["DbContextSettings:DbPassword"];
var builder = new NpgsqlConnectionStringBuilder(idenConnectionString)
{
Password = dbPassword
};
services.AddDbContext<IdentityDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequiredLength = 8;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+ ";
options.SignIn.RequireConfirmedEmail = false;
options.User.RequireUniqueEmail = false;
}).AddRoles<ApplicationRole>().AddEntityFrameworkStores<IdentityDBContext>()
.AddDefaultTokenProviders();
/****Identity Server implementation with asp.net core Identity***/
var idsServerConnectionString = Configuration["DbContextSettings:IdentityServer4ConnectionString"];
var migrationAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var idsServerdbPassword = Configuration["DbContextSettings:DbPassword"];
var idsServerbuilder = new NpgsqlConnectionStringBuilder(idsServerConnectionString)
{
Password = dbPassword
};
var idBuilder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Login";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromMinutes(10),
CookieSlidingExpiration = true
};
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
options.EnableTokenCleanup = true;
}).AddAspNetIdentity<MembershipUser>()
.AddProfileService<ProfileService>();
X509Certificate2 cert = null;
using (var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
"thumbprint",
false);
if (certCollection.Count > 0)
{
cert = certCollection[0];
}
}
if (Environment.IsDevelopment())
{
idBuilder.AddDeveloperSigningCredential();
}
else
{
idBuilder.AddSigningCredential(cert);
}
idBuilder.Services.ConfigureExternalCookie(options =>
{
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode)(-1);
});
idBuilder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode)(-1);
});
services.AddMediatR(typeof(Startup));
RegisterServices(services);
}
private void RegisterServices(IServiceCollection services)
{
services.AddSingleton<IEventBus, RabbitMQBus>(sp =>
{
var scopeFactory = sp.GetRequiredService<IServiceScopeFactory>();
return new RabbitMQBus(sp.GetService<IMediator>(), scopeFactory);
});
services.AddTransient<UserDBContext>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// uncomment if you want to add MVC
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
Can I store the cookies into SessionStore using MemoryCacheTicketStore ? Kindly share your thoughts.

A potential thing could be that IIS thinks the cookie header is too long.
By default ASP.NET Core chunks up the cookie in 4Kb chunks, like this picture shows:
So either you try to reduce the size of the cookie or look at the IIS settings, if you can increase the max header length?
Alternatively, you stop saving the tokens inside the cookie, by setting:
options.SaveTokens = false;
Now you of course need top store it somewhere else, like in a tokenstore.

Related

Different domain silent renew issue

I have two different clients with diffrent domains. And i have identity server hosting different domains. Like this;
client1.com, client2.com (client domains) , auth.client1.com (identity server domain).
Silent renew works on client1.com. But on client2.com when try to silent renew it's gives an error and whole page become empty.
Error is;
"Error: AuthCallback AuthResult came with error: login_required".
Am i missing something?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureNonBreakingSameSiteCookies();
services.AddControllersWithViews();
string migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
string connectionString = "<connectionString>"
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
options.EnableTokenCleanup = true;
}).AddAspNetIdentity<ApplicationUser>(); ;
string cerFileName = "<cerFileName>";
string cerPassword = "<cerPassword>"
if (Environment.IsDevelopment())
{
string fileName = Path.Combine(Environment.ContentRootPath, "tempkey.rsa");
builder.AddDeveloperSigningCredential(filename: fileName);
}
else
{
// string fileName = Path.Combine(Environment.ContentRootPath, "<file>");
if (!File.Exists(cerFileName))
{
throw new FileNotFoundException("Signing Certificate is missing!");
}
X509Certificate2 cert = new X509Certificate2(cerFileName, cerPassword, X509KeyStorageFlags.MachineKeySet);
//X509Certificate2 cert = new X509Certificate2(fileName, "", X509KeyStorageFlags.MachineKeySet);
builder.AddSigningCredential(cert);
Console.WriteLine("###Certificate is done");
}
string authorityUrl = Configuration.GetValue<string>("<AuthorityUrl>");
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwt =>
{
jwt.Authority = authorityUrl;
jwt.TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = false,
};
jwt.RequireHttpsMetadata = false;
});
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
}
public void Configure(IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
SeedData.EnsureSeedData(scope.ServiceProvider, Configuration);
}
app.UseCookiePolicy();
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
///app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}

Asp.Net core Authorization with Bearer token status Unauthorized 401 with Valid token

public class User: IdentityUser
{
// ... code here
}
[HttpPost]
[Route("Login")]
//POST: /api/User/Login
public async Task<IActionResult> Login(LoginModel model)
{
var user = await _UserManager.FindByEmailAsync(model.Email);
if (user != null && await _UserManager.CheckPasswordAsync(user, model.Password))
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(
new Claim[]
{
new Claim("UserID", user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(5),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_ApplicationSettings.JWT_Secret)), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
else
{
return BadRequest(new { message = "Username or password is incorrect" });
}
}
}
[HttpGet]
[Authorize]
//GET: /api/UserProfile
public async Task<Object> GetUserProfile()
{
var t = User.Claims.Count();
string userId = User.Claims.First(c => c.Type == "UserID").Value;
var user = await _UserManager.FindByIdAsync(userId);
return new
{
user.FirstName,
user.LastName,
user.Email,
user.ProfileType
};
}
}
When I try to get the connected user with the returned token (using postman), I always get status 401 Unathorized.
Also, i found out that User.Claims.Count() is 0 ( i did this by commenting the in order to see what's wrong [Authorize]).
Does anyone know what the issue is?
Thanks!
EDIT: App configuration
public void ConfigureServices(IServiceCollection services)
{
//Inject AppSettings
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
services.AddControllers();
services.AddDbContext<AuthentificationContext>(
options =>
{
options.UseMySql(Configuration.GetConnectionString("IdentityConnection"));
});
services.AddDefaultIdentity<User>()
.AddEntityFrameworkStores<AuthentificationContext>();
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
options.User.RequireUniqueEmail = true;
}
);
services.AddCors();
//jwt authentification
var key = Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:JWT_Secret"].ToString());
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = false;
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});
services.Configure<CookiePolicyOptions>(options =>
{
services.AddHttpContextAccessor();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.Use(async (ctx, next) =>
{
await next();
if (ctx.Response.StatusCode == 204)
{
ctx.Response.ContentLength = 0;
}
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(options =>
options.WithOrigins(Configuration["ApplicationSettings:Client_URL"].ToString())
.AllowAnyMethod()
.AllowAnyHeader());
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseAuthentication();
}
}
You should pay attention to the middleware order that put app.UseAuthentication(); before app.UseAuthorization();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

Authorization happens before authentication after migrating to .net core 3.1

After migrating from .net core 2.2 to .net core 3.1, and after taking some time to fix all the bugs depending on documentations, after running the app, the authorization method get's hit before it requires the user to be authenticated.
I am using OpenId Connect with AD FS for authentication and a custom method called "PermissionGranted" for authorization. And this method get's hit before it requires for the user to be authenticated.
This is the code of my Startup.cs file:
public class Startup
{
private readonly ConfigSettings settings;
private readonly GetSettings configSettings;
private readonly MyMemoryCache myMemoryCache;
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
configSettings = new GetSettings();
settings = configSettings.getSettings();
Configuration = configuration;
Environment = env;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
myMemoryCache = new MyMemoryCache();
Log.Logger = new LoggerConfiguration().Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File(settings.LogFile, rollingInterval: RollingInterval.Day)
.WriteTo.ApplicationInsights(new TelemetryConfiguration(settings.AppInsightsConnection), TelemetryConverter.Events)
.CreateLogger();
}
public IConfiguration Configuration { get; }
private IWebHostEnvironment Environment { get; }
public IServiceProvider ServiceProvider { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(Log.Logger);
services.AddSingleton(settings);
services.AddHttpContextAccessor();
services.AddRouting(options => options.LowercaseUrls = true);
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.AccessDeniedPath = "/users/denied";
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.MetadataAddress = settings.MetadataAddress;
options.ClientId = settings.ClientId;
options.GetClaimsFromUserInfoEndpoint = true;
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.SignedOutRedirectUri = settings.PostLogoutUri;
options.SaveTokens = true;
options.UseTokenLifetime = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = settings.ClientId,
ValidateIssuer = true,
ValidIssuer = settings.Issuer,
ValidateLifetime = true,
RequireExpirationTime = true
};
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
context.Response.Redirect("/Login");
return Task.FromResult(0);
},
OnTicketReceived = context =>
{
// Configuration to make the Authentication cookie persistent for 8 hours (after 8 hours it expires and the user has to re-authenticate)
context.Properties.IsPersistent = true;
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddHours(8);
return Task.FromResult(0);
}
};
options.Events.OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Prompt = "login";
return Task.CompletedTask;
};
});
Audit.Core.Configuration.Setup()
.UseSqlServer(config => config
.ConnectionString(settings.ConnectionString)
.TableName("AuditEvents")
.IdColumnName("EventId")
.JsonColumnName("AuditData")
.LastUpdatedColumnName("LastUpdatedDate"));
services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");
services.AddApplicationInsightsTelemetry(settings.AppInsightsConnection);
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
// Use NewtonsoftJson and custom date format
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
});
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(15);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true; // Make the session cookie essential
});
services.AddAuthorization(options =>
{
options.AddPolicy("Permissions", policy => policy.RequireAssertion(context => PermissionGranted(context)));
});
services.AddDbContext<PasswordManagerContext>(options =>
options.UseSqlServer(settings.ConnectionString));
services.AddSingleton<MyMemoryCache>();
services.AddPaging(options =>
{
options.ViewName = "PagerUI";
options.HtmlIndicatorDown = " <span>↓</span>";
options.HtmlIndicatorUp = " <span>↑</span>";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAntiforgery antiforgery)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSession();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Login}/{action=Index}/{id?}");
//endpoints.MapFallbackToController("Index", "Home");
});
ServiceProvider = app.ApplicationServices;
}
According to official docs, the order of these:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints
matters but in my code they are ordered correctly.
What could be missing or what could possibly be wrong in the configuration?
Thanks in advance

With JWT token and policy set, I get unauthorized 401

I follow the tutorial link below.
https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login
I am trying to understand how it works and I want to use role-based authentication using this token. so I made another policy in the Startup.cs file as below.
And I tried to use it like [Authorize(Policy = "admin")] the controller but every time I try I get unauthenticated using postman.
What am I missing? how to make roles-based authentication based on the tutorial?
Configure
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.WithOrigins("http://localhost:4200", "http://localhost:44318")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
// api user claim policy
services.AddAuthorization(options =>
{
options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
services.AddAuthorization(options =>
options.AddPolicy("admin", policy => policy.RequireRole("admin"))
);
var builder = services.AddIdentityCore<AppUser>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<CDSPORTALContext>().AddDefaultTokenProviders().AddRoles<IdentityRole>();
//.AddRoles<IdentityRole>()
services.AddControllers();
services.AddAutoMapper(typeof(Startup));
services.AddSingleton<IJwtFactory, JwtFactory>();
services.TryAddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IRegionRepository, RegionRepository>();
services.AddScoped<IRegionService, RegionService>();
services.AddScoped<IEmailHelper, EmailHelper>();
}
// 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.UseCors("CorsPolicy");
app.UseExceptionHandler(
builder =>
{
builder.Run(
async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
context.Response.AddApplicationError(error.Error.Message);
await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false);
}
});
});
app.UseRouting();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "Client";
spa.UseAngularCliServer(npmScript: "start");
});
}
}
Auth Controller
// POST api/auth/login
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]CredentialsViewModel credentials)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
if (identity == null)
{
//return null;
return BadRequest(Error.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState));
}
var id = identity.Claims.Single(c => c.Type == "id").Value;
var user = await _userManager.FindByIdAsync(id);
IList<string> role = await _userManager.GetRolesAsync(user);
var jwt = await Tokens.GenerateJwt(identity, role[0], _jwtFactory, credentials.UserName, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented });
return new OkObjectResult(jwt);
}
I tried with all method and none of them working
[Authorize(Policy = "ApiUser")]
[HttpGet("getPolicy")]
public string GetPolicy()
{
return "policyWorking";
}
[Authorize(Roles = "admin")]
[HttpGet("getAdmin")]
public string GetAdmin()
{
return "adminWorking";
}
[Authorize ]
[HttpGet("getAuthorize")]
public string GetAuthorize()
{
return "normal authorize Working";
}
Decoded Token
"jti": "840d507d-b2d0-454b-bd1f-007890d3e669",
"iat": 1587699300,
"rol": "api_access",
"id": "1ac370e2-f5e9-4404-b017-8a3c087e2196",
"nbf": 1587699299,
"exp": 1587706499
I forgot to add this to appsetting.
"JwtIssuerOptions": {
"Issuer": "webApi",
"Audience": "http://localhost:4200/"
}

How to sign in ASP.NET Core Identity User, using OpenIdConnect Authentication?

I'm using ASP.NET Identity to authenticate my users and I want to be able to do this via Azure AD as well. All users will be in the DB beforehand, so all I need to do is sign them in and set their cookies, if the AzureAD login was successful. The problem is that when I implement the new external authentication and validate that they exist in my DB, they are not signed in.
So after successful remote login, if in my controller I check for User.Identity.IsAuthenticated it returns true, but _signInManager.IsSignedIn(User), it returns false. I have tried to follow the MS guidelines and documentation, but I assume there is something wrong with my config.
Here's the startup:
services.AddMvc(options => options.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddRouting(options =>
{
options.LowercaseQueryStrings = true;
options.LowercaseUrls = true;
});
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("<my_db_connection_string_here>")));
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserManager<UserManager<ApplicationUser>>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
Configuration.GetSection("OpenIdConnect").Bind(options);
options.TokenValidationParameters.ValidateIssuer = false;
options.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async ctx =>
{
var request = ctx.HttpContext.Request;
var currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);
var distributedCache = ctx.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
string userId = ctx.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var authContext = new AuthenticationContext(ctx.Options.Authority);
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(
ctx.ProtocolMessage.Code, new Uri(currentUri), credential, ctx.Options.Resource);
ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
});
var builder = services.AddIdentityCore<ApplicationUser>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddLogging(options =>
{
options.AddConfiguration(Configuration.GetSection("Logging"))
.AddConsole();
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
IdentityModelEventSource.ShowPII = true;
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
And in my controller:
[AllowAnonymous]
public IActionResult AzureLogin()
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction(nameof(HandleLogin)):
}
return Challenge(new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(HandleLogin))
});
}
[Authorize]
public async Task<IActionResult> HandleLogin()
{
var isAuth = User.Identity.IsAuthenticated; // true
var isSigned = _signInmanager.IsSignedIn(User); // false
return ....
}
You could try to set AutomaticAuthenticate cookie to true:
services.Configure<IdentityOptions>(options => {
// other configs
options.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
});
Here's how I managed to do it:
Since I'm authorizing the user via ASP.NET Identity, I changed the default authentication method in the authentication options to options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; and in the OpenIdConnectOptions OnAuthorizationCodeRecieved event, I validate and sign in the Identity User via the SignInManager.SignInAsync() method