Restrict WebApi endpoint to users - c#

I'm developing a small PoC WebApi in C#, where a user should be able to view or edit his full name, e.g.:
GET my.api.com/users would return all users (array of JSON objects containing Id and FullName properties, representing users). This is accessible by everyone.
GET my.api.com/users/{id} would return a single user matching that id. This is accessible by everyone.
PUT my.api.com/users/{id} for editing the FullName property of the user matching that id. This should only be accessible on a per-user basis.
I would like to make my PUT request require authentication, and each user only being able to edit his own FullName (e.g. user with id 0 should not be able to make a PUT request to my.api.com/users/1)
Coming from an ASP.NET MVC world, I'm slightly confused as to how to approach this. In MVC, I would simply mark the action as [Authorize] and ensure that the ID of the user making the call matches the actual ID passed in that particular action.
I've understood how authorization would work in WebApi using this Microsoft resource (e.g. using bearer tokens, etc). Would that imply that user registration would be very similar to ASP.NET MVC, i.e. registering using a username/password and then authenticating against the WebApi using bearer tokens?

You can use [Authorize] on WebApi Actions the same way you use it in MVC. Here is a snippet of my old code:
public class MoviesController : ApiController
{
// POST /api/movies/
[Authorize(Roles = RoleName.CanManageMovies)]
[HttpPost]
public IHttpActionResult CreateMovie(MovieDto movieDto)
{
if (!ModelState.IsValid)
return BadRequest();
var movie = Mapper.Map<MovieDto, Movie>(movieDto);
_context.Movies.Add(movie);
_context.SaveChanges();
return Created(new Uri($"{Request.RequestUri}/{movie.Id}"), movie);
}
// PUT /api/movies/1
[Authorize(Roles = RoleName.CanManageMovies)]
[HttpPut]
public IHttpActionResult UpdateMovie(int id, MovieDto movieDto)
{
if (!ModelState.IsValid)
return BadRequest();
var movieInDb = _context.Movies.SingleOrDefault(m => m.Id == id);
if (movieInDb == null)
return NotFound();
Mapper.Map(movieDto, movieInDb);
_context.SaveChanges();
return Ok();
}
// DELETE /api/movies/1
[Authorize(Roles = RoleName.CanManageMovies)]
[HttpDelete]
public IHttpActionResult DeleteMovie(int id)
{
var movieInDb = _context.Movies.SingleOrDefault(m => m.Id == id);
if (movieInDb == null)
return NotFound();
_context.Movies.Remove(movieInDb);
_context.SaveChanges();
return Ok();
}
}
As you can see I'm using the [Authorize] annotation to limit the Actions only to logged in users who are allowed to manage movies.

Related

C# Web api confirm that JWT token sender is equal to parameter of endpoint

I have an ASP-net Web api endpoint as below. It Authorizes the roles via a JWT token with via Roles:
[HttpPost, Authorize(Roles = "Admin, Teacher")]
public async Task<ActionResult<CourseParticipant>> AddCourseParticipant(Guid userID, Guid CourseID)
{
return Ok(await _calendarParticipantService.AddCalendarParticipant(userID, CourseID));
}
Now, I dont want Any Teacher to be able to change the participant. Only the teacher who "owns" the course. My idea is to simple add the GUID of the teacher to the JWT token and then compare. But how do i read the JWT token on the request? and is there a simpler or "correct" way to do this?
You need to add teacher guid to the users claims while creating account like this.
//create user account for teacher ommited
var teacherUser = await _userManager.CreateAsync(identityUser, userDto.Password);
_userManager.AddClaimAsync(teacherUser, new Claim
{
"user_id", $"{teacherUser.Id}"
});
Now update your api endpoint like below. Also Inject UserManager in controller
[HttpPost, Authorize(Roles = "Admin, Teacher")]
public async Task<ActionResult<CourseParticipant>> AddCourseParticipant(Guid userID, Guid CourseID)
{
var user = await _userManager.FindByIdAsync(userID.ToString());
if(await _userManager.IsInRoleAsync(user, "Teacher"))
{
var userClaim = User.Claims.FirstOrDefault(x => x.Type == "user_id");
if(userClaim == null || userClaim.Value != userID.ToString())
{
return Unauthorized;
}
}
return Ok(await _calendarParticipantService.AddCalendarParticipant(userID, CourseID));
}
At first I wanted to recommed policy based authorization. But since you need the actual data in your authorize to check the ownership, this would not be the correct way.
Ressource-Based authorization is the keyword.
With ressoure-based authorization you can write a AuthorizationHandler for your ressource (Course) and apply this to a policy.

