What is the best way to implement roles and permission in Express REST Api - node.js

I need kind of expert opinion to implement Roles and Permission in Express js. I'm planning to develop Restful Api using Express js but didn't get sufficient information to implement Roles and Permission although there are tons of option are available for authentication and authorization.

Create Tables
First you need to create your tables that will hold the associations between roles, permissions, and resources:
Create a roles table ('Admin', 'User', 'Guest')
Create a resources table ('Users', 'Projects', 'Programs')
Create a permissions table ('Create', 'Read','Write','Delete','Deny')
Create a junction table with all three tables as sources
You may not need that kind of granularity for the permissions table, but some people like it. For example, you don't really need 'Deny', since you just check for Read != true.
Now when you want the permissions of a role on a resource, you just look up role_id and resource_id and check for which permissions are set to true.
Create Middleware
Since you're using express, middleware will be easy to add. For example, let's say you have a router called users:
users.post('/', getAuth, handleUserPost)
Assuming you have a token of some sort on the request that identifies the user making the post, and attaching the user instance to the request object you can do this:
getAuth = function (req, res, next) {
if(req.user) {
db.getPerms({role_id: req.user.role_id, resource_id: req.resource.id})
.then(function(perms){
var allow = false;
//you can do this mapping of methods to permissions before the db call and just get the specific permission you want.
perms.forEach(function(perm){
if (req.method == "POST" && perms.create) allow = true;
else if (req.method == "GET" && perms.read) allow = true;
else if (req.method == "PUT" && perms.write) allow = true;
else if (req.method == "DELETE" && perm.delete) allow = true;
})
if (allow) next();
else res.status(403).send({error: 'access denied'});
})//handle your reject and catch here
} else res.status(400).send({error: 'invalid token'})
}
That code was roughed in just for this example, so I wouldn't go copy and pasting it, but it should give you the right idea.

Role rights in Node.js
Part 1:What is role and rights?
Role rights implementation is important part of any software.Role is a position of responsibility and every responsibility enjoys some rights given to them.There may be some common rights between few roles and some rights may strictly belong to specific role.
Rights are Urls that a role is authorised to access.It is thus necessary to create collection in db storing information of rights to a role.
We have role collection schema as
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const RoleSchema = new Schema({
roleId:{
type:String,
unique:true,
required:[true,"Role Id required"]
},
type:{
type:String,
unique:true,
required:[true,"Role type is required"]
},
rights:[{
name: String,
path: String,
url: String
}]
});
module.exports = Role = mongoose.model('role',RoleSchema);
Now remember every role that is supose to exist is in Role collection and of above schema type.
In schema rights array of object we see the object has keys:
name(for name of url like "set-username")
path(for base path hit "/users/")
url(requested url or complete path "/users/set-username")
Thus if a user with role user has right to change username then he can hit url
/users/set-username.However a wanderer will not be able to access this url.A higher role like admin & superadmin should logically have access to all lower role rights(urls).
Role in real application are:-
Wanderer (Someone who is just visiting our site.He should be able to access all public routes.Simple Urls/Public urls accessible to all thus need not to make a seprate role for this as it is not any authenticated right.)
Guest (Someone who has registered but not verified say email not verified).
User (Someone who has his verified email)
Admin (Made a Admin by SuperAdmin after verifying.he enjoy most of rights)
Superadmin (Master of application.He enjoy some more sophisticated rights.More rights then admin)
Till Now we have understood what excatly is right and how it is mapped to a role.
Part 1.5:Registerd Urls/Config Urls
Here we have a file called registeredUrls.js which is like:
module.exports = {
simple:{
"/":[""],
'/users/':["login","register"],
},
auth:{
//admin
'/admin/': ['load-users' , 'set-new-password','delete-user'],
'/teacher/':["add-teacher","delete-teacher","edit-teacher"],
'/student/':["add-student","delete-student","edit-student","test-result"],
//user
'/test/':["view-test","submit-test"],
'/profile/': ['change-username', 'update-profile-data', 'set-new-password', 'upload-pic', 'update-social-links'],
'/teacher/':['load-teacher'],
'/student/':['load-student']
}
}
Similarly confgUrls.js
const configUrls= {
'/roles/': ['get-rights', 'create', 'update-rights', 'load', 'delete', 'assign']
}
module.exports = configUrls;
Part 2:Creating SuperAdmin
This is most essential part of application.Whenever server starts for first time or restart/reboot this step occurs.In config/init.js follow procedure:
Load All simple urls(public) and Auth Urls(admin & users) & super-admin-specific urls into superAdminRights[].
Then run a function to create a user with role superadmin if doesn't exist.
Get a Role of type:"superadmin" if found:replace its rights with new rights(superAdminRights).else:create Role of type :"superadmin" and then fill its rights(superAdminRights).
At end of this function call we are always sure that we have a superadmin in application with all its sophisticated urls/rights initialised.
Part 3:Super-Admin-Specific-Urls
These are rights that are enjoyed by super admin only and must be maintained in seprate file in parallel to registerd url file.These include url rights which map routes used only by superadmin.
Here we have routes to create role,load roles,get-rights for a roleId,update-rights for roleId/role type, assign-role to a user,delete a role.
For each user in code we need to change their role from guest to user(say after email verification).Or guest/user to admin by superadmin using assign-role url.Then updating admin rights using route update-rights.
The process ensure that every role has A collection Document and filled rights there.
Part 4:Authenticator Middleware
This heart of our RBACS logic.Here we use a middleware which follow process:
1. Fill all authentication required urls in a [AUTH_URLS] with auth-urls(registeredUrls.js) & super-admin-specific-urls(confgUrls.js) and simple url in different [SIMPLE_URLS].
2. Then check if (AUTH_URLS.indexOf(request.url) > -1){3rd step} else if (SIMPLE_URLS.indexOf(request.url)>-1){then it is public url so simple allow next()} else { response unknown url}
3. In this step we know that url being requested in AUTH_URLS thus required token check for authorization token header if found then extract token from it then{4th step}.if no authorization header found response "required token"/"unknown".
4. Token found, now verify that this token has a valid session.If yes {5th step} else token session not found login again.
5. validate jwt token verifying it if verified{6.step} else response "not valid jwt token".
6.till now correct url requested & token session exist & jwt valid token.Now using role-type information from session(stored in session is basic info of user and token) find in Role for this user session role-type thus we have its rights[].Now see if the request.url is included in rights[].if found {7th step} else {reponse "role not found/unknown user"}.
7. if found then {has access to this url next() } else { response "access denided"}

