C
C#•10mo ago
DonatoDeluxe

Asp.Net 7.0 MVC Project - Authentication/Authorization

hey everyone junior-midlevel dev here i'm currently trying to implement a logic where a custom class "User" has a boolean "IsAdmin" and on user login, i want to check rights on different controllers with the iServiceCollection authentication. i know there is a IdentityFramework which is being used in many tutorials, but since the use case is much more simplistic, i want to simply login a user and check if it's an admin or not. what i did so far is, i've added the authentication and policy as following under "Program.cs"
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
options.LoginPath = "/Users/Login"
);

builder.Services.AddAuthorization(options =>
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"))
);
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
options.LoginPath = "/Users/Login"
);

builder.Services.AddAuthorization(options =>
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"))
);
added the [Authorize(Policy = "AdminOnly")] attribute above the controller-classes which work so far so that i get redirected to the login page when i try to access a page with the authorization enabled. the thing is, that i don't know how i have to implement the logic, that in the login method, that the user get's actually logged in as an admin or not. i couldn't find anything helpful or something that would work yet... i'm using visual studio 2022 and it's an asp.net 7.0 MVC project thank you for your help in advance
26 Replies
Pobiega
Pobiega•10mo ago
well, your policy requires the user to have the "Admin" role, so when you sign the user in you'd have to assign that role I'm guessing somewhere in your login handler you do await HttpContext.SignInAsync(...)?
DonatoDeluxe
DonatoDeluxeOP•10mo ago
i've tried adding the "SignIn"-method in my Login action from a tutorial but it didn't work. here is my full Login action
[HttpPost]
[AllowAnonymous]
public IActionResult Login(string Name, string Password)
{
if (!ModelState.IsValid)
{
return View();
}

var password = Helper.Password.Encrypt(Password ?? "");
var user = _context.User.SingleOrDefault(u => u.Name == Name && u.Password == password);

if (user == null)
{
ModelState.AddModelError("", "Name oder Passwort ist falsch");
return View();
}

SignIn(new ClaimsPrincipal(
new ClaimsIdentity(
new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, user.IsAdmin ? "Admin" : "User"),
},
"cookie",
nameType: null,
roleType: ClaimTypes.Role
)
),
authenticationScheme: "cookie"
);

return RedirectToAction("Index", "Home");
}
[HttpPost]
[AllowAnonymous]
public IActionResult Login(string Name, string Password)
{
if (!ModelState.IsValid)
{
return View();
}

var password = Helper.Password.Encrypt(Password ?? "");
var user = _context.User.SingleOrDefault(u => u.Name == Name && u.Password == password);

if (user == null)
{
ModelState.AddModelError("", "Name oder Passwort ist falsch");
return View();
}

SignIn(new ClaimsPrincipal(
new ClaimsIdentity(
new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, user.IsAdmin ? "Admin" : "User"),
},
"cookie",
nameType: null,
roleType: ClaimTypes.Role
)
),
authenticationScheme: "cookie"
);

