We now have in place a way for users to signup for our service and begin paying. Now we need a way to authenticate them to give them access to modifying their billing details and access restricted content or features. In this example we use a Node.js application.

1. Configure Password Reset URL

Go to 'Embeds' -> 'Login Page',  the Password Reset URL should be <your_app_url>/login which corresponds to a route you will create later in this tutorial.

2. Dependencies

To get started, install the following dependencies.

You can view the code used in this example here

npm install passport passport-jwt express-session --save

3. Setup express-session

In your app.js, use express-session. The secret  is a string that is used to sign the session cookie.

// in app.js

const session = require('express-session');

// configure session object
let sessionObject = {
  secret: 'THIS SHOULD BE CHANGED',
  cookie: {},
  resave: false,
  saveUninitialized: true
};

// use secure cookies, needs HTTPS
if (app.get('env') === 'production') {
  sessionObject.cookie.secure = true;
}

app.use(session(sessionObject));

4. Get your Secret Key

To validate the authentication tokens coming from Servicebot, you will need to get the HMAC secret key. Go to Servicebot->Embeds->Login and select "Other" to see the HMAC secret. 

5. Configure Passport

In app.js, use the passport and passport-jwt modules, and configure passport to use a JWTStrategy with your secret key. Use passport.initialize() and passport.session() to initialize passport with persistent login sessions. 

// in app.js
// load all the things we need
let passport = require('passport');
const JwtStrategy   = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;

//configure JWT strategy
const strategy = new JwtStrategy(
    {
        //Secret key stored in process.env
        secretOrKey : process.env.SERVICEBOT_SECRET,
        jwtFromRequest: ExtractJwt.fromBodyField("token")
    },
    function(payload, done) {
        // payload.uid is the Servicebot User ID for the user
        // payload.user contains the Servicebot user object
        done(null, payload)
    }
)

passport.use("servicebot-login", strategy);

app.use(passport.initialize());
app.use(passport.session());

The last two commands should be called after you've used the session middleware (app.use(session(sess)).

6. Configure user data serialization for the session

Passport serializes and deserializes user instances to and from the session in order to support login sessions. These functions can be used to link Servicebot users to users in your own database if you want to use your own storage for user data.

// in app.js
passport.serializeUser(function (user, done) {
  done(null, user);
});

passport.deserializeUser(function (user, done) {
  done(null, user);
});

7. Implement Login/Logout Routes

In this example, following routes are implemented:

  • GET /login displays the login page
  • POST /login authenticates the user by calling Passport's authenticate method
  • GET /logout  closes the local user session and redirects the user again to the root index /.
  • GET /profile  displays the user's profile.

8. Add an authentication router

The POST /login  route uses the passport strategy we created in the previous step to log the user in, creating a session cookie which will signify that the user is logged in. The /logout  route clears the session cookie and redirects the user back to home. 

//in routes/auth.js

var express = require('express');
var router = express.Router();
var passport = require('passport');

router.post('/login', passport.authenticate('servicebot-login'),
    function(req, res){
        res.status(200);
    }
);
router.get('/logout', function (req, res) {
    req.logout();
    res.redirect('/');
});

9. Adding the page rendering routes

Next we should need to add the routes which will render the different pages of our app. the isLoggedIn  middleware will protect the /profile  route so any logged out user gets redirected. 

// routes/render.js

var express = require('express');
var router = express.Router();
var passport = require('passport');

//middleware to check if user is logged in, otherwise redirect
function isLoggedIn(req, res, next) {

    // if user is authenticated in the session, carry on
    if (req.isAuthenticated())
        return next();

    // if they aren't redirect them to the home page
    res.redirect('/');
}

/* GET home page. */
router.get('/', function (req, res, next) {

  //get user out of session if logged in
  res.render('index', { user: req.user });
});

router.get('/signup', function (req, res) {

    // render the page
    res.render('signup');
});

router.get('/login', function (req, res) {

    // render the page
    res.render('login');
});

//secure route with isLoggedIn middleware
router.get('/profile', isLoggedIn, function (req, res) {
    res.render('profile', {
        // get the user out of session and pass to template
        user: req.user
    });
});      

module.exports = router;

10. Add the routes to the app

Use the new routes in your app.js  in order for them to be available for consumption.

