I'm new to ASP.Net MVC, learning it on my own.
I am trying to create a web-app to allow users to send requests online,
simply I've to Tables in my Database, using IdentityDbContext to generate userTable, and I got another Table 'Fatwa",
the user sends a request and saves them in Fatwa Table with UserID, to reference which user sends the request.
my problem is I got the POST request using scaffolding, it works fine, but I don't know how to Attach the userID with the Post request,
I did make it a ForeignKey in the database but don't know how to edit my post request to allow it to save the userID with the request.
please help me
thank you
this is the POST request
// POST: api/Fatwas
[HttpPost]
public async Task<IActionResult> PostFatwa([FromBody] Fatwa fatwa)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Fatwa.Add(fatwa);
await _context.SaveChangesAsync();
return CreatedAtAction("GetFatwa", new { id = fatwa.FatwaID }, fatwa);
}
if i understand well you need the current user id, create object of user inside the class of Fatwa to make the relation between the user and fatwa then you can get the user from the current context HttpContext context inside the context there are context.User?.Identity.Name and much more
Related
I am using role based authentication in .Net Core 3.1 Api. I am using Jwt tokens and user claims. Role based authentication works fine. But in some controllers I want to make sure that user gets his/her own data. Because if an employee sends other employee id in a request he/she can get that resource data, I don't want that.
I have email, id and roles in token with some other data.
What I want is that something like [Authorize(Roles="Employee", Id={userId})]
[HttpGet("getUserInventory")]
//[Authorize(Roles="Employee", Claims.Id={userId})]
public IActionResult getUserInventory([FromQuery] int userId)
{
var inventories = _userInventoryExportService.GetGlobalInventory(userId);
if(inventories.Success)
{
return Ok(inventories.Data);
}
return BadRequest(inventories.Message);
}
Have a look at this tutorial we've created at Curity: Securing a .NET Core API. You will see there how to configure authorization based on claims found in a JWT access token.
had the same use case, to authorize user access to its own mailbox only.
controller:
[HttpPost("{address}/inbox/messages/list")]
[Authorize(Policy = "userAddress")]
public async Task<ActionResult<Response>> ListMessages([FromRoute] string address)
{
// return user mailbox data.
}
here i define the userAddress, and also the way i pull the address string from the url. it is not possible to pass this value from the controller, i had to pick it from a global request class:
//Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("userAddress", policy =>
{
policy.RequireAssertion(context =>
{
var userAddress = context.User.FindFirst(JWTClaim.Email).Value;
// /api/v1/mailbox/email#example.com/inbox/messages/list
var address = new HttpContextAccessor().HttpContext.Request.RouteValues["address"].ToString();
return address == userAddress;
});
});
});
it is worth to note that the context contains the actual request values, but is not publicly accessible, only via debugger:
context.Resource.HttpContext.Request.RouteValues["address"].ToString();
I am following the tutorial posted on the Microsoft website https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/details?view=aspnetcore-2.2
I just wonder once I click the delete button, how does it know which method or action should be invoked first? get or post? with the same parameters and action name
The code below might show you more details.
Thank you
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var movie = await _context.Movie.FindAsync(id);
_context.Movie.Remove(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
As stated in the comments above, a GET request will usually return a View to perform an action, but it won't actually perform that action unless it is a POST request, e.g. a GET request to an action named Edit will grab the data to edit and display it. That's it. The changes are not saved to the database until a POST to the Edit action is submitted.
Overloaded methods are required to have different parameter signatures. Since the other scaffolded pairs of CRUD actions (except Delete) have different signatures, they can have the same name. But since both the GET and POST methods for the Delete action have the same parameter signature, one of them needs to be renamed, which is why the POST action is named DeleteConfirmed. However, having GET and POST methods named differently will break the routing built into MVC. Adding the ActionName("Delete") attribute fixes that.
Routing depend on the HTTP Method + The name + The Parameters
so, when you issue a GET request to /Movies/Delete/5 it will use the first one.
When you issue a POST request to /Movies/Delete/5, it will use the second one.
If you have more than one POST method with different parameters, it will use the most specific. ex:
Delete(int id, bool confirm)
Delete(int id)
If you issue a POST request to /Movies/Delete/5, it will go for the second action, but if you change it to /Movies/Delete/5?confirm=true, it will go for the first one unless the parameter confirm was nullable, in this case it will throw an exception as it will not be able to determine which action to invoke
I have a question similar to Owner based Authorization
Is it possible to use resource-based authorization or policy-based authorization to allow only the owner of a model to view/edit/delete it?
With something like
[Authorize(Policy = "OwnerAuthorization")]
public class EditModel : PageModel
Or do I have to add logic to every OnGet/OnPost on every page for handling the authorization?
do I have to add logic to every OnGet/OnPost on every page for handling the authorization?
You don't have to, but for a better performance, you should add logic for every OnGet/OnPost on every page.
To authorize the request in the way of resource-based authorization, we need firstly to know what the resource is, and only after that we can authorize the user against the resource and policy.
Typically, we need load the resource from the server and then we can know whether the resource belongs to the current user. As loading the resource from server is usually done within action method, we usually authorize the request within the action method. That's exactly what is described in the official document.
var authorizationResult = await _authorizationService
.AuthorizeAsync(User, Document, "EditPolicy");
if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
However, if we choose to decorate the pagemodel with [Authorize(Policy = "OwnerAuthorization")] only, and don't invoke the _authZService.AuthorizeAsync(User, resource, "OwnerAuthorization"); within the action method, we'll have to load the resource within the authorization handler( or a simple function). In other words, we'll query the database twice. Let's say an user wants to edit a Foo model , and then make a HTTP GET request to /Foo/Edit?id=1 to show the form. The OnGetAsync(int? id) method is :
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null){ return NotFound(); }
// load foo from database
Foo = await _context.Foos.FirstOrDefaultAsync(m => m.Id == id);
if (Foo == null){ return NotFound(); }
return Page();
}
Now the resource-based authorization will load the Foo entity from database and check the owner. If succeeds, the action method will then validate the model.id and load resource from the database again.
I`m facing some problems when trying to customize one of the quickstarts from identityServer4 QuickStart 9, basically, I need to create a single sign-on application that will be used by several services, multiple web applications, one electron, and PhoneGap app.
Currently, my flow is a bit more complicated than simply authenticating the user, see below:
User inputs login and password -> system validates this piece of data and presents the user with a selection of possible sub-applications to select -> the user selects one of the sub-applications -> the system now requests the user to select a possible environment for this application (staging/production can be customized)
I want to do this flow on the authentication layer because otherwise, I would have to replicate all these steps on all the apps, and off-course I want the authentication to have separate development lifecycle.
Currently, I'm trying to make 3 modifications to achieve this:
PersistentGrantStore -> save this steps to a custom table using the
grant key as a reference. (something like
Key/application/environment)
IProfileService -> add custom claims that represent this steps
(stuck here), and are temporary, they only have meaning for this token and subsequent refreshes.
authenticationHandler -> validate if the user went through all the
steps
I will also need to make a modification to the token endpoint to accept these 2 parameters via custom header due to my spa`s apps
my question boils down to: is there a better way to this? am I overcomplicating this?
sorry if this question is too basic, but I`m not used to doing this type of auth.
If i understand you correctly, following way might be helpful.
Create a temp cookie and display select page after user loggedin:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model, string button)
{
if (ModelState.IsValid)
{
var loginResult = .service.Check(model.Username, model.Password);
if (loginResult.IsSucceed)
{
await HttpContext.SignInAsync("TempCookies", loginResult.Principal);
var selectViewModel = new SelectViewModel();
model.ReturnUrl = model.ReturnUrl;
return View("SelectUserAndEnvironment", selectViewModel);
}
else
{
ModelState.AddModelError("", "****.");
return View(model);
}
}
return View(model);
}
Add claims you want and sign in for IdentityServerConstants.DefaultCookieAuthenticationScheme
[HttpPost]
[Authorize(AuthenticationSchemes = "TempCookies")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SelectUserAndEnvironment(SelectModel model)
{
// add claims from select input
var claims = new List<Claim>();
claims.Add(new Claim(<Type>, <Value>));
var p = new ClaimsPrincipal(new ClaimsIdentity(auth.Principal?.Identity, claims));
await HttpContext.SignOutAsync("TempCookies");
await HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, p);
return Redirect(model.ReturnUrl);
}
And use claims in ProfileService
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// you can get claims added in login action by using context.Subject.Claims
// other stuff
context.IssuedClaims = claims;
await Task.CompletedTask;
}
Finally add authentication scheme in Startup.cs
services.AddAuthentication()
.AddCookie("TempCookies", options =>
{
options.ExpireTimeSpan = new TimeSpan(0, 0, 300);
})
If you want to use external login, change above code appropriately.
Background:
I'm building more and more web applications where the designers / template makers decide that adding a "profile picture" and some other user-related data, of course only when someone is logged in.
As most ASP.NET MVC developers I use viewmodels to provide razor layouts with the information that I need shown, sourced from repositories et al.
It is easy to show a user name through using
HttpContext.Current.User.Identity.Name
What if I want to show information that's saved in my backing datastore on these pages? Custom fields in the ApplicationUser class like a business unit name or a profile picture CDN url.
(for sake of simplicity let's assume I use the Identity Framework with a Entity Framework (SQL database) containing my ApplicationUsers)
Question
How do you solve this:
Without poluting the viewmodel/controller tree (e.g. building a BaseViewModel or BaseController populating / providing this information?
Without having to roundtrip the database every page request for these details?
Without querying the database if a user is not logged in?
When you cannot use SESSION data (as my applications are often scaled on multiple Azure instances - read why this isn't possible here- I'm not interested in SQL caching or Redis caching.
I've thought about using partials that new their own viewmodel - but that would still roundtrip the SQL database every pageload. Session data would be safe for now, but when scaled up in azure this isn't a way either. Any idea what would be my best bet?
TLDR;
I want to show user profile information (ApplicationUser) on every page of my application if users are logged in (anon access = allowed). How do I show this info without querying the database every page request? How do I do this without the Session class? How do I do this without building base classes?
The best way with Identity is to use claims to store custom data about the user. Sam's answer pretty close to what I'm saying here. I'll elaborate a bit more.
On ApplicationUser class you have GenerateUserIdentityAsync method which used to create ClaimsIdentity of the user:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyApp:FirstName",this.FirstName), //presuming FirstName is part of ApplicationUser class
new Claim("MyApp:LastName",this.LastName),
});
return userIdentity;
}
This adds key-value pairs on the user identity that is eventually serialised and encrypted in the authentication cookie - this is important to remember.
After user is logged in, this Identity are available to you through HttpContext.Current.User.Identity - that object is actually ClaimsIdentity with claims taken from the cookie. So whatever you have put into claims on login time are there for you, without having to dip into your database.
To get the data out of claims I usually do extension methods on IPrincipal
public static String GetFirstName(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new DomainException("User is not authenticated");
}
var personNameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:FirstName");
if (personNameClaim != null)
{
return personNameClaim.Value;
}
return String.Empty;
}
This way you can access your claims data from your Razor views: User.GetFirstName()
And this operation is really fast because it does not require any object resolutions from your DI container and does not query your database.
The only snag is when the values in the storage actually updated, values in claims in the auth cookie are not refreshed until user signs-out and signs-in. But you can force that yourself via IAuehtenticationManager.Signout() and immediately sign them back in with the updated claims values.
You could store your extra information as claims. In your log in method fill your data to generated identity. For example if you are using Identity's default configuration you could add your claims in ApplicationUser.GenerateUserIdentityAsync() method:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyValueName1","value1"),
new Claim("MyValueName2","value2"),
new Claim("MyValueName2","value3"),
// and so on
});
return userIdentity;
}
And in your entire application you have access those information by reading current user claims. Actually HttpContext.Current.User.Identity.Name uses same approach.
public ActionResult MyAction()
{
// you have access the authenticated user's claims
// simply by casting User.Identity to ClaimsIdentity
var claims = ((ClaimsIdentity)User.Identity).Claims;
// or
var claims2 = ((ClaimsIdentity)HttpContext.Current.User.Identity).Claims;
}
I think the "how to" is a little subjective as there are probably many possible ways to go about this but I solved this exact problem by using the same pattern as HttpContext. I created a class called ApplicationContext with a static instance property that returns an instance using DI. (You could alter the property to generate a singleton itself as well if you aren't, for some reason, using DI.)
public interface IApplicationContext
{
//Interface
string GetUsername();
}
public class ApplicationContext : IApplicationContext
{
public static IApplicationContext Current
{
get
{
return DependencyResolver.Current.GetService<IApplicationContext>();
}
}
//appropriate functions to get required data
public string GetUsername() {
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
return HttpContext.Current.User.Identity.Name;
}
return null;
}
}
Then you just reference the "Current" property in your view directly.
#ApplicationContext.Current.GetUsername()
This would solve all of you requirements except #2. The database call may not add a significant enough overhead to warrant avoiding altogether but if you require it then your only option would be to implement some form of caching of the user data once it is queried the first time.
Simply implement ChildAction with caching and vary by loggedin user