KEMBAR78
StrongLoop Node.js API Security & Customization | PDF
API SECURITY, CUSTOMIZATION,
AND MOBILE BACKENDS
Jordan Kasper | Developer Evangelist
Open source REST API framework based on Express.js
LOOPBACK FEATURES
Model-driven API development
Dynamic REST API endpoint generation
Connect to any datasource (SQL, NoSQL, REST, SOAP)
Rich model relations
Access controls (built in token authentication)
Geolocation, push notifications, offline sync
Angular, Android, and iOS SDKs
STEP 1: INSTALL STRONGLOOP TOOLS
~$ npm install ­g strongloop
And scaffold your application:
~$ slc loopback
WORKING WITH DATA MODELS
CREATING A MODEL VIA CLI
~/my­app$ slc loopback:model
[?] Enter the model name: CoffeeShop
[?] Select the data­source to attach CoffeeShop to: (Use arrow keys)
❯ mdb (mongodb)
[?] Select model's base class: (Use arrow keys)
  Model
❯ PersistedModel
  ACL
[?] Expose CoffeeShop via the REST API? (Y/n) Y
[?] Custom plural form (used to build REST URL):
CREATING A MODEL VIA CLI
[?] Property name: name
   invoke   loopback:property
[?] Property type: (Use arrow keys)
❯ string
  number
  boolean
  object
  array
  date
  ...