// in app.js

app.use("/", require("./routes/auth"));
app.use("/", require("./routes/render"));

11. Create the Login Page

  1. In Servicebot, go to 'Embeds'->'Login Page'
  2. Click 'Copy Client Code'
  3. Create a file in your frontend to hold your pricing page. (From code example: views/account/login.pug)
  4. Paste in the code to the file and configure for your needs

The options of the Login embed code are:

  • url: The Servicebot instance URL. This should be automatically assigned from the embed code. It is the URL of the Servicebot instance. We will assign it to a global variable so we can use it in other places.
  • handleResponse: Function called after a user logs in. Response is a JSON containing the user object, token, owned service instances, and seats following the form {seats, instances, user, token}. It is also called after complete registration and after password reset completes:
{
   "oauthResponse":{

   },
   "token":"eyJhbGciO....",
   "user":{
      "id":184,
      "role_id":3,
      "name":null,
      "email":"ExampleUser@example.com",
      "provider":"local",
      "status":"active",
      "customer_id":"cus_ENqxcZcqzWicOe",
      "phone":null,
      "last_login":null,
      "created_at":"2019-01-21T16:03:41.205Z",
      "updated_at":"2019-01-21T16:03:41.205Z",
      "google_user_id":null,
      "google_refresh_token":null,
      "references":{
         "user_roles":[
            {
               "id":3,
               "role_name":"user",
               "created_at":"2018-07-09T22:03:13.704Z",
               "updated_at":"2018-07-09T22:03:13.704Z"
            }
         ],
         "service_instances":[
            {
               "id":112,
               "service_id":33,
               "user_id":184,
               "requested_by":184,
               "payment_plan":{
                  "id":"SaaS-with-Auth-ID112-tzfj",
                  "name":"SaaS with Auth",
                  "tiers":null,
                  "active":true,
                  "amount":6000,
                  "object":"plan",
                  "created":1548086621,
                  "product":"prod_ENqxsO3EUKorLD",
                  "currency":"usd",
                  "interval":"year",
                  "livemode":false,
                  "metadata":{

                  },
                  "nickname":null,
                  "tiers_mode":null,
                  "usage_type":"licensed",
                  "billing_scheme":"per_unit",
                  "interval_count":1,
                  "aggregate_usage":null,
                  "transform_usage":null,
                  "trial_period_days":14,
                  "statement_descriptor":"Subscription"
               },
               "name":"SaaS with Auth",
               "description":null,
               "subscription_id":"sub_ENqxy1RfF3sWcC",
               "subscribed_at":1548086621,
               "trial_end":1549296221,
               "status":"running",
               "type":"subscription",
               "split_configuration":null,
               "created_at":"2019-01-21T16:03:41.239Z",
               "updated_at":"2019-01-21T16:03:42.071Z",
               "payment_structure_template_id":123
            }
         ],
         "service_instance_seats":[],
         "funds":[
            {
               "id":54,
               "user_id":184,
               "flagged":false,
               "source":null,
               "created_at":"2019-01-21T16:03:58.047Z",
               "updated_at":"2019-01-21T16:03:58.047Z"
            }
         ]
      }
   },
   "instances":[
      {
         "id":112,
         "service_id":33,
         "user_id":184,
         "requested_by":184,
         "payment_plan":{
            "id":"SaaS-with-Auth-ID112-tzfj",
            "name":"SaaS with Auth",
            "tiers":null,
            "active":true,
            "amount":6000,
            "object":"plan",
            "created":1548086621,
            "product":"prod_ENqxsO3EUKorLD",
            "currency":"usd",
            "interval":"year",
            "livemode":false,
            "metadata":{

            },
            "nickname":null,
            "tiers_mode":null,
            "usage_type":"licensed",
            "billing_scheme":"per_unit",
            "interval_count":1,
            "aggregate_usage":null,
            "transform_usage":null,
            "trial_period_days":14,
            "statement_descriptor":"Subscription"
         },
         "name":"SaaS with Auth",
         "description":null,
         "subscription_id":"sub_ENqxy1RfF3sWcC",
         "subscribed_at":1548086621,
         "trial_end":1549296221,
         "status":"running",
         "type":"subscription",
         "split_configuration":null,
         "created_at":"2019-01-21T16:03:41.239Z",
         "updated_at":"2019-01-21T16:03:42.071Z",
         "payment_structure_template_id":123,
         "references":{
            "service_templates":[
               {
                  "id":33,
                  "category_id":null,
                  "created_by":1,
                  "name":"SaaS with Auth",
                  "published":true,
                  "created_at":"2018-10-26T02:08:56.897Z",
                  "updated_at":"2018-11-18T20:21:13.902Z"
               }
            ],
            "service_instance_seats":[],
            "service_instance_properties":[
               {
                  "id":137,
                  "name":"__usage",
                  "type":"metric",
                  "data":{
                     "value":0
                  },
                  "config":{
                     "unit":"User",
                     "value":{
                        "name":"User"
                     },
                     "isSeat":true,
                     "pricing":{
                        "tiers":[
                           "Basic",
                           "Plus"
                        ],
                        "operation":"multiply"
                     }
                  },
                  "prop_class":null,
                  "prop_label":"__usage",
                  "prop_description":null,
                  "created_at":"2018-10-26T02:08:56.917Z",
                  "updated_at":"2018-10-26T02:08:56.917Z",
                  "parent_id":112,
                  "private":false,
                  "prompt_user":true,
                  "required":false
               }
            ],
            "service_instance_messages":[],
            "charge_items":[],
            "service_instance_cancellations":[],
            "payment_structure_templates":[
               {
                  "id":123,
                  "tier_id":81,
                  "trial_period_days":14,
                  "amount":6000,
                  "type":"subscription",
                  "currency":"usd",
                  "interval":"year",
                  "interval_count":1,
                  "subscription_prorate":true,
                  "statement_descriptor":null,
                  "split_configuration":null,
                  "created_at":"2018-11-06T19:16:16.471Z",
                  "updated_at":"2018-11-06T19:16:16.471Z"
               }
            ],
            "users":[
               {
                  "id":184,
                  "role_id":3,
                  "name":null,
                  "email":"kevtest10@yopmail.com",
                  "provider":"local",
                  "status":"active",
                  "customer_id":"cus_ENqxcZcqzWicOe",
                  "phone":null,
                  "last_login":null,
                  "created_at":"2019-01-21T16:03:41.205Z",
                  "updated_at":"2019-01-21T16:03:41.205Z",
                  "google_user_id":null,
                  "google_refresh_token":null
               }
            ]
         }
      }
   ],
   "service_instance_seats":[]
}

  • uid: The userId of a user to reset password for. When paired with a resetToken,  the embed presents the user with a page to reset their password. Also can be passed as a url variable like https://example.com/reset?resetToken=xxxxx&uid=12.
  • resetToken: The resetToken generated by user using the forgot password functionality. When paired with a uid, the login form will display a password reset form. Also can be passed as a url variable like https://example.com/reset?resetToken=xxxxx&uid=12
  • inviteToken: The inviteToken generated from the seat management form. If present, the embed will present the user with an account registration page. Also can be passed as a url variable like https://example.com/register?inviteToken=xxxxx
  • googleScope: The scopes requested of the client when using google authentication, should be a list of scopes separated by spaces. Defaults to "profile email", you can find a list of scopes here
  • accessType: If set to "offline", a refresh_token will be generated and attached to the user object instead of the traditional access_token. The oauthResponse attribute will contain a useless code that was consumed by the server to generate the refresh_token. offline access also disables traditional username/password authentication.

Example Embed Code:

Servicebot.Login({        
  url : "https://example.serviceshop.io",        
  selector : document.getElementById('servicebot-login-form'),
  handleResponse : (response) => {          
    console.log(response.user) //user object
    console.log(response.token) //JSON Web Token to be validated from your app, also contains user object encoded  
    console.log(response.seats) //seats this user belongs to
    console.log(response.instances) //service instances this user owns
    console.log(response.oauthResponse) //the response from an oauthProvider used to login such as Google        
  }    
})

Tips:

  • If you pass inviteToken or resetToken and uid as URL variables, they will be pulled automatically into the embed, the invite email and reset password email should have these url variables automatically attached to the URL so you just need to make sure the URLs your configure are pointed to a page with the login embed
  • You can decode the JSON web token to get the user object which will include references to service instances, seats, and funds
Did this answer your question?