Related

How to query the "Exposed Api" for all users calendars in a Tenant?

I want to be able to - from a crohn job on a 3rd. party server - query and modify all users in my the Tenants (organisation) calendars - but i just end up with a "Application ID URI" that i don't know what to do with.
No matter how i query the microsoft graph API i get "Invalid Audience" and the docs on the subject doesn't really seem to highlight how exactly to specify the correct "audience" or what a "resource" actually entails.
These are the steps i have taken in the Azure Active Directory portal:
I use the Client Credentials Grant flow so i can access with a Cron and not through a user. https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
1) Registered an application in the portal. So i get some ID's (anonymized here)
Application (client) ID
:
51ed7b6d-d33e-491e
Directory (tenant) ID
:
c181f4f3-912b-4acf-
Object ID
:
3f52a799-f2ab-4161-a81c
2) Created a secret so i can provide that together with the Application id to get a token.
3) Given "Api Application Permissions" to https://graph.microsoft.com/Calendars.ReadWrite.
4) "Exposed an Api" and called it's scope "readwrite calendars" so the Application ID URI with the scope end up being: api://51ed7b6d-d33e-491e-9d40-1/readwritecalendars
5) Authorized the API to the application with the Application (client) ID from step 1 so i don't need admin consent to query.
Problem is now i just end up with an "Application ID URI", okay how i do i exactly query my for the calendars then?
I am using https://github.com/TheNetworg/oauth2-azure here:
I am able to successfully get a token:
$provider = new \TheNetworg\OAuth2\Client\Provider\Azure([
'clientId' => env('OAUTH_APP_ID'),
'clientSecret' => env('OAUTH_APP_PASSWORD'),
'redirectUri' => 'http://www.google.dk'
]);
$provider->tenant = 'secret';
$token = $provider->getAccessToken('client_credentials', [
'resource' => 'https://graph.windows.net',
]);
$provider->urlAPI = "https://graph.microsoft.com/v1.0/";
$provider->resource = "https://graph.microsoft.com/";
return $token ;
Gives me:
{
"token_type": "Bearer",
"ext_expires_in": "3600",
"expires_on": "1562669834",
"not_before": "1562665934",
"resource": "https://graph.windows.net/",
"access_token": "longstringofnumbers",
"expires": 1562269224
}
There is no "Aud" or "Audience" field returned.
This i where i get confused, how exactly do i form a link, how do i say that i want to query all calendars with the token?
Looking at the outlook graph documentation it specifies i need to query like this (https://learn.microsoft.com/en-us/graph/api/user-list-calendars?view=graph-rest-1.0&tabs=http):
GET /me/calendars
This doesn't really make sense to me as "me" seems to imply a user, i am trying to query stuff in a tenant not tied to a specific user?
Anyway if i try to query like:
$provider->get('me/calendars',$token);
Or different combinations of this i just get:
Access token validation failure. Invalid audience.
To sum it all up: how do i actually query the API to list/modify calendars, what is the actual endpoint i have to hit? And where do i put the actual query?
Thanks in advance!
A result would be a JSON object instead of the error message. A successful return to the query. In this case a list of all users calendars, or a "successfully updated" after modifying a calendar.
In Azure AD you can specify "API Permissions" for an app registration. If you want to query all calendars with one access token you should use "Application permissions".
Point 4 is for other types of applications like Office Add-Ins.
For all request you can use the default Graph endpoint https://graph.microsoft.com/v1.0/. So if you wan't to query a user calendar you should use this:
GET https://graph.microsoft.com/v1.0/<UserId or UPN>/calendar
Got it working with:
$provider = new \TheNetworg\OAuth2\Client\Provider\Azure([
'clientId' => env('OAUTH_APP_ID'),
'clientSecret' => env('OAUTH_APP_PASSWORD'),
]);
$provider->tenant = env('AZURE_TENANT');
$provider->urlAPI = "https://graph.microsoft.com/v1.0/";
$provider->resource = "https://graph.microsoft.com/";
$token = $provider->getAccessToken('client_credentials', [
'resource' => 'https://graph.microsoft.com',
]);
$currentCalendarEvents = $provider->get('users/some#some.com/calendars/Calendar/events?$top=1000', $token);
The $provider->resource and urlapi has to be set before the request off course as written in the docs: https://github.com/TheNetworg/oauth2-azure#microsoft-graph

aspnetcore.identity - in role, but getting denied access

Core 2.0, using AspnetCore.Identity. I created a few roles, including "Admin".
//initializing custom roles
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
string[] roleNames = { "Admin", "Training", "Operations", "Membership", "Individual" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
// ensure that the role does not exist
if (!roleExist)
{
//create the roles and seed them to the database:
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
I checked the SQL tables, and they're all there.:
Then I add myself to the Admin role (not the full method, but the relevant parts):
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var _eric = await UserManager.FindByEmailAsync("username#gmail.com");
await UserManager.AddToRoleAsync(_eric, "Admin");
I check the tables, and I'm in there (along with another guy I added to a different role):
I then travel over to my method and slap Authorize on it with two of the roles, one of which I'm in (Admin):
[Authorize(Roles ="Training, Admin")]
public ActionResult Index()
{
return View();
}
And then I get access denied. I'm missing something, but can't figure out what step I messed up. User is in there, I'm logged in, the data tables show me as having the role assigned, and the Authorize tag looks good.
Roles are claims and claims are loaded only on sign in. If you modify a claim (such as by adding a role), you must sign the user out and either automatically sign them back in or prompt the user to re-authenticate, to reload the claims.

Accessing Azure Assigned Groups via Razor or Controllers in ASP.NET Core

My ASP.NET Core web app is using an Azure Active Directory tenant and using OpenID Connect to sign-in users. I'm able to login successfully and I'm able to view the full list of Claims on a user with the following code:
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
My security token includes the following "groups":
{
type: "groups",
value: "e8f1a447-336a-47bb-8c26-79f1183f989f"
},
{
type: "groups",
value: "38421450-61ba-457b-bec2-e908d42d6b92"
}
I'm having trouble trying to determine how to capture these groups to perform logic in my Razor views and controllers. For example, I need to hide/show a button in my Razor view depending on whether a user is in a specific group. In my controllers I may need to allow/deny an action.
What is the standard/preferred method to do this in ASP.NET Core?
When Azure AD adds applicable group claims to the token it issues for users, the value for the group claim will be the Object ID of the security group and not the name of the security group(a group’s name can be changed in the directory so it is not a reliable identifier for the group ) .You could check whether the user’s existence in the security group in controller by :
// Look for the groups claim for the 'Dev/Test' group.
const string devTestGroup = "99dbdfac-91f7-4a0f-8eb0-57bf422abf29";
Claim groupDevTestClaim = User.Claims.FirstOrDefault(
c => c.Type == "groups" &&
c.Value.Equals(devTestGroup, StringComparison.CurrentCultureIgnoreCase));
// If the app has write permissions and the user is in the Dev/Test group...
if (null != groupDevTestClaim)
{
//
// Code to add the resource goes here.
//
ViewBag.inGroup = true;
}
else
{
ViewBag.inGroup = false;
}
Then in view , you could control whether show/hide links/buttons :
#if (ViewBag.inGroup)
{
<div>show/hide button/link goes here</div>
}
In your AppSettings.json, add your group's name and GUID object ID:
"AzureAdAuthorizationGroups": {
"MyGroup": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
Next, hook up authorisation in your Startup.cs ConfigureServices method
services.AddAuthorization(options => {
options.AddPolicy("MyGroup", policyBuilder => policyBuilder.RequireClaim("groups", Configuration.GetValue<string>("AzureAdAuthorizationGroups:MyGroup")));
});
Finally in your view:
#if ((await AuthorizationService.AuthorizeAsync(User, "MyGroup")).Succeeded)
{
// ...
}

Parse - Is this the correct method on how to use ACL and CLP to protect Users and other Objects

New to Parse, requesting feedback from community on whether I have set up Parse ACL and CLP security correctly....
It seems that Parse Security is all about building security layers on top off other security layers.
My concerns are that someone can use clientkey and access various objects and their data unless I lock it down correctly with ACL and CLP's....
Below, I have briefly outlined the three objects (_User, _Role and my FileUpload), the associated code and relationships.
The website I am building to test security will give a user the ability to create a user (in _User) and upload their files to FileUpload object.
_User Object
From the login page, I only want to allow user creation, user table is set up as follows :-
"public access" CLP set with "Create" permission only, rest are disabled.
CloudCode afterSave on user creation sets ACL on new user (code below).
CloudCode afterSave on user creation assigns role called "signed" to new user. (code below, role "signed" specifics described below)
In the CloudCode on User afterSave, code for items 2 and 3 above is below:-
Parse.Cloud.afterSave(Parse.User, function(request) {
if (!request.object.existed()) {
Parse.Cloud.useMasterKey();
var user = request.object;
if (user.existed()) { return; }
//2. set ACL on user
var acl = new Parse.ACL(user);
acl.setPublicReadAccess(false);
user.setACL(acl);
user.save();
//3. assign role "signed" to user
var roleQuery = new Parse.Query(Parse.Role);
roleQuery.equalTo("name", "signed");
roleQuery.first().then(function(role) {
role.getUsers().add(user);
return role.save();
});
}
});
_Role Object
I created a role in _Role called "signed"
"signed" role ACL is set to "Master Key Only".
"public access" read & write CLP for _Role is disabled.
No CLP is enabled in _Role (see point 2), therefore, no CRUD is permitted, but I am not sure how Parse allows "signed" role to work.
FileUpload Object
This object is set up so that the user can upload files to FileObjects only when logged in.
A pointer is present in FileUpload Object to User Object.
"public access" read & write CLP for FileUpload Object is disabled.
"signed" CLP for FileUpload Object added.
CLP permissions for role "signed" in FileUpload Object is set to "Find", "Create", "update" only, this permits ACL to be set to user objectId
To ensure that the user cannot see another user's fileuploads, I have set the following ACL on "FileUpload" afterSave.
Parse.Cloud.afterSave("FileUpload", function(request, response) {
var currentUser = Parse.User.current();
if(!currentUser) {
console.log('This is a user that is not logged in trying to upoad a file');
} else if (currentUser) {
var acl = new Parse.ACL(currentUser);
acl.setPublicReadAccess(false);
var user = request.object;
user.setACL(acl);
user.save();
response.success();
} else {
console.log('Cannot upload file');
}
});
This might be a bit late, but I'll give you my feedback either ways, since no one answered yet.
First, for the _User class, there's no need to set these ACLs, since a Parse.User by default has public read and read and write permissions. Since you have a create only CLP there's no need to explicitly set the public access to false, unless you plan to change the CLP in the future.
Second, for the Parse.User afterSave I recommend using the masterkey only when you need it. Hence, you could remove the useMasterKey call at the beginning and only call it with roleQuery.first.
The FileUpload afterSave has a bug I believe: you want to set the request.object's ACL, rather than the user's ACL.
Other than that it looks good.

Check the Sharepoint user exists in AD

If a user is removed from AD, the user still exists in sharepoint. Now i want to check if a user exists in AD, so is there any sharepoint object model can do this?
Thanks in advance.
If the user is in the domain user Group, it collects all users under that user group and loops the list to that try to find the current user. If a user is found, it returns true, otherwise returns false.
Check this article Check Whether User Exists in Active Directory that include some security related issues to achieve the requirement.
// Get ad users in the groups. Since MOSS does
// not support nested groups
// this will always be a collection of AD users
// and groups
foreach (SPUser user in group.Users)
{
// Check if this is a Group
if (!user.IsDomainGroup)
{
// Verify if the user name matches the user name in group
if (user.LoginName.ToUpper().Equals(upperCaseUserName))
{
// if a match is confirmed, return from the method.
// There is no need to continue
userIsInGroup = true;
return;
}
}
else {
// If the AD entity is a User Group,
// then check for users in that group
if (IsUserInADGroup(web, user.LoginName,
username, out reachedMax))
{
userIsInGroup = true;
return;
}
}
Hope this help..

Resources