ASP.Net Core Identity JWT Role-Base Authentication is Forbidden - asp.net-core

Good day.
The API is for a Quote sharing web-app.
I have setup role-based JWT authentication where I have "Member" and "Admin" roles with users of those roles correctly registered and able to retrieve tokens.
So far, methods (or classes) with only
[Authorize]
can be correctly accessed provided a registered token.
Now once I added roles, access to methods or classes that require a certain role
[Authorize(Role="Admin")]
is Forbidden (403), even though I do pass a correct token with the Authorization header.
Please note: I have verified that users are correctly created (dbo.AspNetUsers), roles are correctly created (dbo.AspNetRoles containing "Admin" and "Member" roles) and user-roles are correctly mapped (dbo.AspNetUserRoles).
This is the Startup class which contains a method CreateRoles() that's called by Configure():
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<QuotContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<Member, IdentityRole>()
.AddEntityFrameworkStores<QuotContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 2;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.AddLogging(builder =>
{
builder.AddConfiguration(Configuration.GetSection("Logging"))
.AddConsole()
.AddDebug();
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider, QuotContext dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
app.UseAuthentication();
app.UseMvc();
dbContext.Database.EnsureCreated();
CreateRoles(serviceProvider).Wait();
}
private async Task CreateRoles(IServiceProvider serviceProvider)
{
//initializing custom roles
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<Member>>();
string[] roleNames = { "Admin", "Member" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
var poweruser = new Member
{
UserName = Configuration["AppSettings:AdminEmail"],
Email = Configuration["AppSettings:AdminEmail"],
};
string password = Configuration["AppSettings:AdminPassword"];
var user = await UserManager.FindByEmailAsync(Configuration["AppSettings:AdminEmail"]);
if (user == null)
{
var createPowerUser = await UserManager.CreateAsync(poweruser, password);
if (createPowerUser.Succeeded)
await UserManager.AddToRoleAsync(poweruser, "Admin");
}
}
}
This is the MembersController class containing Register() and Login() methods:
[Authorize]
public class MembersController : Controller
{
private readonly QuotContext _context;
private readonly UserManager<Member> _userManager;
private readonly SignInManager<Member> _signInManager;
private readonly ILogger<MembersController> _logger;
private readonly IConfiguration _configuration;
public MembersController(QuotContext context, UserManager<Member> userManager,
SignInManager<Member> signInManager, ILogger<MembersController> logger,
IConfiguration configuration)
{
_context = context;
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_configuration = configuration;
}
[HttpPost("register")]
[AllowAnonymous]
public async Task<IActionResult> Register([FromBody] RegisterModel model)
{
if (ModelState.IsValid)
{
var newMember = new Member
{
UserName = model.Email,
Email = model.Email,
PostCount = 0,
Reputation = 10,
ProfilePicture = "default.png"
};
var result = await _userManager.CreateAsync(newMember, model.Password);
if (result.Succeeded)
{
_logger.LogInformation(1, "User registered.");
await _signInManager.SignInAsync(newMember, false);
return Ok(new { token = BuildToken(model.Email, newMember) });
}
_logger.LogInformation(1, "Registeration failed.");
return BadRequest();
}
return BadRequest();
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email,
model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in." + _configuration["AppSettings:AdminPassword"]);
var member = _userManager.Users.SingleOrDefault(r => r.Email == model.Email);
return Ok(new { token = BuildToken(model.Email, member) });
}
_logger.LogInformation(1, "Login failed.");
return BadRequest();
}
return BadRequest(ModelState);
}
private string BuildToken(string email, Member member)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, member.Id)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpireDays"]));
var token = new JwtSecurityToken(
_configuration["JwtIssuer"],
_configuration["JwtIssuer"],
claims,
expires: expires,
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
Here's the example of 2 methods: the first requiring simple authentication which is successfully accessed provided a user token, and the second which is forbidden even given an admin token:
public class AuthorsController : Controller
{
private readonly QuotContext _context;
public AuthorsController(QuotContext context)
{
_context = context;
}
[HttpGet]
[Authorize]
public IEnumerable<Author> GetAuthors()
{
return _context.Authors;
}
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> PostAuthor([FromBody] Author author)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Authors.Add(author);
await _context.SaveChangesAsync();
return StatusCode(201);
}
}
Thank you for your help.
A github repo containing the full project: https://github.com/theStrayPointer/QuotAPI