How to get user claims inside ASP Core 2 method without Authorize attribute?

I have a method on an API that can be accessed anonymously. I want to use resource authorization to determine if the user has access. If the object is "public" than it can be accessed by anyone (including anonymous users). If the object is "private" than it can only be viewed by logged in users. This logic works fine if I have an authorize attribute on the method, but if not the User has no claims even when they are logged in.
Is there a way to get the user's claims in a method without an Authorize attribute?
Method looks like this:
[HttpGet]
[Route("name/{name}")]
public async Task<IActionResult> Get(string name)
{
var activity = Repo.GetCommandLineActivity(name);
if (activity == null)
{
return NotFound();
}
var isAuthed = await _authService.AuthorizeAsync(User, activity, new ViewIPublicPrivateRequirement());
if (isAuthed.Succeeded)
{
return Ok(activity);
}
return Unauthorized();
}
The solution was actually very simple, adding [AllowAnonymous] and [Authorize] did the trick.
It is possible to retrieve the ClaimsPrincipal even without the [Authorize] attribute, but it truly feels like a hack, and I wouldn't recommend it. If I were you, I'd create two different endpoints, one for public access, and the other for authenticated users.
That being said, the way to retrieve the ClaimsPrincipal is to call the AuthenticateAsync method. Note that this code is for ASP.NET Core 2.0, it would be slightly different for 1.1.
Here's the modified method:
[HttpGet("name/{name}")]
[AllowAnonymous]
public async Task<IActionResult> Get(string name)
{
var activity = Repo.GetCommandLineActivity(name);
if (activity == null)
{
return NotFound();
}
// based on the way your authentication is configured in services.AddAuthentication(),
// this can be omitted (in which case, the default authenticate scheme will be used)
var authenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme;
var auth = await HttpContext.AuthenticateAsync(authenticationScheme);
if (auth.Succeeded)
{
// CAUTION: HttpContext.User will STILL be null
var user = auth.Principal;
return Ok(activity);
}
return Unauthorized();
}
Caution: HttpContext.User will NOT be set if the [Authorize] attribute is omitted (or if [AllowAnonymous] is specified.
Short answer - you cannot.
If you check closely you will see that, when you have the Authorize attribute, the User object is of type ClaimsPrincipal and when you don't have it - it is of type WindowsPrincipal.
But you can always add custom Authorize attribute, or Custom policy-based authorization, and check the user claims there and do your stuff.

In ASP MVC Identity, what is the best way to restrict access based on criteria?

I have a separate database filled with employees, each with a unique email address. The website I am creating, employees can register an account and if they have a matching email address I would like them to see their contact information and edit it if possible.
Here is the following code I was able to use to achieve this.
[AllowAnonymous]
// GET: Contacts/Details/
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Contact contact = db.Contacts.Find(id);
if(User.IsInRole("Admin")||(User.Identity.GetUserName()==contact.Email))
{
return View(contact);
}
if (contact == null)
{
return HttpNotFound();
}
return RedirectToAction("AccessDenied","Error");
}
Ideally I would like to remove [AllowAnonymous] and have something like
[Authorize(Roles="Admin",Users=User.Identity.GetUserName())]
but this pulls up an error:
"User.Identity.GetUserName() an object reference is required".
Any suggesions?
You can create a new attribute class, which should inherit from Authorise attribute class. You can pass your desired parameter in the new attribute class and play accordingly by overriding OnAuthorization method.
Authorize Attribute constructor takes only Constant values, you cant use it dynamically for each user. if you want your specific type of users can only have access to this method, create a role for them and use it instead.
[Authorize(Roles="Admin,SpecialUsers")]

Call controller from another controller and return a view

