I need to check if an user is logged in or not. In View I'm checking it like this;
#if (User.Identity.IsAuthenticated)
{
//links...
}
else
{
//links...
}
always returns false and Identity is empty although I SignInAsync in my login function. I tried to change the order of the configure method usings but it didn't work.
Here is my startup and login function.
public void ConfigureServices(IServiceCollection services)
{
var key = Encoding.ASCII.GetBytes(Configuration.GetSection("Appsettings:Secret").Value);
services.AddDbContext<BiHaberContext>();
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<BiHaberContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 3;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Lockout.MaxFailedAccessAttempts = 3;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.User.RequireUniqueEmail = false;
options.SignIn.RequireConfirmedEmail = false;
options.SignIn.RequireConfirmedPhoneNumber = false;
options.SignIn.RequireConfirmedAccount = false;
});
services.AddAutoMapper(typeof(Startup));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
//options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.LoginPath = "/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
services.AddAuthentication();
services.AddAuthorization();
services.AddControllersWithViews();
services.AddScoped<ISemesterService, SemesterManager>();
services.AddScoped<IDepartmentService, DepartmentManager>();
services.AddScoped<ICourseService, CourseManager>();
services.AddScoped<IAnnouncementService, AnnouncementManager>();
services.AddCors();
services.AddResponseCaching();
services.AddMemoryCache();
}
// 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.UseCors(x => x.AllowAnyHeader().AllowAnyOrigin().AllowAnyHeader());
app.UseResponseCaching();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}");
});
}
Login:
[HttpPost("/login")]
public async Task<IActionResult> Login(LoginModel model)
{
if (!ModelState.IsValid)
return View(model);
var myContent = JsonConvert.SerializeObject(model);
var stringContent = new StringContent(myContent, System.Text.Encoding.UTF8, MediaTypeNames.Application.Json);
using (var postTask = await ApiHelper.ApiClient.PostAsync("Auth/Login", stringContent))
{
string jwt = await postTask.Content.ReadAsStringAsync();
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(JwtExtension.CorrectJwtFormat(jwt));
var claims = token.Payload.Claims.ToList();
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties()
{
AllowRefresh = true, ExpiresUtc = DateTimeOffset.Now.AddMonths(1), IsPersistent = true
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),authProperties);
return RedirectToAction("index", "home");
}
}
API works well and brings me 7 claims and claimsIdentity contains them as well. And redirecting to index. What did I do wrong I just couldn't figure out.
Added: Also I can not use Authorize attribute. So there is no authorizing anywhere.
When I delete this line
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<BiHaberContext>();
It worked. Identity is overriding my own claims. Thanks to #David Liang
Related
Context
I have one web application seperated in two "utilities".
Web API
Razor Pages
So my app is both an API and a ASP.NET Razor Pages app, to authenticate on the Web API side I use JWT Bearer and on Web App side a simple Cookie.
Problem
When using Cookie Authentication, I followed Microsoft's Use cookie authentication without ASP.NET Core Identity and it's absolutely not working AT ALL.
I use a custom AuthManager with a SignInAsync method. The cookie is indeed created BUT the ClaimsPrincipal in my HttpContext is empty
I didn't find ANY SOLUTION AT ALL on internet, the only solutions that seemed viable were by using custom Middlewares but I don't even know where to start.
If anybody encountered the same problem as me.
Thanks
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
UserModel user = _userService.UserLogin(email, password).MapFromBLL();
if (user is null) return false;
user.Roles = _roleService.GetUserRoles(user.Id).Select(r => r.MapFromBLL());
if (user.Roles is null || user.Roles.Count() == 0) return false;
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim("Stamp", user.SecurityStamp.ToString())
};
IEnumerable<Claim> roleClaims = user.Roles.Select(ur => new Claim(ClaimTypes.Role, ur.Name));
claims.AddRange(roleClaims);
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
AuthenticationProperties authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTime.Now.AddDays(_jwtModel.ExpirationInDays),
IsPersistent = true,
IssuedUtc = DateTime.Now,
};
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
try
{
await _httpAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties);
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return false;
}
}
Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.None;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = jwtModel.Issuer,
ValidAudience = jwtModel.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtModel.Secret)),
ClockSkew = TimeSpan.Zero
};
});
EDIT 10-02-2022
I seperated the post in two chapters : "Context" and "Problem"
My solution
Ok, so I found a solution to my problem. It was, as usual after 10 hours of research, dumb. In my Startup.cs I used JwtBearerDefaults.AuthenticationScheme as DefaultAuthenticateScheme. So I changed it with CookieAuthenticationDefaults.AuthenticationScheme, and now by miracle IT WORKS. After logging in, my ClaimsPrincipal User is full and it retrieve correctly the cookie.
My new services.AddAuthentication() in Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
I don't really know if I should keep options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;. If anyone could enlight me.
I didn't change anything in my method public async Task<bool> SignInAsync() I just added await _httpAccessor.HttpContext.SignOutAsync() at the beginning just to be sure.
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
await LogOutAsync();
// The same code as before
}
The LogOutAsync() method
public async Task LogOutAsync()
{
try
{
await _httpAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
Problems encountered
Authorization
After fixing this problem, I had another one. Now that my default scheme is Cookie based, I can't just use [Authorize] and except it to work with both Cookie or Jwt. To fix that I just added this code in Startup.cs after services.AddAuthentication()
AuthorizationPolicy multiSchemePolicy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.DefaultPolicy = multiSchemePolicy;
});
Code that I found here
API Unauthorize return Login Page instead of 401 or 403 status codes
This one is kind of strange, now the cookie and the JwtBearer worked fine, when I tried to access an [Authorize] route, sometimes it returned me the HTML page to Login instead of 401 Status Code and sometimes not.
To avoid this problem, I found a solution here
using services.ConfigureApplicationCookie didn't work for me, instead I adapted my services.AddCookie().
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 401;
}
return Task.CompletedTask;
},
OnRedirectToAccessDenied = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 403;
}
return Task.CompletedTask;
}
};
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
});
VoilĂ , I hope I have helped at least one of you. If there is anything to change, just let me know !
Complete code
Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = jwtModel.Issuer,
ValidAudience = jwtModel.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtModel.Secret)),
ClockSkew = TimeSpan.Zero
};
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 401;
}
return Task.CompletedTask;
},
OnRedirectToAccessDenied = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 403;
}
return Task.CompletedTask;
}
};
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
});
AuthorizationPolicy multiSchemePolicy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.DefaultPolicy = multiSchemePolicy;
});
AuthManager.cs
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
await LogOutAsync();
UserModel user = _userService.UserLogin(email, password).MapFromBLL();
if (user is null) return false;
user.Roles = _roleService.GetUserRoles(user.Id).Select(r => r.MapFromBLL());
if (user.Roles is null || user.Roles.Count() == 0) return false;
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim("Stamp", user.SecurityStamp.ToString())
};
IEnumerable<Claim> roleClaims = user.Roles.Select(ur => new Claim(ClaimTypes.Role, ur.Name));
claims.AddRange(roleClaims);
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
AuthenticationProperties authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTime.Now.AddDays(_jwtModel.ExpirationInDays),
IsPersistent = true,
IssuedUtc = DateTime.Now,
};
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
try
{
await _httpAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties);
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return false;
}
}
public async Task LogOutAsync()
{
try
{
await _httpAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
We're trying to develop an ASP Net application, and we have to use an oidc authentication system.
we've got our own OIDC server. When we try to connect we've got an error message :
"System.Exception: An error was encountered while handling the remote login.
---> Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolException: Message contains error: 'invalid_client', error_description: 'Invalid authentication method for accessing this endpoint.', error_uri: 'error_uri is null'."
here is our ConfigureService function and Configure function:
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
services.AddMvc();
services.AddOptions();
SetGlobalConfig();
services.AddHttpContextAccessor();
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
services.AddAntiforgery(options =>
{
options.Cookie.Name = "X-CSRF-TOKEN-OurAppli";
options.HeaderName = "X-CSRF-TOKEN-OurAppli";
options.FormFieldName = "X-CSRF-TOKEN-OurAppli";
});
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(5);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
});
services.AddDetection();
services.AddControllersWithViews();
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
var bearerUrl = Configuration.GetValue<string>("BearerUrl");
var callBackUrl = Configuration.GetValue<string>("CallBackUrl");
var sessionCookieLifetime = Configuration.GetValue("SessionCookieLifetimeMinutes", 60);
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime))
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ResponseType = "code";
options.ClientId = "*******";
options.ClientSecret = "********";
options.Authority = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Clear();
options.Scope.Add("openid uid isMemberOf");
options.SaveTokens = true;
options.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth/authorize",
TokenEndpoint = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth/access_token",
UserInfoEndpoint = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth/userinfo",
EndSessionEndpoint = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth/connect/endSession",
RegistrationEndpoint = "https://ourOIDCServer/ourOIDCServer/oauth2/multiauth/connect/register",
JwksUri = "ourOIDCServer/ourOIDCServer/oauth2/multiauth/connect/jwk_uri",
};
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("acr_values","ourACR");
context.ProtocolMessage.SetParameter("authlevel", "3");
var byteArray = Encoding.ASCII.GetBytes(options.ClientId + ":" + options.ClientSecret);
context.Request.Headers.Add("Authorization", "Post " + byteArray);
return Task.FromResult(0);
}
};
});
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(365);
});
}
public void Configure(IApplicationBuilder app, IHostEnvironment 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.UseResponseCompression();
app.UseDetection();
app.UseSession();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; font-src 'self';script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'");
await next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "Index",
pattern: "{controller=Home}/{action=Index}");
});
app.UseStaticFiles();
}`
And there is our HomeController :
[Authorize]
public ActionResult Index()
{
Console.WriteLine(_user);
Console.WriteLine(_habilitation);
dynamic models = new ExpandoObject();
models.detection = _detectionService;
if (!string.IsNullOrEmpty(_user) || _user != "")
{
models.Authorization = _habilitation;
models.Name = _user;
Console.WriteLine(models.Name + " Auth= " + models.Authorization);
models.Habilitation = 1;
}
return View("Index", models);
}
I never used an OIDC authentication system with ASP Net so i don't know if there is something wrong with my code...
Hope someone can help me!
Can you add to the question how the client is defined? What OIDC provider are you using?
The problem seems that the client asks for "authorization code flow" here:
options.ResponseType = "code";
but the OIDC provider does not have that enabled. I would check that first.
I am using a digital ocean droplet using Ubuntu 18.04 LTS and deployed my ASP.NET Core 5.0 MVC project on it and everything works great. I was trying to install rotativa on it to print pdf documents but I cannot make this work.
I followed these steps: https://blog.elmah.io/generate-a-pdf-from-asp-net-core-for-free/
But I am getting this error when trying to print the pdf:
Exception: QPainter::begin(): Returned false Exit with code 1, due to unknown error.
I have this in my Startup.cs:
using Wkhtmltopdf.NetCore;
namespace farmamest
{
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.AddControllersWithViews();
services.AddRazorPages();
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<Context>();
services.AddControllersWithViews();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
//options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// Default SignIn settings.
options.SignIn.RequireConfirmedEmail = false;
options.SignIn.RequireConfirmedPhoneNumber = false;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
options.User.RequireUniqueEmail = true;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
// options.Cookie.Name = Configuration["CookieName"];
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;
});
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.None;
});
services.AddMvc(config =>
{
// var policy = new AuthorizationPolicyBuilder()
// .RequireAuthenticatedUser()
// .Build();
// config.Filters.Add(new AuthorizeFilter(policy));
});
services.Configure<PasswordHasherOptions>(option =>
{
option.IterationCount = 12000;
});
services.AddWkhtmltopdf("Rotativa");
}
// 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();
// app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/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.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseHttpsRedirection();
app.UseStaticFiles();
//cookie policy
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseDefaultFiles();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
}
I saved the files in <foldername>/<projectname>/bin/Debug/net5.0/Rotativa
How do I make this work? Is this version of net issue related? I am using net5.0, I cant find solutions in the web, I got headaches.
It's an issue with .NET 5. Looks related to the invoking Wkhtmltopdf.
https://github.com/fpanaccia/Wkhtmltopdf.NetCore/issues/46
I'm trying to get JWT working here, the token is successfully received on my client end after login but when I request user info at the /info route, the authorization fails. Any help would be much appreciated, thanks in advance.
I get the error:
Route matched with {action = "GetInfo", controller = "Accounts", page = ""}. Executing controller action with signature System.Threading.Tasks.Task`1[ProjectConker.Controllers.AccountsInfo] GetInfo() on controller ProjectConker.Controllers.AccountsController (ProjectConker).
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
This is where the token is issued.
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]LoginInfo credentials)
{
if (credentials == null)
{
return BadRequest("Invalid client request");
}
var user = await UserManager.FindByNameAsync(credentials.Username);
await SignInManager.SignInAsync(user, isPersistent: false);
var result = await SignInManager.PasswordSignInAsync(user,
credentials.Password, isPersistent: false, lockoutOnFailure: false);
if (result.Succeeded)
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("**********"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: "http://localhost:5000",
audience: "http://localhost:5000",
claims: new List<Claim>(){
new Claim("username", credentials.Username)
},
expires: DateTime.Now.AddMinutes(5),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return Ok(new { Token = tokenString, UserName = user.UserName });
}
else
{
return Unauthorized();
}
}
Save token to local storage
public Login(loginForm : ILoginForm) : Observable<ILoginForm>
{
return this.http.post<ILoginForm>(this.accountsUrl + "/login", loginForm, httpOptions)
.pipe(map<any, any>((data, index) => {
localStorage.setItem("auth_token", data.token);
this.username = data.username;
this.loggedIn = true;
console.log(data);
return data;
}));
}
Gets user information
public GetAccountInfo() : Observable<any>
{
httpOptions.headers.set('Authorization', localStorage.getItem('auth_token'));
return this.http.get(this.accountsUrl + "/info", httpOptions);
}
returns user info, but authorization fails here
[HttpGet]
[Route("info")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<AccountsInfo> GetInfo()
{
var usernameClaim = User.Claims.SingleOrDefault(c => c.Type == "username");
Console.WriteLine(usernameClaim.Value, ConsoleColor.Red);
var user = await UserManager.FindByNameAsync(usernameClaim.Value);
return new AccountsInfo{ DisplayName = user.UserName };
}
My startup.cs
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.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://localhost:5000",
ValidAudience = "http://localhost:5000",
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("superSecretKey#345"))
};
});
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddHttpClient();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder =>
{
builder.AllowAnyMethod().AllowAnyHeader()
.WithOrigins("*")
.AllowCredentials();
}));
services.AddSignalR();
services.AddEntityFrameworkSqlServer();
services.AddDbContext<ConkerDbContext>(
options => options.UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll));
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<ConkerDbContext>();
services.AddScoped<SearchEngine>();
services.AddTransient<RoadmapService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/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.UseSpaStaticFiles();
app.UseAuthentication();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/api/chat");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
Since Angular's HttpHeaders are immutable, you can't just use httpOptions.headers.set('Authorization', localStorage.getItem('auth_token'));, because it would have no effect on the original object.
First thing is the header is invalid, use the Bearer method provided by Ashique, then your GetAccountInfo call would look like this:
public GetAccountInfo() : Observable<any> {
const headers = new HttpHeaders({'Authorization': 'Bearer ' + localStorage.getItem('auth_token')});
return this.http.get(this.accountsUrl + "/info", {headers});
}
Here I assumed that you don't have other HttpOptions set, so I'm just passing the header to the HttpClient. Try it this way and let us know if it's still not working.
The default way to add authorization header in HTTP request for ASP.NET core token authentication is to add Bearer before the token. So the code should be like this-
httpOptions.headers.set('Authorization', "Bearer " + localStorage.getItem('auth_token'));
You can override the default behavior to remove the need of Bearer.
Please read the below post to help understand the reason for using bearer before token.
https://www.quora.com/Why-is-Bearer-required-before-the-token-in-Authorization-header-in-a-HTTP-request
Also try this,
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = creds,
IssuedAt = DateTime.Now,
NotBefore = DateTime.Now
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
What i mean is instead of creating a new JWTSecurityToken, Create a SecuritTokenDescriptor, instead of using function WriteToken, use CreateToken. I have used JWT in this way and it worked.
In the login end point where you create the token you are using key "**********" and in the Setup class you are using key "superSecretKey#345" , this is the problem , the authentication middleware is trying to validate the incoming JWT tokens with key different from the key used to issue the token , you have to use the same key for both issuing and validating , also put the key in somewhere else like "appsettings" to avoid this conflict
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("**********"));
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("superSecretKey#345"))
I am trying to use asp.net identity framework for mvc and JWT for APIs. Requirement is that api accessing username/device is in the url, for example, api/v1/username/accounts. The user or the device that JWT was issues has username in it. Can I do it in the startup.cs file. The following code was working fine until recently then it started doing strange thing by allowing asp.net identity to use JWT protected APIs. I want to check if username in the url api/v1/username/accounts matches the token one .Following is my code. Thanks for your insights.
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Log.Logger = new LoggerConfiguration()
.MinimumLevel
.Warning()
.WriteTo.RollingFile("Logs/GateKeeperLog-{Date}.txt")
.CreateLogger();
}
public static IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(Configuration);
services.AddDbContext<GkEnterpriseContext>(options =>
options.UseSqlServer(Configuration["Database:Connection"]));
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<GkEnterpriseContext>()
.AddDefaultTokenProviders();
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.Formatting = Formatting.Indented;
}).AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddSerilog();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseWhen(context => context.Request.Path.Value.Contains("/api")
, builder =>
{
builder.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Audidence"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes
(JwtTokenIssuer.PrivateKey)),
ValidateLifetime = true,
NameClaimType = JwtRegisteredClaimNames.FamilyName
}
});
app.UseWhen(context => context.Request.Path.Value.StartsWith("/api/v2/computers/")
, builder1 =>
builder1.MapWhen((ctx) =>
{
var deviceName = ctx.User.Claims.SingleOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Name)?.Value ?? "";
var testPath = new Microsoft.AspNetCore.Http.PathString($"/api/v2/computers/{deviceName}");
var pathMatch = ctx.Request.Path.StartsWithSegments(testPath);
return String.IsNullOrWhiteSpace(deviceName) || !pathMatch;
}, cfg =>
{
cfg.Run((req) =>
{
req.Response.StatusCode = 403;
return req.Response.WriteAsync("Sorry , you cant access this resource...");
});
}));
});
app.UseIdentity();
app.UseStatusCodePagesWithReExecute("/StatusCodes/{0}");
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "defaultApi",
template: "api/v2/{controller}/{id?}");
});
}
}
// JWT issung code block, it is now issuing tokens as expected, only validating is the problem.
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,computer),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.FamilyName,"GkDevice")
};
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(PrivateKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: Startup.Configuration["Tokens:Issuer"],
audience: Startup.Configuration["Tokens:Audidence"],
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddYears(10),
signingCredentials: creds
);
var data = new Token
{
Message = "New Token was issued",
Jwt = new JwtSecurityTokenHandler().WriteToken(token),
Iat = GkHelpers.ConvertTimeToEpoch(token.ValidFrom) ,
Exp = GkHelpers.ConvertTimeToEpoch(token.ValidTo)
};
return data;
Something like this might help you --
app.UseWhen(context => context.Request.Path.Value.StartsWith("/api"), builder =>
{
...jwt code...
builder.MapWhen((ctx) =>
{
var userName = ctx.User.Claims.SingleOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Name)?.Value ?? "";
var testPath = new Microsoft.AspNetCore.Http.PathString($"/api/v2/computers/{userName}/");
var pathMatch = ctx.Request.Path.StartsWithSegments(testPath);
return String.IsNullOrWhiteSpace(userName) || !pathMatch;
}, cfg =>
{
cfg.Run((req) =>
{
req.Response.StatusCode = 403;
return req.Response.WriteAsync("");
});
});
});
The inner MapWhen will trigger when the username in the "Name" claim (configure how to get the username here) does not match the given Path. It will then immediately execute the following request pipeline which will return a 403 code with an empty response body.
I am unsure, however, if you can process Identity-related items in the same request pipeline in which you actually add identity. You might have to extract that MapWhen code outside of the UseWhen.