Hello,
I’m developing a timer that needs global access to stored records.
I’ve read some other forum posts ( http://support.backendless.com/t/two-questions-about-security-best-practices-on-backendless ) and saw the recommendation to create a “super-user” with the proper permissions and then log-in/log-out this user when the timer is executed.
Is this still the best way to complete this operation, or is there some other way to connect to the tables? The server-side code should already know the app ID and secret key, since those are stored with coderunner and used during execution.
I’ve also seen examples (https://github.com/Backendless/JS-Code-Runner/blob/master/examples/handlers/custom/average-stars.js) where the backend code calls Backendless.Persistence.of(‘whatever’) without ever authenticating, and this just seems to work. I’m assuming this is because the backend app exposed the data publicly and thus no auth was required, but I can’t tell for sure because it isn’t documented.
Hi Cameron,
When your server-side code uses the API to access data, all requests will be processed with the ServerCodeUser role. The role is automatically assigned to the requests IF the server-side code uses the “CodeRunner API Key”. In order to globally grant permission to all data records in Backendless, you would need to make sure that ServerCodeUser has those permissions (it does by default, see the Users > Security Roles screen in console).
Hope this helps.
Regards,
Mark
Hi Mark,
Currently, requests sent from a timer are not being handled by ServerCodeUser. They are being handled by NotAuthenticatedUser.
I’ve tested this by running a find in my timer code. With NotAuthenticatedUser permissions set to deny all, I receive no results (empty array). When I enable the find permission on NotAuthenticatedUser, I receive the expected results.
In both cases, ServerCodeUser has full permissions as you mentioned.
I’m trying to view log files to see which role the request is sending with (I remember seeing a string identifier in other logs), but I’m unable to view any log files - the system says there is an error and an internal ticket has been sent.
Hi Cameron,
Suppose you login a user in your app (or timer) and then make an API request. The backend will assign two roles to the request:
AuthenticatedUser
ServerCodeUser (assuming you're using CodeRunner API Key)
If you do not login a user and make an API request, the backend will also assign two roles:
NotAuthenticatedUser
ServerCodeUser (once again, assuming you're using CodeRunner API key)
With that said, the changes you made and result you got makes sense, since the requests are processed with the NotAuthenticatedUser role in place..
Regards,
Mark
Mark,
Perhaps I’m misunderstanding you. Are both the ServerCodeUser and NotAuthenticatedUser roles applied during the request, or just one?
In your original response, you said:
“When your server-side code uses the API to access data, all requests will be processed with the ServerCodeUser role. The role is automatically assigned to the requests IF the server-side code uses the “CodeRunner API Key”. In order to globally grant permission to all data records in Backendless, you would need to make sure that ServerCodeUser has those permissions (it does by default, see the Users > Security Roles screen in console).”
My take-away from this statement was in the first sentence - requests from server side code will be processed with the ServerCodeUser role, if the CodeRunner API key is used.
I’m running everything through CodeRunner (and thus, the CodeRunner API key).
The ServerCodeUser role is either not applied, or is overridden by something else (perhaps the NotAuthenticatedUser role).
Can you explain in more detail what I’m missing here?
Maybe this is what I’m missing:
(per https://backendless.com/documentation/data/js/data_security.htm)
Table permissions for the user-defined roles. This step is identical to the one described above with the exception that is checks custom roles for the table. Since this guide reviews the decision making process for the Find operation, Backendless checks the column for Find. If any of the custom roles deny access, the operation is rejected and no data is returned.
In this case, am I forced to “authenticate” the timer with a role that has permissions to access the records I need? Or can I somehow instruct ServerCodeUser role permissions to take precedence?
–
Edit: My confusion here is that the above section mentions “user-defined” roles. While I can define permissions on the ServerCodeUser role, by the following description also found on that page, ServerCodeUser is a system role.
•“User-defined roles” – roles created by the application developer
•“System roles” – roles built into Backendless (Authenticated User, NonAuthenticated User, SocialUser, etc)
Cameron,
The backend assigns as many roles as would be applicable. If there is no authenticated user, there will be at least 2 roles: NotAuthenticatedUser and a role which matches the API key used in the request.
If there is an authenticated user, it would 2 or more roles (AuthenticatedUser, a role for the API key and any other roles which the user belongs to).
The ServerCodeUser is indeed a “system role” and so is NotAuthenticatedUser. If one of them denies access, the overall decision would be to deny. Any user-defined role sits on top of the system roles and thus would override the access decision if it grants a permission for an operation on a table.
Is the data which your timer needs to access exposed to the app? Should the users of the app have access to it? If the answer is no, then you can allow only business logic access it by denying access to all system roles which are mapped to various API keys (RESTUser, JSUser, etc). Alternatively, the approach where timer authenticates itself with a special user account with custom role would work.
Hope this helps.
Regards,
Mark
We want to:
deny NotAuthenticatedUser access
restrict user access to only their own records
allow the timer access to all records.
It sounds like the primary solution is to just create a global ServerCode account and authenticate it in the timer.
In the case that we chose to go this route, does authentication work the same way as in the standard Javascript SDK? For example, once I call:
Backendless.UserService.login(
username,
password,
true
).then(user => { ... });
…anything further calls made using Backendless.{whatever} will be auth’d using that user account?
Yes, the timer can login using the same API. All subsequent API calls will be done with the authenticated user identity.
1 Like
Thanks for the clarification. My apologies for the confusion.
Mark,
How do you suggest handling this process for an event that may be called quite frequently? I’m writing a handler for a web-hook from a external service. Of course, I can’t control the incoming request rate. If the event is called twice in such a way that the callers “overlap”, is it possible that the following would occur:
- the first “super user” would login
- the second “super user” would login before the first “super user” finalized its request
- the first “super user” now has an invalid login token and can’t save its data
If so, how do you advise clients to mitigate this issue?
Thanks.
I do not recommend logging in every time. Instead, store the value for auth-token in server-side cache, if the value is not in there or is expired, then re-login and put the value back into cache.
What you described is definitely possible only if your backend is configured to disable multiple logins (Users > Login > Enable Multiple Logins).
Regards,
Mark
The auth token is stored as user-token in your SDK cache, correct?
Backendless.LocalCache.set('user-token', 'token here');
And, assuming I had set the user-token (and that was all I had to do), do I verify a proper login through a call to
Backendless.UserService.getCurrentUser()
or through a different method?
I ask because the above method has confused me in the past - if I update the user-token property through Backendless.LocalCache.set(), calling getCurrentUser() returns null.
User object and the token are different things. Having a token does not make the user object available (or even retrievable). Let’s take a look at the code:
https://github.com/Backendless/JS-SDK/blob/4.0/master/src/backendless.js#L2237-L2252
As you can see, the function does not do anything with the token, it relies on “current-user-id” saved in local cache.
As a result, you will be able to retrieve the user object only if user’s objectId is persisted. However, do not rely on local cache - subsequent requests may be processed by a different coderunner than the one which originally handled the login.
Mark
That makes sense. I’m migrating to you from a different BaaS provider - with them I had become accustomed to the User ID being associated with a token, so when I interacted with the library I passed the user’s auth token, not their ID.
Back to permissions: let me try to get a broader scope of things.
Any time I send a request without logging in a user, the NotAuthenticatedUser role will be applied. I don’t want un-authenticated users to be able to send their own requests to execute actions, like registering a user. I do want my server-side JS libraries to be able to register users; however, since I denied all permissions to NotAuthenticatedUser, JS libraries get rejected from registering users (a deny overrides an allow). So, to register a user through the JS library, I first have to create a master user who has the necessary permissions and then log in that user every time I want to register a user, so that the NotAuthenticatedUser role is removed and now I’m allowed to create.
Now I have a different dilemma - storing master user credentials across multiple application implementations (web app vs. ios app vs. whatever). This adds another layer for failure or security issues - if the credentials are compromised, the entire database is toast.
Is the master user approach still your recommendation, or are there any other solutions?
Before we get to the master user discussion, let me ask you a few questions:
We do not have an explicit permission for user registration. How did you deny the user registration operation for NotAuthenticatedUser?
Typically when a user is registering, there would not be a logged in user, hence that request is always being processed as NotAuthenticatedUser (unless a logged in user registers someone else).
Is the nature of your app that only server-side code should be able to register users?
Since there was no permission specified for user creation and I’m receiving a
{ code: 1011, message: ‘User has no permission to create entity’ }
response when I try to register a user via the JS SDK, I assumed that Backendless.User functioned as a wrapper for the standard Data Service and that a “register” was simply a combination of a create from the Data Service CRUD operations and associated events (like sending a welcome email). Based on this assumption, the response would make sense, because I’ve denied all permissions to NotAuthenticatedUser.
You’re definitely right, it doesn’t make sense to register a user while there is a logged in user. That’s why I’m trying to better understand how to properly secure these things, so I can avoid having to use the master user.
As for the nature of the app: We’d like to be able to register users through SDKs without having to authenticate a user to do so. I’ve deviated from the original discussion of doing this within a timer - I should have created a separate topic, my apologies.
You’re right - since a new user object is written to the Users table, it would need a permission for the Create operation assigned to the NotAuthenticatedUser for the Users table if you register users from the client-side. You can do additional “vetting” of the registration process by injecting a “beforeRegister” event handler and validating user information in there.
With that said, you can globally deny access for all operations for the NotAuthenticatedUser role, except for the Create (and possibly Update) operation(s) in the Users table. A permission for the Update operation might be needed for the purpose of password recovery.
1 Like
Perfect. We’ll look into that setup.
I really appreciate your fast + helpful feedback, Mark!
You are welcome. Glad to have you in our community!