I got the same problem. I've just find a way. In fact, a JWT token embeds the roles. So you have to add role claims in your token when you generate it.
var roles = await _userManager.GetRolesAsync(user);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(dateTime).ToString(), ClaimValueTypes.Integer64)
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, "Token");
// Adding roles code
// Roles property is string collection but you can modify Select code if it it's not
claimsIdentity.AddClaims(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var token = new JwtSecurityToken
(
_configuration["Auth:Token:Issuer"],
_configuration["Auth:Token:Audience"],
claimsIdentity.Claims,
expires: dateTime,
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Auth:Token:Key"])), SecurityAlgorithms.HmacSha256)
);
Found an explanation here and here.

Related

How to get the id of the user out of jwt token in .net core or how to return a value from a custom authorization attribute?

I create my token in the following way
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString()),
new Claim(ClaimTypes.Role, "tada")
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var encryptedtoken = tokenHandler.WriteToken(token);
And now i would like to simply get the users id from my authorize attribute and put it in the context somehow?
I know i can decode the token like so
[Authorize(Roles = "tada")]
public IActionResult Get()
{
var token = HttpContext.Request.Headers[HeaderNames.Authorization][0];
var tokenArray = token.Split(" ");
var handler = new JwtSecurityTokenHandler();
var tokenS = handler.ReadToken(tokenArray[1]) as JwtSecurityToken;
return Ok(tokenS.Payload.SingleOrDefault(t => t.Key == "unique_name").Value);
}
But how do i reuse this code in a more clever way can i create my own authorization attribute that will store it in the context if there is no way, how do i create a singleton/scoped/transient service?
here's how i configure jwt
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.AddDbContext<CatstagramDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddIdentity<User, IdentityRole>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<CatstagramDbContext>();
var applicationSettingConfiguration = Configuration.GetSection("ApplicationSettings");
services.Configure<AppSettings>(applicationSettingConfiguration);
var appSettings = applicationSettingConfiguration.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddControllers();
}
// 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();
}
app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.ApplyMigrations();
}
}
I am using this function to get any token claim value
public static string GetClaimValue(HttpContext httpContext, string valueType)
{
if (string.IsNullOrEmpty(valueType)) return null;
var identity = httpContext.User.Identity as ClaimsIdentity;
var valueObj = identity == null ? null : identity.Claims.FirstOrDefault(x => x.Type == valueType);
return valueObj==null? null:valueObj.Value;
}
you can use it like this
var name = GetClaimValue(HttpContext, "unique_name");
When the authentication middleware authenticates the request, it populates HttpContext.User property with a ClaimsPrincipal that holds the claims for the current user.
ClaimsPrincipal class has an extension method in System.Security.Claims in namespace called FindFirstValue.
User.FindFirstValue("unique_name");
gives you the value for the first unique_name claim.
Source: https://github.com/dotnet/aspnetcore/blob/2be49d930a5fb53e781abd175c3b2a8f8b7827d4/src/Identity/Extensions.Core/src/PrincipalExtensions.cs

How to get identity values from google external provider in ASP.NET Core Web API

