C
C#17mo ago
bakk

❔ Good way to make sure a user is allowed to do something based on context in ASP.NET?

I'm making a website where people can join projects and then post in those projects. Currently, in order to make sure users can only post in projects they're apart of, I pass a user id to every method that eg. handles posting, to let it make sure the user is allowed to do that based on what it gathers from the database. Is this a normal way to do it, or is there some cleaner way?
6 Replies
Pobiega
Pobiega17mo ago
I tend to have a step after input validation but before business logic that checks that they are allowed to access the resource they are trying to. Since that logic is very often shared, its in its own helper. Might look like...
_authorizationHelper.EnsureCanAccess(userId, projectId);
_authorizationHelper.EnsureCanAccess(userId, projectId);
obviously, you dont want to get userId from the user, you read it from their authentication ie, what user did they authenticate as
bakk
bakkOP17mo ago
Hmm yeah that's kind of what I have in practice I guess, but I keep it with the database handling methods and have it return an IQueryable so the data it retrieved can be used for more things. This makes me more confident that it's going to work well, thanks
Pobiega
Pobiega17mo ago
That should work.
V0FBU1VM
V0FBU1VM17mo ago
@b.kk You can create a requirement and authorize the user with that. Or create your own Authorization attribute. Where you can read the user id from the provided token and inject an instance of your database. Where you can perform the validation. Here is an example
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class PermitAttribute : Attribute, IAsyncAuthorizationFilter
{
private readonly RoleType[] _roles;

public PermitAttribute(params RoleType[] roles)
{
_roles = roles;
}

public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var claimsPrincipal = context.HttpContext.User;
var claim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;

if (string.IsNullOrEmpty(claim) || !Guid.TryParse(claim, out _))
{
context.Result = new ForbidResult();
return;
}

var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<User>>();
var user = await userManager.FindByIdAsync(claim);
if (user == null || user.IsSuspended)
{
context.Result = new ForbidResult();
return;
}

if (_roles.Length <= 0)
return;

var roles = await userManager.GetRolesAsync(user);
var rolesMatch = _roles.All(role => roles.Contains(role.ToString(), StringComparer.OrdinalIgnoreCase));

if (!rolesMatch)
context.Result = new ForbidResult();
}
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class PermitAttribute : Attribute, IAsyncAuthorizationFilter
{
private readonly RoleType[] _roles;

public PermitAttribute(params RoleType[] roles)
{
_roles = roles;
}

public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var claimsPrincipal = context.HttpContext.User;
var claim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;

if (string.IsNullOrEmpty(claim) || !Guid.TryParse(claim, out _))
{
context.Result = new ForbidResult();
return;
}

var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<User>>();
var user = await userManager.FindByIdAsync(claim);
if (user == null || user.IsSuspended)
{
context.Result = new ForbidResult();
return;
}

if (_roles.Length <= 0)
return;

var roles = await userManager.GetRolesAsync(user);
var rolesMatch = _roles.All(role => roles.Contains(role.ToString(), StringComparer.OrdinalIgnoreCase));

if (!rolesMatch)
context.Result = new ForbidResult();
}
}
bakk
bakkOP17mo ago
Hmm might be a bit overkill for me, but good to know, thanks
Accord
Accord17mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server