[?] Required? (y/N)
When complete, simply hit "enter" with a blank property
MODEL CONFIG
{
  "name": "CoffeeShop",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string",
      "required": true
    }
  },
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": []
}
RUN THE APPLICATION
~/my­app$ node server/server.js
Web server's listening at http://localhost:3000
Browse your REST API at http://localhost:3000/explorer
COFFEESHOP ROUTES
http://localhost:3000/api/CoffeeShops
[]
We don't have any coffee shops yet,
so we get an empty array!
http://localhost:3000/explorer
RELATIONSHIP MODELING
VARIOUS RELATIONSHIP TYPES
HasMany
HasManyThrough
HasAndBelongsToMany
Polymorphic
Embedded (embedsOne and embedsMany)
We also have a special "ownership" relation:
"BelongsTo"
RELATIONSHIP DEFINITION
// common/models/coffee­shop.json
{
  "name": "CoffeeShop",
  // ...,
  "relations": {
    "reviews": {
      "type": "hasMany",
      "model": "Review"
    }
  }
}
BELONGSTO RELATIONSHIPS (OWNERSHIP)
// common/models/review.json
{
  "name": "Review",
  // ...,
  "relations": {
    "reviewer": {
      "type": "belongsTo",
      "model": "User"
    }
  }
}
AUTHENTICATION AND
AUTHORIZATION
LOOPBACK AUTH MODELS
User
Principal
Role
RoleMapping
ACL
PRINCIPALS
An entity that can be identified or authenticated
A user
A role
An application
ROLES AND MAPPINGS
A Role is a group of principals with the same permissions.
RoleMappings are used to map principals onto Roles.
DYNAMIC ROLES
Some dynamic roles already exist for you:
$everyone (regardless of authorization status)
$unauthenticated
$authenticated
$owner (using a belongsTo relation)
CREATING NEW ROLES
// server/boot/roles.js
module.exports = function(app) {
    app.models.Role.create({
        name: 'admin'
    }, function(err, theAdminRole) {
        if (err) { cb(err); }
        // Maybe add some users to it?
    });
};
ADDING USERS TO A CUSTOM ROLE
theAdminRole.principals.create({
    principalType: app.models.RoleMapping.USER,
    principalId: someUser.id
}, function(err, principal) {
    // handle the error!
    cb(err);
});
CUSTOM DYNAMIC ROLES
Use Role.registerResolver() to set up a custom role handler:
// server/boot/roles.js
Role.registerResolver('admin', function(role, context, cb) {
    // custom method you create to determine this...
    determineAdminStatus(function(err, isAuthorized) {
        if (err) {
            // maybe handle the error here?
            return cb(err);
        }
        // execute callback with a boolean
        cb(null, isAuthorized);
    });
});
ACCESS CONTROL LAYERS
A layer defines what access a principal has for a certain
operation against a specific model.
WHITELISTING
For example, we might create these layers:
Deny everyone to access the model
Allow '$everyone' role to read instances
Allow '$authenticated' role to create instances
Allow '$owner' to update an existing instance
This is an example of "whitelisting", and is safer than
denying specific operations.
DEFINING AN ACL
We use the slc loopback:acl subcommand:
~/my­app$ slc loopback:acl
DEFINING AN ACL
The CLI will ask you some questions...
~/my­app$ slc loopback:acl
? Select the model to apply the ACL entry to: CoffeeShop
? Select the ACL scope: All methods and properties
? Select the access type: All (match all types)
? Select the role: All users
? Select the permission to apply: Explicitly deny access
DEFINING AN ACL
Now allow everyone to read CoffeeShops:
~/my­app$ slc loopback:acl
? Select the model to apply the ACL entry to: CoffeeShop
? Select the ACL scope: All methods and properties
? Select the access type: Read
? Select the role: All users
? Select the permission to apply: Explicitly grant access
DEFINING AN ACL
Here's what this looks like in the config file:
// in common/models/coffee­shop.json
"acls": [
  {
    "accessType": "*",
    "principalType": "ROLE",
    "principalId": "$everyone",
    "permission": "DENY"
  },
  {
    "accessType": "READ",
    "principalType": "ROLE",
    "principalId": "$everyone",
    "permission": "ALLOW"
  }
]
DEFINING ACLS
Access Control Layers execute in order, so be sure to DENY
first, then add all of your "whitelisting".
USING OWNERS
By creating a belongsTo relation, we can use $owner:
"acls": [
  // ...,
  {
    "accessType": "WRITE",
    "principalType": "ROLE",
    "principalId": "$owner",
    "permission": "ALLOW"
  }
]
RESTRICTING SPECIFIC METHODS
We can ALLOW or DENY access to specific remote methods:
"acls": [
  // ...,
  {
    "accessType": "EXECUTE",
    "principalType": "ROLE",
    "principalId": "admin",
    "permission": "ALLOW",
    "property": "create"
  }
]
ADVANCING YOUR API
REMOTE METHODS
A way to create new, non-CRUD methods on your model.
REMOTE METHODS
First, define the handler function...
// common/models/coffee­shop.js
module.exports = function(CoffeeShop){
    CoffeeShop.status = function(id, cb) {
        CoffeeShop.findById(id, function(err, shop) {
            if (err) { return cb(err); }
            cb( null, determineStatus() );
        });
    };
    // ...
};
REMOTE METHODS
Then define the API spec...
// common/models/coffee­shop.js
module.exports = function(CoffeeShop){
    CoffeeShop.status = function(id, cb) { /* ... */ }
    CoffeeShop.remoteMethod(
        'status',
        {
            accepts: [ {
                arg: 'id',
                type: 'number',
                required: true,
                http: { source: 'path' }
            } ],
            returns: { arg: 'isOpen', type: 'boolean' },
            http: [ {verb: 'get', path: '/:id/status'} ]
        }
    );
};
ACCESSING REMOTE METHODS
A GET request to /api/CoffeeShops/1/status might return:
{ isOpen: true }
RESTRICTING REMOTE METHODS
Remember, we can ALLOW or DENY access to specific
remote methods on a model, including custom ones!
"acls": [
  // ...,
  {
    "accessType": "EXECUTE",
    "principalType": "ROLE",
    "principalId": "$unauthenticated",
    "permission": "DENY",
    "property": "status"
  }
]
GETTING THE CURRENT USER
At any point in your application you can get the current
user access token, and the user ID:
// common/models/coffee­shop.js
var loopback = require('loopback');
module.exports = function(CoffeeShop) {
    CoffeeShop.status = function(id, cb) {
        var context = loopback.getCurrentContext();
        var token = context.get('accessToken');
        console.log( 'Access Token ID', token.id );
        console.log( 'Current User ID', token.userId );
    };
};
CONNECTING TO THE FRONT END
LoopBack has client SDKs for Angular, iOS, Android, and
Xamarin!
STEP ONE
The first step is to generate the client code.
Here we use the Angular generator:
~/my­app$ mkdir client/js/services
~/my­app$ lb­ng server/server.js client/js/services/lb­services.js
ANGULAR MODELS
LoopBack provides models for you to use in Angular which
have all of the remote methods (create, find, destroy, etc).
For User objects this includes the login() method!
ANGULAR MODELS
When you create your application modules, just include the
LoopBack Services we created from the CLI:
angular.module('my­app', ['ui.router', 'lbServices'])
    .config( ... )
    .run( ... );
CREATING AN AUTH SERVICE
Now we can create an Angular service for authentication:
angular.module('my­app').factory(
    'AuthService',
    ['User', '$q', '$rootScope', function(User, $q, $rootScope) {
        function login(email, password) {
            return User
                .login({ email: email, password: password })
                .$promise
                .then(function(response) {
                    $rootScope.currentUser = {
                        id: response.user.id,
                        tokenId: response.id
                    };
                });
        }
        return {
            login: login
        };
    }]
);
CREATING AN AUTH SERVICE
The logout() method is easy!
function logout() {
    return User
        .logout()
        .$promise
        .then(function() {
            $rootScope.currentUser = null;
        });
    };
