Skip to content
Helmut Tammen edited this page Oct 17, 2016 · 1 revision

Authentication and Authorization

n-odata-server leverages the authentication and authorization mechanisms supplied by loopback. This wiki describes how to use it in your applications.

To mention upfront: If you want a detailed description of Authentication, Authorization and permissions you should have a look to the according loopback documentation.
In this document you find a brief introduction that helps you get started quickly.

Defining ACLs

First you have to define ACLs (Access Control Lists). These ACLs describe who is allowed to do what with your models. The ACLs are defined in the model.json file. The following example is defined in file common/models/business-trip.json of the n-odata-server example project.
The ACLs are defined with the array acls. The below definition denies access to this model (and all its entities) for everyone and allows access for all users that have been assigned to the r_businesstrips_access role.

{
  "name": "BusinessTrip",
  "plural": "BusinessTrips",
  "base": "PersistedModel",
  "options": {
    "validateUpsert": true
  },
  "persistUndefinedAsNull": true,
  "properties": {
    ...
  },
  "validations": [],
  "relations": {},
  "acls": [
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "r_businesstrips_access",
      "permission": "ALLOW"
    },
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "DENY"
    }
  ],
  "methods": {}
}

Create user and role

To be able to access the model objects you have to have a user, a role and assign the user to the role.
In the n-odata-server example project we create these artifacts in the file server/boot/initial_security.ts. All files that are located in the server/boot directory are automatically executed when the application boots. In your project you can of course add code for creating these artifacts wherever you want and write a nice UI for it. We decided to use a boot script for simplicity reasons here.

Here is the code we written to create the artifacts.

Create user

We first create a user if not already available. The user gets an email and a password. After the user has been created or read from the database the initRoleForUser function is called.

	public init() {
		// find user1 in db
		this.User.findOne({where: {email: '[email protected]'}}).then((user) => {
			if(!user) {
				// create user if not already exists
				this.User.create({username: 'user1', email: '[email protected]', password: 'secret'}).then((user) => {
					if(user) {
						logger.debug(`User created: ${JSON.stringify(user.toJSON())}`);
						this.initRoleForUser(user);
					} else {
						logger.error(`user 'user1' could not be created. Program may not work as expected`);
					}
				});
			} else {
				this.initRoleForUser(user);
			}
		}).catch((err) => {
			logger.error(`error: ${err}`);
		});
	};

Create role

The role r_businesstrips_access is either read from the database or created within the function initRoleForUser. After the role is available the function assignUserToRole is called.

	private initRoleForUser(user:any) {
		this.Role.findOne({where: {name: 'r_businesstrips_access'}, include: 'principals'}).then((role) => {
			if(!role) {
				this.Role.create({name: 'r_businesstrips_access', description: 'grants general access to businesstrips'}).then((role) => {
					if(role) {
						logger.debug(`Role created: ${JSON.stringify(role.toJSON())}`);
						this.assignUserToRole(user, role);
					} else {
						logger.error(`role 'r_businesstrips_access' could not be created. Program may not work as expected`);
					}
				})
			} else {
				this.assignUserToRole(user, role);
			}
		})
	}

Assign user to role

The assignment of a user to a role is done adding a principal to the roles principals hasMany association.

	private assignUserToRole(user, role) {
		// Promise (then) does not work here
		this.RoleMapping.findOne({where: {principalType: this.RoleMapping.USER, principalId: user.id, roleId: role.id}}).then((roleMapping) => {
			if(!roleMapping) {
				role.principals.create({
					principalType: this.RoleMapping.USER,
					principalId: user.id
				}).then((roleMapping) => {
					logger.debug(`Rolemapping created: ${JSON.stringify(roleMapping.toJSON())}`);
				})
			}
		})
	}

Test access

As soon as you have defined the ACLs and are sure that a user has been assigned to the appropriate role you can test your application.

Login user

First you have to login your user to get an access token.

curl -X POST --header "Content-Type: application/json" --header "Accept: application/json" -d "{ 
\"email\": \"[email protected]\",
\"password\": \"secret\"
} " "http://localhost:3000/api/Users/login"

You retrieve a result that looks like this.

{
  "id": "yE1OCPVTHmGERUwDFegJawCTPV24hr8OTUGgGbNEiZSRoS3rLjJPoZt3WOdCAd4o",
  "ttl": 1209600,
  "created": "2016-10-17T09:18:24.302Z",
  "userId": 5
}

The id is your access token that you must provide with each request to the businesstrip model

Access model data

Now as you know your access token you can access your model data.

curl -X GET -H "Authorization: yE1OCPVTHmGERUwDFegJawCTPV24hr8OTUGgGbNEiZSRoS3rLjJPoZt3WOdCAd4o" -H 
"Cache-Control: no-cache" -H "Postman-Token: 16662279-b82e-e369-8e3d-1aef109357da" 
"http://0.0.0.0:3000/odata/BusinessTrips"

You should get back something like this.

{
  "d": {
    "results": [
      {
        "id": "22",
        "Starttime": "/Date(1463724000000)/",
        "Endtime": "/Date(1463752800000)/",
        "Location": "Hamburg",
        "Customer": null,
        "Distance": 120.7,
        "Note": null,
        "Breakfast": false,
        "Lunch": false,
        "Dinner": false,
        "__metadata": {
          "uri": "http://0.0.0.0:3000/odata/BusinessTrips('22')",
          "type": "NODATASERVER.BusinessTrip"
        }
      },
      ...
      ...
      ...
    }
  }
}    

If you provide a wrong or no access token you get a 401 error that looks like this.

{
  "message": "Authorization Required",
  "stack": "Error: Authorization Required\n    
    at ...    
    at ...    
    at ...    
  "statusCode": 401,
  "code": "AUTHORIZATION_REQUIRED"
}