Users, Roles, and Permissions

Baqend comes with a powerful user, role, and permission management. This includes a generic registration and login mechanism and allows restricting access to insert, load, update, delete, and query operations through per-class and per-objects rules. These access control lists (ACLs) are expressed through allow and deny rules on users and roles.

Registration

To restrict access to a specific role or user, the user needs a user account. Baqend supports a simple registration process to create a new user account. The user class is a predefined class which will be instantiated during the registration process. A user object has a predefined username which uniquely identifies the user (usually an email address) and a password. The password will be hashed and salted by Baqend before being saved.

DB.User.register('john.doe@example.com', 'MySecretPassword').then(function() {
  //Hey we are logged in
  console.log(DB.User.me.username); //'john.doe@example.com'
});

If you like to set additional user attributes for the registration, you can alternatively create a new user instance and register the newly created instance with a password.

var user = new DB.User({
  'username': 'john.doe@example.com',
  'firstName': 'John',   
  'lastName': 'Doe',   
  'age': 33
});

DB.User.register(user, 'MySecretPassword').then(function() {
  //Hey we are logged in
  console.log(DB.User.me === user); //true
});

LoginOption

If you don't want your user to be logged in or his login only to be valid for the current session after his registration, you can also pass a login option as third parameter:

DB.User.register('john.doe@example.com', 'MySecretPassword', DB.User.LoginOption.NO_LOGIN).then(function() {
  //Hey we are not logged in
  console.log(DB.User.me); //null
});

The following login options exist:

  • LoginOption.NO_LOGIN - Users are not logged in after registration.
  • LoginOption.SESSION_LOGIN - User is only logged in for the active session – after he closes the browser, he will be logged out.
  • LoginOption.PERSIST_LOGIN - User is fully logged in (default behavior).
Tip: You can also use the LoginOption when logging a user in: DB.User.login('username', 'password', DB.User.LoginOption.SESSION_LOGIN), for example.

Email Verification

By default a newly registered user is automatically logged in and does not need to verify his email address. To enable email verification open the settings in the Baqend dashboard and go to the email section. There you can enable the email verification and setup a template for the verification email, which is then automatically send to every newly registered user.

Until the newly registered user has verified his email address by clicking on the verification link in the verification email, he is considered inactive and cannot log in. This state is indicated by a read only inactive field of type Boolean in the user object. After verification this field is automatically set to false. Only the admin is able to set the inactive field manually, e.g. to activate or ban users.

Email Hooks

Every time Baqend sends an email to a user, you can hook to that event by providing a module. There are two emails sent out:

  • the user.register hook is called before a registration email is sent,
  • the user.resetPassword hook is called before a reset-password email is sent.

You can manipulate the contents of the email via the following fields:

  • body - the compiled template containing the variable values.
  • template - the email template you can configure in the settings.
  • link - the link which can be configured in the settings.
  • subject - the email subject.
  • to - email of the receiver.
  • fromName - name of the sender of the email.

Make sure to return the email object in the end; if not, no email will be sent!

Here is an example:

exports.call = function call(db, email, req) {
  var recipient = db.User.me; //Get the recipient's unloaded user object

  email.fromName = 'John Doe'; // change the senders name
  email.subject = 'Welcome to your App'; // change the email subject
  email.body = `Hey Jane! Follow the link to verify your email address: ${email.link} – Yours, John`;

  return email;
}

You can also use additional properties of the created user as well:

exports.call = function call(db, email, req) {
  return db.User.me.load().then(recipient => {

    email.fromName = 'John Doe'; // change the senders name
    email.subject = 'Forgotten Password'; // change the email subject
    email.body = `${recipient.username} Follow the link to reset your password: ${email.link} – Yours, John`;

    return email;
  });
}

Login and Logout

When a user is already registered, he can login with the DB.User.login() method.

DB.User.login('john.doe@example.com', 'MySecretPassword').then(function() {
  //Hey we are logged in again
  console.log(DB.User.me.username); //'john.doe@example.com'  
});

After the successful login a session will be established and all further requests to Baqend are authenticated with the currently logged-in user. You can also pass login options as a third parameter if you want the user to be logged in during the current session.

