I'm trying to create some REST API with user roles like admin, superadmin etc.
I was trying to achieve this by using feathers-permissions module, but there are none working examples and the internet. Have you ever dealt with such task?
What I do now is:
feathers generate app and then feathers generate authentication. What should I do next?
The secret to implementing permissions and roles in Feathers is that Hooks really provide everything you need with all the flexibility you might want. There isn't really a a need to spend time looking for a separate module and learning it's API.
Store the permissions (which are normally just strings) in an array on the user (or a separate permissions service based on the users ID) and then in a before hook check if the user is allowed to perform the operation the hook is registered as (here the permission is called messages::create), and if not throw a Feathers error:
const { Forbidden } = require('feathers-errors');
app.service('messages').hooks({
before: {
create: [ context => {
// `params.provider` is set for any external access
// usually we don't need to check permissions for internal calls
const isExternal = !!context.params.provider;
if(isExternal && !context.params.user.permissions.includes('messages::create')) {
throw new Forbidden('You are not allowed to access this');
}
}]
}
});
This pattern can also be implemented with any existing permissions module for Node. feathers-permissions is a simple module that allows to do this more easily.
For more information also see the blog posts about Access Control Strategies in FeathersJS and Easy API Authorization with CASL and Feathers.
Related
I want to implement authorization layer on my microservices project. I have 3 microservices customer-service, workspace-service and cloud-service. So if a customer want to create a cloud instance he will create on behalf of a workspace which means that the cloud instance will belong to the workspace instead of him and also he may invite other customer to the workspace, and have access to the cloud instance. The data structure may looks like this.
// workspace
{
"workspaceId": "ws-123",
"customers": ["customer-123", "customer-456"]
}
// cloud-instance
{
"cloudId": "cloud-123",
"workspaceId: "ws-123"
}
I want to implement authorization logic that check if a user has access to a particular cloud instance. In order to do that I need to have workspaceId somewhere else in my authentication object. One thing that I can think of is that to put workspaceId in the jwt claims so my jwt may looks like this.
header.{ ..., workspaceId: ["ws-123"] }.signature
but the drawback of this solution is that the workspaceId claim won't be updated until the token has been refresh.
another solution that is to implement a service that query data from workspace-service and use it to validate.
const hasAccess = (customerId, workspaceId_on_cloud_instance) => {
let actual_workspaceId_that_he_has = workspace_service_exposed_api.findWorkspaceByCustomerId(customerId)
return actual_workspaceId_that_he_has == workspaceId_on_cloud_instance
}
but this approach would heavily rely on workspace-service if workspace-service is down then the other service can not handle a request at all since it doesn't have access to workspaceId.
So IMHO I would rather go for the 1st option since I use oauth2.0 and token will be refresh every 30s, but is it bad practice to do that? I wonder if there's better solution.
Having 3 microservices you cannot implement functionality with assumption that one service is down. I have feeling that access token lifespan is also defined based on this restriction - to have up to date data in the token. As I correctly understand, in worst case there is also ~30 sec. delay related to workspaceId update in token payload.
Token claim will change only when you invite or remove person from workspace so this service must work anyway. I would use 2nd solution with longer token lifespan to not generate it so often.
Another solution is to generate new token every time when workspace is changed - you can treat adding/removing to workspace as a business logic that invalidates token, but probably in this case communication to workspace service is also required.
If you are afraid that microservice will be down or you will have problem with communication, maybe you should focus more on app infrastructure or choose a new solution based on monolith application.
And back to question from title - adding any custom claim to token payload is standard approach.
I want to interact with the Google's Drive API from a Cloud Function for Firebase. For authentication / authorization, I am currently relying on getClient, which I believe uses the Service Account exposed in the Cloud Function environment:
import { google } from 'googleapis';
// Within the Cloud Function body:
const auth = await google.auth.getClient({
scopes: [
'https://www.googleapis.com/auth/drive.file',
],
});
const driveAPI = google.drive({ version: 'v3', auth });
// read files, create file etc. using `driveAPI`...
The above approach works, as long as target directories / files list the email address of the service account as an editor.
However, I'd like to interact with the Drive API on behalf of another user (which I control), so that this user becomes (for example) the owner of files being created. How can I achieve this?
To set the user you want to deligate as in your code just add a subject to the client, with user being the email of the user on your workspace domain.
const client = await auth.getClient();
client.subject = user;
I was able to achieve calling the Drive API on behalf of another user thanks to the suggestions made by #DalmTo.
The first step is to configure domain-wide delegation of authority in Google Workspace for the default AppEngine Service Account.
Next, the code in my question can be extended to receive a subject with the email of the user to impersonate via the clientOptions:
import { google } from 'googleapis';
// Within the Cloud Function body:
const auth = await google.auth.getClient({
scopes: [
'https://www.googleapis.com/auth/drive.file',
],
clientOptions: {
subject: 'email#to.impersonate',
},
keyFile: './serviceAccountKey.json',
});
const driveAPI = google.drive({ version: 'v3', auth });
// read files, create file etc. using `driveAPI`...
Now, the truly odd thing is that this only works when also passing the service account key via the keyFile option in addition. I.e., relying on the key being automatically populated (as it is in Cloud Functions for Firebase) does NOT work when also trying to impersonate a request. There are ongoing discussions of this bug on GitHub, specifically see this comment.
To make domain-wide delegation work without having to provide the keyFile option (which will likely require you to manage the sensitive key file in some way), another option is to sign a JWT and use it to obtain an Oauth token. The approach is outlined by Google, and I found this SO answer providing a code-example very helpful.
So I have a single page frontend only app. Right now I have something like this
// db.js
import firebase from "firebase/app"
import "firebase/firestore";
var firebaseConfig = {
...
};
export const db = firebase
.initializeApp(firebaseConfig)
.firestore();
in main.js I was experimenting with putting the db instance in the global window scope just to see if I could go to the chrome web console and access it to submit a doc and indeed I can
// main.js
import { db } from './db'
window.db = db;
and then from chrome console
db.collection("test").add({'somekey': 'Can I add this doc?'})
How do I prevent someone from doing this without having a real backend to check auth? I like the reactivity of vue + firebase. If I don't expose the db variable to global scope is that enough? I was reading this post:
https://forum.vuejs.org/t/how-to-access-vue-from-chrome-console/3606/2
because any variable you create inside your main.js fiel will still not be globally available due to how webpack
One of the great things about Firestore is that you can access it directly from within your web page. That means that within that web page, you must have all configuration data to find the relevant Google servers, and find your Firebase project on those servers. In your example, that data is part of firebaseConfig.
Since you app needs this configuration, any malicious user can also get this data from your app. There is no way to hide this: if you app needs, a sufficiently motivated malicious user will be able to find it. And once someone has the configuration, they can use it to access your database.
The way to control access to the database, is by using Firebase's server-side security rules. Since these are enforced on the server, there is no way to bypass them, neither by your code, nor by the code that a malicious user writes.
You can use these security rules to ensure that all data is valid, for example making sure that all the required fields are there, and that there's no data that your app doesn't use.
But the common approach is to also ensure that all data access is authorized. This requires that your users are authenticated with Firebase Authentication. You can either require your users to sign in with their credentials, or you can anonymously sign them in. In the latter case they don't need to enter any credentials, but you can still ensure for example that each user can only write data to their own area of the data, and that they can only read their own data.
I have a Torii adapter that is posting my e.g. Facebook and Twitter authorization tokens back to my API to establish sessions. In the open() method of my adapter, I'd like to know the name of the provider to write some logic around how to handle the different types of providers. For example:
// app/torii-adapters/application.js
export default Ember.Object.extend({
open(authorization) {
if (this.provider.name === 'facebook-connect') {
var provider = 'facebook';
// Facebook specific logic
var data = { ... };
}
else if (this.provider.name === 'twitter-oauth2') {
var provider = 'twitter';
// Twitter specific logic
var data = { ... };
}
else {
throw new Error(`Unable to handle unknown provider: ${this.provider.name}`);
}
return POST(`/api/auth/${provider}`, data);
}
}
But, of course, this.provider.name is not correct. Is there a way to get the name of the provider used from inside an adapter method? Thanks in advance.
UPDATE: I think there are a couple ways to do it. The first way would be to set the provider name in localStorage (or sessionStorage) before calling open(), and then use that value in the above logic. For example:
localStorage.setItem('providerName', 'facebook-connect');
this.get('session').open('facebook-connect');
// later ...
const providerName = localStorage.getItem('providerName');
if (providerName === 'facebook-connect') {
// ...
}
Another way is to create separate adapters for the different providers. There is code in Torii to look for e.g. app-name/torii-adapters/facebook-connect.js before falling back on app-name/torii-adapters/application.js. I'll put my provider-specific logic in separate files and that will do the trick. However, I have common logic for storing, fetching, and closing the session, so I'm not sure where to put that now.
UPDATE 2: Torii has trouble finding the different adapters under torii-adapters (e.g. facebook-connect.js, twitter-oauth2.js). I was attempting to create a parent class for all my adapters that would contain the common functionality. Back to the drawing board...
UPDATE 3: As #Brou points out, and as I learned talking to the Torii team, fetching and closing the session can be done—regardless of the provider—in a common application adapter (app-name/torii-adapters/application.js) file. If you need provider-specific session-opening logic, you can have multiple additional adapters (e.g. app-name/torii-adapters/facebook-oauth2.js) that may subclass the application adapter (or not).
Regarding the session lifecycle in Torii: https://github.com/Vestorly/torii/issues/219
Regarding the multiple adapters pattern: https://github.com/Vestorly/torii/issues/221
Regarding the new authenticatedRoute() DSL and auto-sesssion-fetching in Torii 0.6.0: https://github.com/Vestorly/torii/issues/222
UPDATE 4: I've written up my findings and solution on my personal web site. It encapsulates some of the ideas from my original post, from #brou, and other sources. Please let me know in the comments if you have any questions. Thank you.
I'm not an expert, but I've studied simple-auth and torii twice in the last weeks. First, I realized that I needed to level up on too many things at the same time, and ended up delaying my login feature. Today, I'm back on this work for a week.
My question is: What is your specific logic about?
I am also implementing provider-agnostic processing AND later common processing.
This is the process I start implementing:
User authentication.
Basically, calling torii default providers to get that OAuth2 token.
User info retrieval.
Getting canonical information from FB/GG/LI APIs, in order to create as few sessions as possible for a single user across different providers. This is thus API-agnotic.
➜ I'd then do: custom sub-providers calling this._super(), then doing this retrieval.
User session fetching or session updates via my API.
Using the previous canonical user info. This should then be the same for any provider.
➜ I'd then do: a single (application.js) torii adapter.
User session persistence against page refresh.
Theoretically, using simple-auth's session implementation is enough.
Maybe the only difference between our works is that I don't need any authorizer for the moment as my back-end is not yet secured (I still run local).
We can keep in touch about our respective progress: this is my week task, so don't hesitate!
I'm working with ember 1.13.
Hope it helped,
Enjoy coding! 8-)
I'm quite new to JayData, so this may sound like a stupid question.
I've read the OData server tutorial here: http://jaydata.org/blog/install-your-own-odata-server-with-nodejs-and-mongodb - it is very impressive that one can set up an OData provider just like that. However the tutorial did not go into details about how to customize the provider.
I'd be interested in seeing how I can set it up with a custom database and how I can add a layer of authentication/authorization to the OData server. What I mean is, not every user may have permissions to every entity and not every user has the permission to add new entities.
How would I handle such use cases with JayData?
Thanks in advance for your answers!
UPDATE:
Here are two posts that will get you started:
How to use the odata-server npm module
How to set up authentication/authorization
The $data.createODataServer method frequently used in the posts is a convenience method that hides the connect/express pipleline from you. To interact with the pipeline examine the method body of $data.createODataServer function found in node_modules/odata-server folder.
Disregard text below
Authentication must be solved with the connect pipeline there are planty of middleware for that.
For authorization EntityContext constructor accepts an authorization function that must be promise aware.
The all-allow authorizator looks like this.
function checkPerm(access, user, entitysets, callback) {
var pHandler = new $data.PromiseHandler();
var clbWrapper = pHandler.createCallback(callback);
var pHandlerResult = pHandler.getPromise();
clbWrapper.success(true); // this grants a joker rw permission to everyone
//consult user, entitySet and acces to decide on success/error
//since you return a promise you can call async stuff (will not be fast though)
return pHandlerResult;
}
I have to consult with one of the team members on the syntax that let you pass this into the build up process - but I can confirm this is doable and is supported. I'll get back with the answer ASAP.
Having authenticated the user you can also use EntityContext Level Events to intercept Read/Update/Create/Delete operations.
$data.EntityContext.extend({
MySet: { type: $data.EntitySet, elementType: Foobar,
beforeDelete: function(items) {
//if delete was in batch you'll get multiple items
//check items here,access this.request.user
return false // deny access
}
});
And there is a declarative way, you can annotate Role names with permissions on entity sets, this requirest that your user object actually has a roles field with an array of role names.
I too have been researching oData recently and as we develop our platform in both node and C# naturally looked at JayStorm. From my understanding of the technical details of JayStorm the whole capability of Connect and Express are available to make this topic possible. We use Restify to provide the private API of our platform and there we have written numerous middleware modules for exactly this case.
We are using JayData for our OData Service layer also, and i have implemnment a very simple basic authentication with it.
Since the JayData is using Express, so we can leverage Express' features. For Basic Auth, the simplest way is:
app.use(c.session({ secret: 'session key' }));
// Authenticator
app.use(c.basicAuth('admin', 'admin'));
app.use("/odata.svc", $data.JayService.OData.Utils.simpleBodyReader());
you also can refer to this article for more detail for authentication with Express: http://blog.modulus.io/nodejs-and-express-basic-authentication
Thanks.
I wrote that blogpost, I work for JayData.
What do you mean by custom database?
We have written a middleware for authentication and authorization but it is not open source. We might release it later.
We have a service called JayStorm, it has a free version, maybe that is good for you.
We probably will release an appliance version of it.