[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
private readonly SignInManager<IdentityUser> _signInManager;
public AuthController(SignInManager<IdentityUser> signInManager)
{
_signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
}
[HttpGet("token")]
public ChallengeResult Token()
{
var properties = new GoogleChallengeProperties
{
RedirectUri = "/auth/retrieve",
AllowRefresh = true,
};
return Challenge(properties, "Google");
}
[HttpGet("[action]")]
public async Task Retrieve()
{
var token = await HttpContext.GetTokenAsync("access_token");
var externalLoginInfoAsync = await _signInManager.GetExternalLoginInfoAsync();
var identityName = User?.Identity?.Name;
var authenticateResult = await HttpContext.AuthenticateAsync();
}
}
I direct the user to /auth/token, where he is redirected to the Google Oauth Page, if successful, he is redirected to /auth/retrieve, where I expect the user data, but token, externalLoginInfoAsync, identityName, authenticateResult is null
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("Default")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication()
.AddCookie()
.AddGoogle(options =>
{
options.Scope.Add("https://www.googleapis.com/auth/gmail.settings.basic");
options.AccessType = "offline";
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Events.OnCreatingTicket = ctx =>
{
var identityName = ctx.Identity.Name;
return Task.CompletedTask;
};
options.ClientId = "SMTH_VALUE";
options.ClientSecret = "SMTH_VALUE";
});
services.AddControllers();
}
I debug the google provider and found the user values in the Events - identityName is not null.
How i can get this value in the controller?
You could refer the following code to configure Google authentication in Startup.ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication()
.AddGoogle(opt =>
{
opt.ClientId = "620831551062-rcvu44q4rhr5d8ossu3m0163jqbjdji0.apps.googleusercontent.com";
opt.ClientSecret = "GXFN0cHBbUlZ6nYLD7a7-cT8";
opt.SignInScheme = IdentityConstants.ExternalScheme;
});
services.AddControllersWithViews();
services.AddRazorPages();
}
Then, use the following sample to login using Google and get user information:
[Authorize]
public class AccountController : Controller
{
private UserManager<ApplicationUser> userManager;
private SignInManager<ApplicationUser> signInManager;
public AccountController(UserManager<ApplicationUser> userMgr, SignInManager<ApplicationUser> signinMgr)
{
userManager = userMgr;
signInManager = signinMgr;
}
// other methods
public IActionResult AccessDenied()
{
return View();
}
[AllowAnonymous]
public IActionResult GoogleLogin()
{
string redirectUrl = Url.Action("GoogleResponse", "Account");
var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return new ChallengeResult("Google", properties);
}
public IActionResult Login()
{
return View();
}
[AllowAnonymous]
public async Task<IActionResult> GoogleResponse()
{
ExternalLoginInfo info = await signInManager.GetExternalLoginInfoAsync();
if (info == null)
return RedirectToAction(nameof(Login));
var result = await signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, false);
string[] userInfo = { info.Principal.FindFirst(ClaimTypes.Name).Value, info.Principal.FindFirst(ClaimTypes.Email).Value };
if (result.Succeeded)
return View(userInfo);
else
{
ApplicationUser user = new ApplicationUser
{
Email = info.Principal.FindFirst(ClaimTypes.Email).Value,
UserName = info.Principal.FindFirst(ClaimTypes.Email).Value
};
IdentityResult identResult = await userManager.CreateAsync(user);
if (identResult.Succeeded)
{
identResult = await userManager.AddLoginAsync(user, info);
if (identResult.Succeeded)
{
await signInManager.SignInAsync(user, false);
return View(userInfo);
}
}
return AccessDenied();
}
}
}
The result like this:
More detail information, see How to integrate Google login feature in ASP.NET Core Identity and Google external login setup in ASP.NET Core

Role based Authentication: Bearer Token to Cookie using Microsoft .NetCore 3.1

I'm trying to transform.NetCore 3.1 Code from Bearer Token implementation to Cookie-based implementation Also trying to make Role-based authorization work with existing code. Can you please help me to change this code? The below code shows currently how Bearer Token is retrieved and the next part shows how role-based authorization is implemented in code.
Here is the current Bearer Token implementation.
var key = Encoding.ASCII.GetBytes(Configuration["AppSettings:Secret"]);
var signingKey = new SymmetricSecurityKey(key);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKey = signingKey,
ValidateIssuer = false,
ValidateAudience = false
};
});
Following annotation currently used for Role-based Authorization -
[Authorize(Roles = "1")]
[Route("api/[controller]")]
[ApiController]
public class JobLogsController : ControllerBase
{
private readonly EtpRepoContext _context;
private IJobLogsRepository _jobLogsRepository;
private IConfiguration _configuration;
public JobLogsController(EtpRepoContext context, IJobLogsRepository jobLogsRepository, IConfiguration configuration)
{
_context = context;
_jobLogsRepository = jobLogsRepository;
_configuration = configuration;
}
// GET: api/JobLogs
[HttpGet]
public async Task<ActionResult<IEnumerable<JobLog>>> GetJobLog()
{
return await _context.JobLog.ToListAsync();
}
// GET: api/JobLogs/5
[HttpGet("{id}")]
[ProducesResponseType(typeof(JobDetail), 200)]
[ProducesResponseType(typeof(string), 400)]
public IActionResult FindById([FromRoute] String id)
{
string contentStr = "";
try
{
if(id.Length >= 10)
{
contentStr = _jobLogsRepository.GetLogById(id);
}
else
{
contentStr = _jobLogsRepository.GetFileById(id);
}
var content = Newtonsoft.Json.JsonConvert.SerializeObject(new { content = contentStr });
return Ok(content);
}
catch (Exception ex)
{
return StatusCode(500, "Internal server error");
}
}
This is how the Microsoft identity model is used to claim the token.
public class ClaimsTransformer : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
ClaimsIdentity claimsIdentity = (ClaimsIdentity)principal.Identity;
// flatten realm_access because Microsoft identity model doesn't support nested claims
// by map it to Microsoft identity model, because automatic JWT bearer token mapping already processed here
if (claimsIdentity.IsAuthenticated && claimsIdentity.HasClaim((claim) => claim.Type == "identity"))
{
var realmAccessClaim = claimsIdentity.FindFirst((claim) => claim.Type == "identity");
dynamic realmAccessAsDict = JsonConvert.DeserializeObject<Object>(realmAccessClaim.Value);
string role = realmAccessAsDict.role.ToString();
claimsIdentity.AddClaim(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", role));
//var role = realmAccessClaim.
//var realmAccessAsDict = JsonConvert.DeserializeObject<Object>(realmAccessClaim.Value);
/*if (realmAccessAsDict["role"] != null)
{
foreach (var role in realmAccessAsDict["role"])
{
claimsIdentity.AddClaim(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", role));
}
}*/
}
return Task.FromResult(principal);
}
}
}

