Web API for get teacher directory - asp.net

I am going to develop a rest service using Web API. For that we have a requirement that we have a number of parents who can able to login to the application as a parent. There is an access token provided for each user who accessing the application after successful login.
There is a service available for each parent to get the list of teachers who are teaching their child. There may be more than one child for a parent. The list of childid available at the client when parent get logged in.
So we need to pass childid with accesstoken of the user to the api. Which is the best method to pass. Is it a GET or POST including childid as json body?
What is the best method for creating service like this. GET or POST? including accesstoken in HEADER or any other way. like inside json?

One way of implementing this might be to give each user (parent) Claims representing their parentage of each child. These would then be added to the user's principal in the manner described in this article. Some data store (for example, a database table) would record which users (parents) are parents of which children. Finally, in the API action method, compare the id of the child whose information is requested to the list of child claims on the user's principal, and if the id isn't present, return a 4xx status code (probably a 403 Forbidden).
Something like:
// Cast the Thread.CurrentPrincipal
IClaimsPrincipal icp = Thread.CurrentPrincipal as IClaimsPrincipal;
// Access IClaimsIdentity which contains claims
IClaimsIdentity claimsIdentity = (IClaimsIdentity)icp.Identity;
// Access claims
if(claimsIdentity.Claims
.Where(c => c.ClaimType == "name-of-your-custom-claim")
.Select(c => c.Resource.ToString())
.Contains(childId)) { return new HttpResponseMessage(HttpStatusCode.Forbidden);
Should work. Haven't tested the code or checked that it compiles; it's an example to give the gist. I may clean it up later.

Related

Save data into the Firebase user object

Using this :
firebase.auth().onAuthStateChanged(function(user){
if (user){
Firebase is returning the user, which includes a userID.
When a webpage get this object it takes the user ID, and need to access again the DB records to check some very basic stuff ( if it's a paid user, or another simple key) in order to choose the UI.
Is there away to return in this callback the user including some predefined basic info about him?
If you want to add some extra info to a user account you cannot use the User object, which has a fixed list of properties.
There are two classical approaches:
1. Create a specific User record
You create a Firestore document (or a Realtime Database node) for each user. Usually you use the User's uid as the id of the Firestore Document/RTDB node.
This means that you will need to fetch the database to get this extra user info, e.g.
firebase.auth().onAuthStateChanged(function(user){
if (user) {
firebase.firestore().collection('users').doc(user.uid).get()
.then(...)
}
//...
}
2. Use Custom Claims
If you need to store only a limited number of extra information in order to provide access control, you could use Custom Claims. This is particularly adapted for storing user roles, e.g. paid user.
Custom user claims are accessible via user's authentication tokens, therefore you can use them to modify the client UI based on the user's role or access level. See more details here in the doc.
To get the Claims in the front end, you can do as follows:
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
user.getIdTokenResult()
.then((idTokenResult) => {
// Confirm the user is a paid user.
if (!!idTokenResult.claims.paidUser) {
// Show admin UI.
showPaidUserUI();
} else {
// Show regular user UI.
showRegularUI();
}
})
.catch((error) => {
console.log(error);
});
}
//...
}
It is important to note the following section in the doc:
Custom claims are only used to provide access control. They are not
designed to store additional data (such as profile and other custom
data). While this may seem like a convenient mechanism to do so, it is
strongly discouraged as these claims are stored in the ID token and
could cause performance issues because all authenticated requests
always contain a Firebase ID token corresponding to the signed in
user.
Use custom claims to store data for controlling user access only. All other data should be stored separately via the real-time database
or other server side storage.
Custom claims are limited in size. Passing a custom claims payload greater than 1000 bytes will throw an error.

How to create a “virtual” or “temporary” Role for a user under ASP.NET MVC 5 that will still work with controller and method decorations?

Current project:
ASP.NET 4.7.2
MVC 5
C# 7.2
Repository Pattern
I am a bit stumped on how to create a “virtual” Role under MVC 5.
Normal (persistent) roles I fully understand: you create them in the DB and then assign a user to that role. However, I need to assign a user to a “Role” depending on a status in a completely unrelated table, and where that Role only exists during their session - it does not exist for that user before they log in, and it no longer exists for that user once their session ends.
So for example, since the Active status is dependent on whether a user
is on leave or not, let’s call this table the “on leave” table. Very
simple: primary key, user id foreign key, a required start date and an
optional end date. When a user logs in, I need to flag the user as
either fully active (an actual Active role for just that session) or
inactive (no Active role for that session). This will be determined by
whether,
The user has an entry in the “on leave” table, and if so,
The most recent entry has,
A null end date, or
An end date in the future
If the user has an entry in the table, and the most recent entry has a
null end date or an end date in the future, the user does not get the
Active role. If they don’t meet that requirement, they do get that
role.
This is just one of a number of requirements on the site that will require virtual roles, but is a simplified example to get the point across.
This is also very important because about 95-99% of my needs revolves around the controller and method decorations and session authorizations - I need to be able to [Authorize(Roles = "Active")] and User.Identity.IsInRole("Active"), but I want that role to exist for the user only for that session.
Please understand that the underlying data is fully dynamic: if the data on the back end changes to something that invalidates them for Active status, I want their next login to not include the Active role being applied to their session. This is why I am trying to work with a “virtual” or “temporary” role that the user is not directly associated with in terms of the in-DB data.
Now if it was just one thing that was being checked for (like the On Leave table), I could just ensure that adding an end date would add the user to a fully traditional Active role, but the problem exists for entries in the future -- how would I go around auto-adding the user to that role once that date passes, at least without doing a database-write-and-login-bounce to properly set their authentication and session variables?
Plus, this is just one item of many that need to work in concert to provide a yes/no determination for Active status. This Active status will be drawn from not just the most current entry of the On Leave table, but also a number of different business rules from around the system that can be retrieved from the initial fetching of the user’s profile through _userManager, and all of them being virtual booleans providing a simple cumulative yes-no answer.
The point is, this Active role status will never touch the database, or even need to. This will always be in the context of the currently logged-in user, set when they log in, and dumped/destroyed when they time out or log out.
I suspect that the place to do this is in the SignInAsync where I set all my Claims, I just don’t know how to do this to the user’s session.
If someone can throw some hints on how to set up a virtual Role there, that the system (controller and method decorations) can work with, that would be real swell.
As requested, my login implementation:
var user = await _userManager.FindAsync(model.Username, model.Password);
if(user != null) {
SignInAsync(user, false).Wait();
// Bounce the user to the "Nexus" method, that determines where they should go based on Role, so that the Role can actually be read once it is in the User’s context.
}
And my SignInAsync() Task:
private async Task SignInAsync(IdentityUser user, bool isPersistent) {
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await _userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new UserClaim.Claim("ShortName", user.ShortName));
identity.AddClaim(new UserClaim.Claim("Name", user.Name));
// Snipped for brevity
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
It seems like Microsoft is going toward Claims based authorization. In ASP.NET Identity, roles are there for backward compatibility.
However, you can still set role as Claim. For example,
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
You then apply Authorize attribute to controller or action method as [Authorize(Roles = "Admin")]
Here is the sample code at GitHub.

How can i do custom authorization in my ASP.NET MVC application

I am making a small SAAS application. I am using the same database for all users and only differentiating between the data using username and ids. This means that the user can type in a new url in the browser and see other users data. This, of course is not a desirable approach. I would like to do a check to see if the current user can actually access the resources: eg.
http://myapplication.com/images/15
And if the user changes the url to
http://myapplication.com/images/16
I should do a check in my database to see if the current user actually has access to see the images with user id 16. And if not redirect to a "not authorized" page.
How do I implement this?
The first step is to make sure that you never have any ID's for the user itself in the url. For instance, never have http://example.com/?user=10. You should always get the users id from their authentication rather than from the URL (or posted values either).
The second step, is to use that ID in your queries. So, for instance, let's say they seek http://example.com/images/100, then in your database you should have a mechanism that links the asset's ownership to the user, either a userid or a mapping table of id's to asset's, etc.. This way, if the user isn't allowed access, it will just return an empty result set. It's impossible for the data to be returned, and the empty result set should tell your page that the item doesn't exist (not necessarily an authorization failure, just that the object doesn't exist).
Third, any pages which are inherently about the user, such as a user profile, account page, or dashboard should never have any ID's at all in the URL, it should just automatically go to the authenticated users page.
Finally, if you need to prevent the user from accessing an entire page or set of pages, then you should do this in the OnAuthorization event or similar (custom attribute, base class, etc..) or using the built-in attribute authorization and use role based authorization. Never do authorization in the PageLoad or similar event (such as the controller action), because by the time you get to that step a lot of work has already happened in the pipeline. It's best to block access long before the page even starts to setup. Authorization events happen at the very beginning of the pipeline.
Make an Action that check userId and returns error page or file
public FileResult Image(string imageName)
{
string UserId = MethodWhereYouGetCurrentUserID();
if(imageName == null) return View("~/Views/Shared/Error.cshtml", (object)"Null image");
string imageShortName = imageName.Split(".")[0];
if(!UserId == imageShortName) return View(~/Views/Shared/Error.cshtml, (object)"You can't access to this");
string path = Server.MapPath("~/Contant/images/"+imageName);
return File(path, "image/jpg");
}
RouteConfig file
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute
(
name: "ImageRoute",
url: "/images/imageName",
default: new {controller = "Home", action = "GetImage"}
);
}

Asp.Net Identity Custom User Roles (querying)

I have successfully created custom users and roles within the Asp.Net Identity framework, however one section which is lacking is support for the 'Roles' collection within a User. I added a logical 'Deleted' flag within a UserRole and I want this to be included when EF retrieves the info from the database, however all it does is retrieve everything (which isn't what I want). Senario:
User A has a Role of 'admin' (non-deleted)
I then add a Role of 'super-admin' and then delete 'admin'
If I then access my version of IdentityUser (which has the 'Roles' ICollection - custom type) I still see the 2 roles (1 deleted, 1 not)
What I want to happen is on login (using mostly default functions with various sections overridden), when EF retrieves the data it should know to exclude any row which is flagged as deleted, however I have been unable to find any method of doing this. Because all the boilerplate code is locked, I cannot easily see what is happening or find any logical place to override this functionality.
Can anyone help with this?
You could just scan the roles after login and remove them with code similar to this:
var roles = UserManager.GetRoles(userId);
foreach (var roleName in roles)
{
var role = RoleManager.FindByName(roleName);
if (role.IsDeleted) UserManager.RemoveFromRole(userId, roleName);
}

Authorization in ASP.NET Web API: Return data specific to authorized user

Let's assume I implemented token based authorization with a custom filter attribute as described here.
Let's also assume, I have a controller that returns tasks:
public IEnumerable<Task> Get()
{
// return tasks for authorized user
}
Now, how would I go about returning only the tasks for the authorized user? Passing the user ID as a query parameter is not an option - it is forbidden to request the tasks of a different user.
you could enrich the HttpRouteData from your action filter and read it in the controller action. actionContext.ControllerContext.RouteData.Values.Add("UserId", someVaue );
You could also use the System.Runtime.Remoting.Messaging.CallContext class ( GetData and SetData )
In the code in the sample you linked to, they are encrypting the user's name in the token. In the filter they are getting this token from an http header, decrypting it back to the username, and querying it against an AuthorizedUserRepository.
AuthorizedUserRepository.GetUsers().First(x => x.Name == RSAClass.Decrypt(token));
You can certainly use a userid instead of the name, and work against a real repository instead of this sample one. You could either do all of this over again in the controller action or constructor, or you could pass it along the route data or some ThreadStatic property. If you want to get really fancy, you could implement claims based security and set a claim on the current thread's principal. Really it doesn't matter how you pass it along.
Ultimately you would just use this value in a where clause of a query or linq statement to filter down to the data you want the user to be allowed to access.

Resources