CREATING AN AUTH SERVICE
When you use the User.login() method (and it is successful),
LoopBack will send an Authorization header with each
request containing the accessToken!
USING 3RD PARTY AUTH
Integrating with 3rd party authentication services is easy!
Just use the .passport LoopBack component
3RD PARTY AUTH
The basic workflow is:
1. Visitor clicks "login with X" button (i.e. Facebook (FB))
2. LoopBack (LB) redirects to FB login page
3. FB redirects to your LB server
4. LB requests access token from FB
5. LB uses token to get user info
6. LB matches info to internal User model
(LB creates the user locally if it doesn't exist)
7. User info is added to the LB context
3RD PARTY AUTH
First, install the passport component:
~/my­app$ npm install ­­save loopback­component­passport
Then set up a PassportConfigurator...
(This is the piece that connects LoopBack to Passport.)
CONFIGURING PASSPORT
We need to configure Passport, a boot script works:
// server/boot/setup­auth.js
var loopback = require('loopback'),
    PPConfigurator = require('loopback­component­passport').PassportConfigu
module.exports = function(app) {
    // Enable http sessions
    app.use(loopback.session({ secret: 'super­secret­string' }));
    var configurator = new PPConfigurator(app);
    configurator.init();
    configurator.setupModels({
        userModel: app.models.user,
        userIdentityModel: app.models.userIdentity,
        userCredentialModel: app.models.userCredential
    });
    configurator.configureProvider('facebook­login', {
        // ...
    });
};
CONFIGURING PASSPORT
And here is our Facebook-specific config...
// server/boot/setup­auth.js
...
module.exports = function(app) {
    // ...
    configurator.configureProvider('facebook­login', {
        provider: 'facebook',
        module: 'passport­facebook',
        clientID: '{your­FB­client­id}',
        clientSecret: '{your­FB­client­secret}',
        callbackURL: 'http://localhost:3000/auth/facebook/callback',
        authPath: '/auth/facebook',
        callbackPath: '/auth/facebook/callback',
        successRedirect: '/auth/account',
        scope: ['email']
    });
};
ADDITIONAL PASSPORT STEPS
Fill in your FB/Google/etc app details
Create a UI button linking to /auth/facebook
Set up a page at /auth/account (or wherever successRedirect
points)
Add other auth providers!
See an example app here:
https://github.com/strongloop/loopback-example-passport
BEING AN OAUTH PROVIDER
Want to be your own OAuth provider?
(instead of using Facebook, Google, etc)
BEING AN OAUTH PROVIDER
You can use StrongLoop's !oauth2 LoopBack component
~/my­app$ npm install ­­save loopback­coponent­oath2
* Note that this component requires a license.
CONFIGURE OAUTH
// server/boot/oauth.js
var oauth2 = require('loopback­component­oauth2');
module.exports = function(app) {
    oauth2.oAuth2Provider( app, {
        dataSource': app.dataSources.mydb,
        authorizePath': '/oauth/authorize',
        loginPage': '/login',
        loginPath': '/login'
    });
};
Full options can be found on docs.strongloop.com
OAUTH LOGIN
Don't forget that you still need to code the login page!
And you will also need a callback URL for the authorization
action.
OAUTH LOCK DOWN
Now we configure the resource endpoints we're locking
down:
// server/middleware.json
"auth": {
  "loopback­component­oauth2#authenticate": {
    "paths": [ "/api" ],
    "params": {
      "session": false,
      "scopes": {
        "reviews": [ "/api/Reviews" ],
        "user": [
          {
            "methods": ["find", "findById", "destroy", "save"],
            "path": "/api/Users"
          }
        ]
      }
    }
  }
}
WHAT ABOUT RATE LIMITING AND PROXY?
Create more middleware at specific phases.
Capture the request and evaluate before passing it on.
Centralized in front of your resource server!
DEMO PROJECT
https://github.com/jakerella/lb-central
You can use this demo project as a starting point:
~$ git clone https://github.com/jakerella/lb­central.git
~$ cd lb­central && npm install
NOTE: This code is licensed by StrongLoop, you will need a license to use it!
RATE LIMITING YOUR API
Control how many requests a client can
make within a time period.
Simply add the middleware config!
CONFIGURE RATE LIMITING
"routes:after": {
  "./middleware/rate­limiting": {
    "params": {
      "interval": 60000,
      "keys": {
        "ip": 100,
        "url": {
            "template": "url­${urlPaths[0]}/${urlPaths[1]}",
            "limit": 500
        },
        "user": {
            "template": "user­${user.id}",
            "limit": 1500
        },
        "app,user": {
            "template": "app­${app.id}­user­${user.id}",
            "limit": 2500
        }
      }
    }
  }
REQUEST PROXIES
Once the the user is authenticated, rate limiting is
compelte, and any other centralized code, we can proxy
requests to their final destination.
PROXYING REQUESTS
Send requests from this server to the resource server(s):
// in middleware.json
"routes:after": {
  "./middleware/proxy": {
    "params": {
      "rules": [
        "^/api/foo/(.*)$ https://service­one.com:3007/api/$1 [P]",
        "^/api/bar/(.*)$ https://service­two.com:3001/api/v2/$1 [P]"
      ]
    }
  }
}
THANK YOU!
QUESTIONS?
Jordan Kasper | Developer Evangelist
Join us for more events!
strongloop.com/developers/events

StrongLoop Node.js API Security & Customization