Sessions in Baqend are stateless, that means there is no state attached to a session on the server side. When a session is started a session token with a specified lifetime is created to identify the user. This session is refreshed as long a the user is active. If this lifetime is exceeded, the session is closed automatically. A logout simply locally deletes the session token and removes the current DB.User.me object.

DB.User.logout().then(function() {
  //We are logged out again
  console.log(DB.User.me); //null
});
Note: There is no need to close the session on the server side or handle any session state like in a PHP application for example.
Tip: The maximum session lifetime is determined by the so called session longlife (default: 30 days). After this time the session expired and the user has to explicitly log in again. You can set the longlife in the settings of your Baqend dashboard.

Forgot Password

If your user forgot his password, you will want him to reset it. Therefore, you can let Baqend send him an email which includes a “reset password” link, by calling:

// Send a “reset password” email
DB.User.resetPassword('Username').then(() => {
  //User received an email
});

In your app settings, you can configure the email template and the reset-password URL the user is navigated to when he clicks the link in that email.

Screenshot of reset E-Mail password

On your reset-password site, you can then set the new password in Baqend by calling:

const paramName = 'bq-token='; //Default token parameter 
const search = location.search;
const token = search.substring(search.indexOf(paramName) + paramName.length);
DB.User.newPassword(token, 'NewPassword').then(() => {
 //User is now logged in
});

You can also pass login options as a third parameter if you don't want the user to be logged in after setting his password.

New Passwords

Password can be changed by giving the old password and specifying the new one. Admin users can change the passwords of all users without giving the previous one:

//Using the user name
DB.User.newPassword('Username', 'oldPassword', 'newPassword').then(() => {
    //New Password is set
});

//Using a user object
DB.User.me.newPassword('oldPassword', 'newPassword').then(...);

//When logged in as an admin
DB.User.newPassword('Username', '', 'newPassword').then(...);

Change Username (email)

If email address validation is enabled, the user can also change his or her user name (email address). For security reasons, the password is also requested so that only authorized users can perform the action.

// Using the user name
DB.User.changeUsername('CurrentUsername@example.com', 'NewUsername@example.com', 'Password').then(() => {
    // A email validation request is send to NewUsername@example.com
});

// Using a user object
DB.User.me.changeUsername('NewUsername@example.com', 'Passowrd').then(...);

The template for the validation email can be customized in the dashboard under Settings. The section becomes visible as soon as email address validation is activated. Furthermore, it is possible to define redirect URLs for successes and failures.

Automatic Login

During initialization the Baqend SDK checks, if the user is already registered and has been logged in before in this session and has not logged out explicitly. As a consequence, returning users are automatically logged in and the DB.User.me object is set. New user are anonymous by default and no user object is associated with the DB.

DB.ready(function() {
  if (DB.User.me) {
    //do additional things if user is logged in
    console.log('Hello ' + DB.User.me.username); //the username of the user
  } else {
    //do additional things if user is not logged in
    console.log('Hello Anonymous');
  }
});

Loading Users

User objects are private by default, i.e. only admins and the user itself can load or update the object. This behaviour is intended to protect sensitive user information. There are two ways to grant access to user objects:

  • The first (not recommended) way is to grant access to specific users or groups or even to make the user objects publicly accessible. Because user objects are protected by object-level ACLs you need to have a look at Baqend's permission system to change the permissions.
  • The second (recommended) way is to divide your user information into two categories public and private. Then store the private information in the private user object and the public information in a separate profile object that is publicly accessible and linked to the user object.

Roles

The Role class is also a predefined class which has a name and a users collection. The users collection contains all the members of a role. A user has a specified role if he is included in the roles users list.

//create a new role
var role = new DB.Role({name: 'My First Group'});
//add current user as a member of the role
role.addUser(DB.User.me);
//allow the user to modify the role memberships
//this overwrites the default where everyone has write access
role.acl.allowWriteAccess(DB.User.me);
role.save().then(...);

A role can be read and written by everyone by default. To protect the role so that no one else can add himself to the role we restrict write access to the current user. For more information about setting permissions see the setting object permissions chapter.

Predefined Roles