Error trying to get authenticate with google in ASP.NET Core 3.1 No authentication handler is registered

The Error
This is my startup class
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Demand authentication in the whole application
services.AddControllersWithViews(o => o.Filters.Add(new AuthorizeFilter()));
services.AddScoped<IConferenceRepository, ConferenceRepository>();
services.AddScoped<IProposalRepository, ProposalRepository>();
services.AddScoped<IAttendeeRepository, AttendeeRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddDbContext<ConfArchDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
assembly =>
assembly.MigrationsAssembly(typeof(ConfArchDbContext).Assembly.FullName)));
services
.AddAuthentication(options =>
{
options.DefaultScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme =
GoogleDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.IsEssential = true;
//options.Cookie.SameSite = SameSiteMode.None;
})
.AddGoogle(options =>
{
options.SignInScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.ClientId =
Configuration["Authentication:Google:ClientId"];
options.ClientSecret =
Configuration["Authentication:Google:ClientSecret"];
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
//app.UseIdentity();
// app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Conference}/{action=Index}/{id?}");
});
}
}
this is my controller, the error is thrown in the line: var result = await HttpContext.AuthenticateAsync(
public class AccountController : Controller
{
private readonly IUserRepository userRepository;
public AccountController(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
// This method must be anonymous to allow access to not logged users
[AllowAnonymous]
public IActionResult Login(string returnUrl = "/")
{
return View(new LoginModel { ReturnUrl = returnUrl });
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginModel model)
{
// Looking for user in local repository in the class userrepository
var user = userRepository.GetByUsernameAndPassword(model.Username, model.Password);
if (user == null)
return Unauthorized();
// Data of the user, claims class is used to represent the data user
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Name),
new Claim(ClaimTypes.Role, user.Role),
new Claim("FavoriteColor", user.FavoriteColor)
};
// Object to save in Identity object type ClaimsIdentity
var identity = new ClaimsIdentity(claims,
CookieAuthenticationDefaults.AuthenticationScheme);
// Create claims principal object with the Identity
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal,
new AuthenticationProperties { IsPersistent =
model.RememberLogin,
ExpiresUtc= DateTime.UtcNow.AddMinutes(10)
});
return LocalRedirect(model.ReturnUrl);
}
[AllowAnonymous]
public IActionResult LoginWithGoogle(string returnUrl = "/")
{
var props = new AuthenticationProperties
{
RedirectUri = Url.Action("GoogleLoginCallback"),
Items =
{
{ "returnUrl", returnUrl }
}
};
return Challenge(props, GoogleDefaults.AuthenticationScheme);
}
[AllowAnonymous]
public async Task<IActionResult> GoogleLoginCallback()
{
// read google identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(
ExternalAuthenticationDefaults.AuthenticationScheme);
var externalClaims = result.Principal.Claims.ToList();
var subjectIdClaim = externalClaims.FirstOrDefault(
x => x.Type == ClaimTypes.NameIdentifier);
var subjectValue = subjectIdClaim.Value;
var user = userRepository.GetByGoogleId(subjectValue);
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Name),
new Claim(ClaimTypes.Role, user.Role),
new Claim("FavoriteColor", user.FavoriteColor)
};
var identity = new ClaimsIdentity(claims,
CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
// delete temporary cookie used during google authentication
await HttpContext.SignOutAsync(
ExternalAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme, principal);
return LocalRedirect(result.Properties.Items["returnUrl"]);
}
// Action to logout
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Redirect("/");
}
}
I figure out the answer, the in the configure services code should be so:
// Demand authentication in the whole application
services.AddControllersWithViews(o => o.Filters.Add(new
AuthorizeFilter()));
services.AddScoped<IConferenceRepository, ConferenceRepository>();
services.AddScoped<IProposalRepository, ProposalRepository>();
services.AddScoped<IAttendeeRepository, AttendeeRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddDbContext<ConfArchDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
assembly =>
assembly.MigrationsAssembly(typeof(ConfArchDbContext).Assembly.FullName)));
services.AddAuthentication(o => {
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//o.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddCookie(ExternalAuthenticationDefaults.AuthenticationScheme)
.AddGoogle(o =>
{ // ClienteId and Secret to authenticate the user from Google
o.SignInScheme =
ExternalAuthenticationDefaults.AuthenticationScheme;
o.ClientId = Configuration["Authentication:Google:ClientId"];
o.ClientSecret =
Configuration["Authentication:Google:ClientSecret"];
});

Identity.IsAuthenticated returning false after SignInAsync() in HTTPGET controllers

This application should automatically sign-in users using their Environment.Username, but I'm struggling to do it.
In the following HomeController, the variable "ThisGivesNegative" remains false even after the "HttpContext.SignInAsync" is invoked.
When I put this code in an HTTPPost action, the sign in is correct so I guess there has to be something with the configuration but after navigating in the web none of the StackOverflow posts worked.
Could any of you give me a hand? Thanks!
public class HomeController : Controller
{
private readonly IAppUserService _appUserService;
private readonly ILogger _logger;
private readonly ApplicationDbContext _context;
public HomeController(
ApplicationDbContext context,
IAppUserService appUserService,
ILoggerFactory loggerFactory
)
{
_context = context;
_appUserService = appUserService;
_logger = loggerFactory.CreateLogger<AccountController>();
}
public async Task<IActionResult> Index()
{
string WindowsUsername = Environment.UserName;
if (WindowsUsername != null)
{
List<AppRole> RolesForThisUser = new List<AppRole>();
RolesForThisUser = _context.AppUserAppRoles.Where(x => x.AppUser.ApexID == WindowsUsername).Select(x => x.AppRole).ToList();
var properties = new AuthenticationProperties
{
//AllowRefresh = false,
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1)
};
List<Claim> MyClaims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, WindowsUsername),
};
foreach (AppRole ThisRole in RolesForThisUser)
{
MyClaims.Add(new Claim(ClaimTypes.Role, ThisRole.RoleName.ToString()));
}
var identity = new ClaimsIdentity(MyClaims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(principal, properties);
bool ThisGivesNegative = HttpContext.User.Identity.IsAuthenticated;
}
return View();
}
}
Here my ConfigureServices code:
public void ConfigureServices(IServiceCollection services)
{
services
.Configure<API_IP21_CurrentValues>(ConfigAppSettings.GetSection("API_IP21_CurrentValues"))
.Configure<API_IP21_HistoricValues>(ConfigAppSettings.GetSection("API_IP21_HistoricValues"))
.Configure<API_PPM_DailyValues>(ConfigAppSettings.GetSection("API_PPM_DailyValues"))
.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
})
.AddDbContextPool<ApplicationDbContext>(options =>
{
options.UseSqlServer(ConfigAppSettings.GetSection("ConnectionStrings").GetSection("DefaultConnection").Value);
})
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/Views/Home/Index.cshtml";
options.LogoutPath = "/Views/Home/Index.cshtml";
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()
.AddTransient<IAppUserService, AppUserService>()
.AddTransient<IEquipmentRepository, EquipmentRepository>()
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
I found the issue, here some help for those in need!
Esentially, cookies are read before the Home Controller is fired so when the view is rendered, the program doesn't know this user has those cookies. We need to reload.
I solve this by adding the following code just before "return view()" in the Home Controller.
if (FirstPostback == false)
{
RedirectToAction("Index", "Home", new { FirstPostback = true});
}
return View(new LoginViewModel { ReturnUrl = returnUrl });