I call an Action from a Login controller to authenticate users, once the user is authenticated I would like to call either the Cashier or the Supervisor action, depending on the user's role, and display the appropriate view.
I can break on AuthenticateUserByCard but RedirectToAction doesn't seem to be working.
I'm not sure if what I'm trying to do is deviating from the MVC architecture, if so please suggest the correct way to do this
Login controller:
public class LoginController : Controller
{
public ViewResult Index()
{
return View();
}
[HttpPost]
public ActionResult AuthenticateUserByCard(string token)
{
//Authenticate user and redirect to a specific view based on the user role
Role role = GetRoleByToken(token);
if(role.UserType == UserType.Supervisor)
return RedirectToAction("Supervisor", "Login", new { id = token });
else
return RedirectToAction("Cashier", "Login", new { id = token });
return null;
}
public ActionResult Supervisor(string id)
{
//Do some processing and display the Supervisor View
return View();
}
public ActionResult Cashier(string id)
{
//Do some processing and display the Cashier View
return View();
}
}
Java Script:
$.get("/Login/AuthenticateUserByCard",{token:token});
jQuery post and get ignore 301 browser redirects returned from the server. You would normally need to handle them yourself. This can get messy: How to manage a redirect request after a jQuery Ajax call
All you really need in this case is to return the choice of methods, but make them return explicit views (not implicit). The default would always be to return the view based on the IIS-called method i.e. "AuthenticateUserByCard" unless you specify the view.
e.g.
public class LoginController : Controller
{
public ViewResult Index()
{
return View();
}
[HttpPost]
public ActionResult AuthenticateUserByCard(string token)
{
//Authenticate user and redirect to a specific view based on the user role
Role role = GetRoleByToken(token);
if(role.UserType == UserType.Supervisor)
return Supervisor(token);
else
return Cashier(token);
return null;
}
public ActionResult Supervisor(string id)
{
//Do some processing and display the Supervisor View
return View("Supervisor");
}
public ActionResult Cashier(string id)
{
//Do some processing and display the Cashier View
return View("Cashier");
}
This will not change the URL though. If you need that too try the other answer I linked. You basically handle the redirect in jQuery and goto the new page.
Alternatively, to change the URL, put the desired URL into a hidden field of the returned views and extract that value to update the browser URL (just a thought) :)

How to use custom Authorize attribute for roles as well as a specific user?

I have my Action Method
[Authorize(Roles="Admin")]
public ActionResult EditPosts(int id)
{
return View();
}
In my case I need to authorize administrators so they can edit posts but (here comes the cool part), I also need to allow the creator of the post to be able to edit the post which is a normal user. So how can I filter out the user that created the post as well as the admins but leave the others unauthorized? I am receiving the PostEntry id as a route parameter but that's after the attribute and also attributes only accept constant parameters, looks like something very difficult, your answers are highly appreciated, Cheers!
You could write a custom authorize attribute:
public class AuthorizeAdminOrOwnerOfPostAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// The user is not authenticated
return false;
}
var user = httpContext.User;
if (user.IsInRole("Admin"))
{
// Administrator => let him in
return true;
}
var rd = httpContext.Request.RequestContext.RouteData;
var id = rd.Values["id"] as string;
if (string.IsNullOrEmpty(id))
{
// No id was specified => we do not allow access
return false;
}
return IsOwnerOfPost(user.Identity.Name, id);
}
private bool IsOwnerOfPost(string username, string postId)
{
// TODO: you know what to do here
throw new NotImplementedException();
}
}
and then decorate your controller action with it:
[AuthorizeAdminOrOwnerOfPost]
public ActionResult EditPosts(int id)
{
return View();
}
I understand that you have already accepted an answer, and this was posted a while back.. (btw:excellent answer for adding custom attributes), However I would point out the following:
If you are using this attribute once. On a Single method. This isn't a good implementation. Instead you should have:
[Authorize] // Just make sure they are auth'ed at all.
public ActionResult EditPosts(int id)
{
Post SomePost = findPostByID (id); // However you do it - single lookup of post
if (!user.IsInRole("Admin") && !{IsOwnerOfPost(post)} ) Return Not Authorized
... Edit post code here
}
This has the advantages of:
No additional class that someone will later wonder where it is used.
No class that isn't usable anywhere else (you don't gain reuse with a custom attribute)
Performance is better: Single fetch of the Post
Way easier for someone to read/figure out how it works. No magic code to track down.
And Years later, when HttpContextBase class doesn't exist, or other parts of the tricks used to fetch the Id parameter are gone, the code still works...

Categories

Resources