There are three predefined roles:

  • admin - Users belonging to this role (e.g. the root) have full access to everything
  • loggedin - Every user who is logged in, automatically has this role. The role can be used to require a user to have a logged-in account to perform certain actions.
  • node - When an operation is triggered by a handler or module, the roles of the user who triggered that request are enhanced by the node role.

Predefined roles can be used just like normal roles. Typical use-case are that you define schema-level permissions to elevate rights of operations triggered by handlers and modules, allow certain things to logged-in users or restrict access to admins.

Note: The node role does not have any special privileges by default, but you can use it in ACLs to give it special rights.

Permissions

There are two types of permissions: class-based and object-based. The class-based permissions can be set by privileged users on the Baqend dashboard or by manipulating the class metadata. The object-based permissions can be set by users which have write-access to an object. As shown in the image below the class-level permissions are checked first. If the requesting user has the right permission on class level, the object-level permissions are checked. Only if the requesting user also has the right permissions on object level, he is granted acces to the entity.

ACLs and where they work

Each permission consists of one allow and one deny list. In the allow list user and roles can be white listed and in the deny list they can be black listed.

The access will be granted based on the following rules:

  • If the user has the admin role, access is always granted and the following rules will be skipped
  • Otherwise:
  • If the user or one of its roles are listed in the deny list, access is always denied
  • If no rules are defined in the allow list, public access is granted
  • If rules are defined the user or one of its roles has to be listed in the allow list in order to get access

The following table shows the SDK methods and the related permissions the user has to have, to perform the specific operation.

Method Class-based permission Object-based permission
.load() type.loadPermission object.acl.read
.find() type.queryPermission object.acl.read
.insert() type.insertPermission -
.update() type.updatePermission object.acl.write
.delete() type.deletePermission object.acl.write
.save() type.insertPermission if the object is inserted
type.updatePermission if the object is updated
object.acl.write
.save({force: true}) both type.insertPermission and type.updatePermission will be checked object.acl.write
Note: There is currently no way to check if a user has permissions to perform an operation without actually performing the operation.

Anonymous Users & Public Access

Anonymous users only have permissions to serve public resources. A resource is publicly accessible, if no class or object permission restricts the access to specific users or roles. To check if the object's permissions allow public access you can check the acl.isPublicReadAllowed() and the todo.acl.isPublicWriteAllowed() methods.

todo.acl.isPublicReadAllowed() //will return true by default
todo.acl.isPublicWriteAllowed() //will return true by default

Note: The access can still be restricted to specific roles or users by class-based permissions even if acl.isPublicReadAllowed() or todo.acl.isPublicWriteAllowed() returns true.

Setting Object Permissions

The object permissions are split up in read and write permissions. When inserting a new object, by default read and write access is granted to everyone. You can manipulate object permissions only if you have write permissions on the object. If you want to restrict write access to the current user but want to share an object within a group, you can add the role to the read permissions and the current user to the write permissions.

DB.Role.find().equal('name', 'My First Role').singleResult(function(role) {
  var todo = new DB.Todo({name: 'My first Todo'});
  todo.acl.allowReadAccess(role)
    .allowWriteAccess(DB.User.me);

  return todo.save();
}).then(...);

OAuth login

Another way to login or register is via a 'Sign in with' - 'Google' or 'Facebook' button. In general any OAuth provider can be used to authenticate and authorise a user. As of now, Baqend supports the main five providers.

Setup

To set them up, follow these steps:

  • Register your applications on the provider's website. The table below links to the provider websites and documentation.
  • Keep the client ID and a client secret generated by the provider for later.
  • Take the link from the table below (according to your provider) and set it as the redirect URL on the provider's website.
  • Lastly go to the settings in your Baqend dashboard and paste in the client ID and client secret for the provider in the OAuth section.

Supported Providers

Provider Setup Notes
google docs Add as redirect URL:
https://<appName>.app.baqend.com/v1/db/User/OAuth/google
facebook docs To set up Facebook-OAuth open the settings page of your Facebook app, switch to Advanced, activate Web OAuth Login and add
https://<appName>.app.baqend.com/v1/db/User/OAuth/facebook
as Valid OAuth redirect URI.
github docs Add as redirect URL:
https://<appName>.app.baqend.com/v1/db/User/OAuth/github
twitter docs Add as redirect URL:
https://<appName>.app.baqend.com/v1/db/User/OAuth/twitter Twitter does not support E-Mail scope. In default case a uuid is set as username.
linkedin docs Add as redirect URL:
https://<appName>.app.baqend.com/v1/db/User/OAuth/linkedin

