NestJS - How to implement RBAC with organization-scoped roles - nestjs

I am designing a REST backend in Nest.js that needs to allow Users to be a part of multiple Organizations. I want to use role-based access control, such that a user can have one or more named roles. Crucially, these roles need to be able to be either "global" (not dependent on any organization, ex. SUPERUSER), or "scoped" (specific to an organization, ex. MANAGER).
I have decided on this basic database design, which links Users to Organizations using the Roles table in a many-one-many relationship:
As you can see, the organizationId field on a Role is optional, and if it is present, then the user is linked to that organization through the role. If it is not present, I assume this to be a "global" role. I find this to be an elegant database design, but I am having trouble implementing the guard logic for my endpoints.
The guard logic would go something like this:
Look up all the Roles from the database that match the current userId.
For global routes, check that at least one of the returned roles is in the list of required roles for the route.
For scoped routes, do the same, but also check that the organizationId of the role matches the organization ID associated with the operation (I'll elaborate below).
Consider these two endpoints for Jobs. The first will retrieve all the jobs associated with a specified organization. The second will find a single job by its id:
Example route 1:
GET /jobs?organizationId=XXXXX
#Roles(Role.MANAGER, Role.EMPLOYEE)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get()
getMyJobs(#Query() query: {organizationId: string}) {
return this.jobsService.getJobs({
organizationId: query.organizationId,
})
}
Example route 2:
GET /jobs/:jobId
#Roles(Role.MANAGER, Role.EMPLOYEE)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get(':jobId')
getJob(#Param('jobId') jobId: string) {
return this.jobsService.getJob(jobId)
}
In the first example, I know the organizationId without doing any work because it is required as a query parameter. This id can be matched against the id specified in the Role. This is trivial to validate, and ensures that only users who belong to that organization can access the endpoint.
In the second example, the organizationId is not provided. I can easily query it from the database by looking up the Job, but that is work that should be done in the service/business logic. Additionally, guard logic executes before getJob. This is where I am stuck.
The only solution I can come up with is to pass the organizationId in every request, perhaps as a url parameter or HTTP header. Seems like there should be a better option than that. I'm sure this pattern is very common, but I don't know what it is called to do any research. Any help regarding this implementation would be greatly appreciated!

It is just another option for you.
You can modify a user object inside RolesGuard by adding a field that stores available organizations for him/her. So you need to calculate organizations for user, who makes a request inside a guard and then put a result array with ids of organizations to a user field (user.availableOrganizationIds = []). And then use it for filtering results
#Roles(Role.MANAGER, Role.EMPLOYEE)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get()
getMyJobs(#User() user) { // get a user from request
return this.jobsService.getJobs({
organizationIds: user.availableOrganizationIds, // <<- filter by organizations
})
}

Related

What is the proper way to restrict API access based on user type?

Suppose I have two users, A and B.
A is allowed to access all the resources available, but B can access them only partially.
What is the proper way to prevent B from accessing resources to which B does not have permission?
Should I create some sort of whitelist that specifies only URLs B can access?
The following snippet is what I currently have.
It is a middleware which checks whether each request is allowed to access specific URLs.
const ALLOWED_URLS = ['api/resource1', 'api/resource2', 'api/resource3'];
const sessionCheck = (req, res, next) => {
const url = req.originalUrl;
// check whether accessing URL is allowed
}
Is there any better approach than this?
What you're asking about is called IAM (Identity & Access Management).
The roles, ownership, and permissions on specific resources is generally persisted in your database as business domain objects of their own. This is language agnostic and not specific to node or express. You should NOT build a white list of URLs. The resources you want to protect are in your database. You should map them to permission objects that in turn map to users. Your not protecting URLs, your protecting resources. Everyone is allowed to access any URL but the resources behind them are what you're protecting and those rules/permissions go in your database.
If your looking for industry standards then here are some common names/terms for those IAM objects that would be persisted in your DB:
Group
Role
User
Policy
User A's access would generally be determined by what role they have or what group they belong to. Whether you give user A a role, put them in an authorized group, or give them a direct permission doesn't really matter, these groupings exist to reduce duplication so you can pass around or take away multiple permissions at once. But the general idea is the same; your resources exist in a DB and you specify what is the required or allowed roles, groups, and users that can access those resources and you map users to groups, roles, etc as simple table entries. This means the real authorization logic is not in Node or Express or even in your webapp, its built into the resources themselves and tied to how the data is retrieved.
Resource Retrieval Code
When anyone makes a request for a given resource the query, regardless of your database type, should fail if the user is not authorized. This means the way you are retrieving data must be directly tied to how its authorized and not two separate steps; meaning you should not get the resource, then check if the user is authorized and you should not check if the user is authorized before getting the resource. The best practice is fuse/join the two so that you can not get the resource unless your authorized because we look for the resource using your role and if you don't have the right role we can't find the resource.
For example:
function getAccount(userId,accountId) {
makeSQLCall(userId,accountId)
}
SELECT *
FROM accounts a
WHERE a.accountId = accountId AND u.userId = userId
JOIN users u ON a.allowedRole = u.role
The SQL doesn't matter as the same can be done with other technologies but the last line is the most important (account.allowedRole = user.role). You literally pull the resource from the database using the users role so that if they are not authorized this fails and no data is returned. This is also your base/parent data retrieval function so other functions that don't know about authorization can use this function and authorization will be dealt with under the hood.
Express Pseudo Code
router.get('/api/resource1',function(req,res){
var user = utility.getUserFromRequest(req)
var resource = accountService.getAccount(user,req.body.accountId)
sendResponse(resource)
})
Looking at the code above the authorization is built into your domain model not your web application. If the user making the request above is not authorized they will get no data back. You have to figure out in your own business use case is it enough to return an empty result or do you need to return a 401 HTTP error code. If you need to inform your non-malicious users they are not authorized you can simply perform isAuthoriized(user,accountId) before running accountService.getAccount as a UI convenience. The strength of this approach is that if you or some other developer forgets to check isAuthorized 1st the call will still return no data since isAuthorized() is just for the users benefit and not security. The security is at the domain/db layer.
Typically you would build user resource routes and validate their ownership when you authenticate and their permissions on authorization.
E.g.
server.get('api/v0/products/:user', authMiddleware, (req, res, next) => {
// from the auth middleware comes a parsed token with payload
if (req.payload.user !== req.params.user) {
return res.status(401).send('not allowed')
}
// do action
})
An alternative would be to only access database objects that matches the payload of your authentication
The example would suggest using JWT middleware with a custom property user

Auth0 subscription plan app_metadata

I'm developing a quiz app which requires authorization for only-subscribed members can see.
How to do that? I'm thinking of putting metadata (is_subscribed) to true for subscribed member and give the scope so he/she can gain permissions.
But, I don't know how to do it. Please help. The docs is so confusing
There are two separate concerns here.
Where to keep the subscription information. app_metadata is fine, or you might choose to do so in a backend database (application specific). A client application will probably handle subscriptions and be in charge of updating that value. If you store the value in app_metadata, you will use Management API v2 to alter the user profile from the application that handles subscriptions.
Add an authorization scope based on the subscription status. In this case, you would use a rule to add a custom scope based on the value of the is_subscribed field. I.e.:
function(user, context, callback) {
if (user.app_metadata && user.app_metadata.is_subscribed) {
context.accessToken.scope = ['read:quiz'];
} else {
// remove the ability to read a quiz if not subscribed.
context.accessToken.scope = [];
}
callback(null, user, context);
}
If you decided to store the subscription information in a backend database instead of in the app_metadata, you would simply access the database from the rule in the above code.

DDD suggest a relation between entities

I have a following structure.
Resources can have multiple endpoints.
Example:
Resource tickets can be accessed on following endpoints:
/api/tickets
/api/agent/tickets
/api/group/5/tickets
/api/tickets/closed etc.
At first, this looks like aggregate, where Resource is AR, and endpoints are child entities.
I also have UserTypes.
What I need is to build a relation between Usertypes and Endpoints, so each UserType can have a diferrent access for endpoints.
Example for, UserType admin could access all endpoints for tickets resource, while user type agent could have access to only portion of endpoints for the same resource.
What would be a suggested way to connect EndPoints and UserTypes in terms of DDD?
Do you need anything else other than a collection of mapping a between Resources and Endpoints on a UserType? This would give you all usertypes their unique resource endpoint access rights
Also seems to be the same question as Solve apparent need for outside reference to entity inside aggregate (DDD)
I would probably create something like the following:
class ResourceEndpoint {
Guid resourceId;
Guid endpointId;
}
class UserType {
List<ResourceEndpoint> ThingsICanAccess;
}

Nodejs Connect Roles to db table

I have a node application written in express.js with passport for authentication and connect-roles for users. I also have a group table that I would like to connect to connect-roles. Is there any documentation on how to go about this. I want connect-roles to use my group table rows for assigning roles.
connect-roles doesn't define any storing mechanisms. it simply injects into the authenticated user you have in session object. Storing the strings equivalent to your roles you test for in connect-roles is up to you. You will normally authenticate the user first (which gives you the user object and his roles) and then do authorize (where connect-roles gets executed). Passport middleware should be used before connect-roles (as per connect-roles documentation)
In my case, I added a roles collection to my user model and took care of retrieving that from my database. You still need to implement the role test function and that's where you reference your User model roles property as you defined it.
Example:
roles.use(function (req, action) {
if (req.isAuthenticated()){
if (req.user.securityRoles.indexOf('admin') >= 0) return true; //admins can access all pages
else return req.user.securityRoles.indexOf(action) >= 0;
}
});
then you protect a route like this:
app.get('/offers', roles.is('offer.read'), offers.index);

How to ensure the same Principal is used during authentication chain in OpenAM?

I need to set up a two-step authentication chain with OpenAM. In the first step, the module requests a user certificate (which has to have been previously linked with an userID) and sends it to an external web service that will validate it and return the userID, which becomes the name of the Principal:
public Principal getPrincipal()
{
return new DataStorePrincipal(userID);
}
On the second step, the module asks the user to type in his userID and password. How do I make sure that the userID typed is the same as the one from step 1?
The modules are chained like this:
Certificate - REQUISITE
ID/Password - REQUIRED
The first module could save the 'userId' in the shared-state map, the 2nd module can read it from the shared-state map. You may look at existing auth-modules source as they provide support for 'shared-state'
You may also look at 'http://docs.forgerock.org/en/openam/10.1.0/admin-guide/index.html#configure-authn-chains'

Resources