return RedirectToAction("Index", "Home");
}
Angius
Angius•10mo ago
Describe "didn't work" Also, I hope you're not actually encrypting your passwords Also also, you can't produce the same hash of the password without having the salt So, if you really want to do it manually: 1. Fetch the user by name 2. Get the password hash and salt 3. Hash the entered password with the retrieved salt 4. Compare hashes Or just use SignInManager
DonatoDeluxe
DonatoDeluxeOP•10mo ago
i can start the application and it behaves as i wanted to. if no user is logged in, i can access actions with the
[AllowAnonymous]
[AllowAnonymous]
attribute. if i want to access one where
[Authorize(Policy = "AdminOnly")]
[Authorize(Policy = "AdminOnly")]
is set, then it redirects me to the login page. after i log in as a user, it still behaves like i'm not logged in so therefore sends me back to the login page once i want to access an "AdminOnly" action as a logged in admin. i'm gonna look into that thank you
Pobiega
Pobiega•10mo ago
(its a part of Identity)
DonatoDeluxe
DonatoDeluxeOP•10mo ago
can i use the SignInManager with my simple custom "User" class or do i need to add all the models from the identityframework to my db and code?
Angius
Angius•10mo ago
Well, no, it's part of Identity, so you will need to at least use the Identity user You mentioned [AllowAnonymous] and [Authorize] attributes though, and they work only with Identity Unless you manually set them up to work with custom models
DonatoDeluxe
DonatoDeluxeOP•10mo ago
so i have to extend the user class like public class User : IdentityUser right? the thing is, that it fucks up my whole code for the user class.... i guess i should just delete my user class and use IdentityUser
Angius
Angius•10mo ago
You can extend it You should, even, if you want to add some properties to the user model Not sure in what ways it would break your existing code, but they can be fixed I assume it's stuff like duplicate properties, since IdentityUser already has, say, Email
DonatoDeluxe
DonatoDeluxeOP•10mo ago
yeah stuff like, duplicate properties and also just very different logics like password handling.
No description
Angius
Angius•10mo ago
The fact that it prevents you from rolling your own password hashing is a good thing lol
DonatoDeluxe
DonatoDeluxeOP•10mo ago
since password is not just a string property anymore, i would have to change it everywhere i used it. same with the ID property. i used an int type but the framework uses a string type as ID
Angius
Angius•10mo ago
It is a string property And you can change the ID type
DonatoDeluxe
DonatoDeluxeOP•10mo ago
i guess since you told i should not do things like that
Angius
Angius•10mo ago
Inherit from IdentityUser<int> Well, I don't know how exactly your Encrypt method works, but I see two red flags: 1. You don't seem to be using any salt 2. It's called Encrypt and not Hash Which makes me think you're actually encrypting the password
DonatoDeluxe
DonatoDeluxeOP•10mo ago
i'm just wondering why would the ID of a model be of type string? are numbers not more of a standart type for ID's?
Angius
Angius•10mo ago
guid
DonatoDeluxe
DonatoDeluxeOP•10mo ago
No description
Angius
Angius•10mo ago
whew Delete this I thought the method name is just a misnomer, but no It is encrypting the pass instead of hashing
DonatoDeluxe
DonatoDeluxeOP•10mo ago
😅 got it online, it made sense to me so therefore i used it. man i hate that i'm such a rookie when it goes a bit deeper than just creating classes and show data on a page 🙃 will google the differences between hashing and encrypting later then. so for now you say there is already a secure build in functionality for the password handling?
Angius
Angius•10mo ago
The code works for encryption, yes But you do not encrypt passwords Hashing works only one way You cannot unhash a password
DonatoDeluxe
DonatoDeluxeOP•10mo ago
so i guess i don't map the password parameter here to the user object right? i'm so confused.... i don't get how i should set the User.PasswordHash when creating a user. it doesn't seem that either IdentityUser or UserManager has a method for hashing the inputted password string
DonatoDeluxe
DonatoDeluxeOP•10mo ago
thank you. i just don't rly understand how i have to use it since these documentations don't have any examples... is it correct that i have to assign it in the constructor like:
private readonly ApplicationDbContext _context;
private UserManager<User> _userManager;

public UsersController(ApplicationDbContext context, UserManager<User> userManager)
{
_context = context;
_userManager = userManager;
}
private readonly ApplicationDbContext _context;
private UserManager<User> _userManager;

public UsersController(ApplicationDbContext context, UserManager<User> userManager)
{
_context = context;
_userManager = userManager;
}
and then later just use
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Email")] User newUser, string Password)
{
if (ModelState.IsValid)
{
await _userManager.CreateAsync(newUser, Password);
_context.Add(newUser);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(newUser);
}
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Email")] User newUser, string Password)
{
if (ModelState.IsValid)
{
await _userManager.CreateAsync(newUser, Password);
_context.Add(newUser);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(newUser);
}
?
Angius
Angius•10mo ago
Yep, you just inject it and use it
DonatoDeluxe
DonatoDeluxeOP•10mo ago
alright thank you 🤗 . i'm done for today but i will get back if i'm stuck again 🙂
Want results from more Discord servers?
Add your server