Login & Registration

In order to use an OAuth provider to register or login users, you call one of the following SDK methods, depending on the provider:

DB.User.loginWithGoogle(clientID, options).then((user) => {
    //logged in successfully
    DB.User.me == user;
});
// Same for
DB.User.loginWithFacebook(...)
DB.User.loginWithGitHub(...)
DB.User.loginWithTwitter(...)
DB.User.loginWithLinkedIn(...)

The login call returns a promise and opens a new window showing the provider-specific login page. The promise is resolved with the logged in user, once the login in the new window is completed. The OAuth login does not distinguish between registration and login, so you don't have to worry about whether a user is already registered or not.

In the options passed to the login you can configure the OAuth scope among others. The scope defines what data is shared by the OAuth provider. On registration the username is set to the email address if it's in the allowed scope. Otherwise a uuid is used.

Note: For the login to work despite popup blockers the call needs to be made on response to a user interaction, e.g. after a click on the sign-in button. Also, an OAuth login will be aborted after 5 minutes of inactivity. The timeout can be changed with the timeout option.

Customize Login & Registration

To customize the login and registration behavior you can simply create a Baqend module named oauth.[PROVIDER], which is called after the user is logged in (or registered). In this module you can access the logged in user and a data object containing the OAuth token as well as the user information shared by the OAuth provider. The token can be used to directly do further API calls or save the token for later use.

As an example, if you like to edit the OAuth login for google, create the Baqend module oauth.google. The module will be called after the user is successfully authorized:

exports.call = function(db, data, req) {
    db.User.me // the unresolved user object of the created or logged in user

    // data contains the profile data send by the OAuth provider
    data.id // The OAuth unique user id
    data.access_token // The OAuth users API token
    data.email // The users email if the required scope was requested by the client
};

The following table lists more information on what data can be shared by the OAuth providers:



Provider Profile documentation
google Just returns the email per default. Visit OAuth 2.0 Scopes for Google APIs for a complete list of supported scopes.
facebook Returns the content of the https://graph.facebook.com/v2.4/me resource
github Returns the authenticated user profile
twitter Just returns the access_token. An Email address can't be queried with the twitter API.
linkedin Returns the content of the https://api.linkedin.com/v1/people/~?format=json resource.

Note: The returned properties depend on the requested scope.

OAuth Login via Redirect

In some cases, it may be desirable to use the OAuth authorization without opening a new window, e.g. when cross-window communication is unavailable because of a missing localStorage object.

To use the login via redirect, you need to set a redirect parameter when calling the particular login method. In this case, the SDK does not return the user object, but creates a unique token and redirects to the specified redirect URL. Your site will be closed and the provider login will open instead.

//Set redirect parameter in loginOption
loginOption = {'redirect': 'http://.../yourRedirectPage'};

//call SDK method with loginOption
DB.User.loginWithGoogle(clientID, loginOption).then(function(user) {
    ...
});

After communicating with the OAuth provider, the unique token is sent as a query parameter to the specified redirect page. In case of a failure, the particular error message is sent instead. The following table lists more information of all possible query parameters:

Parameter Meaning
token A unique token to identify the user object (in case of success)
loginOption The specified login options (in case of success)
errorMessage A url-encoded error message (in case of failure)

In case of success, you can call the following SDK method with the unique token and the specified login options as parameters to login the user.

DB.User.loginWithToken(token, options).then((user) {
    //logged in successfully
    DB.User.me == user;
});

The login call returns a promise which is resolved with the logged in user. The OAuth login does not distinguish between registration and login, so you don't have to worry about whether a user is already registered or not.

Note: For the login via redirect to work, ensure to register all valid redirect URLs (e.g. 'http://.../yourRedirectPage') in the "Authorized Domains" section of your dashboard settings.

Proceed to next Chapter: Baqend Code