Visual Studio – Node Style

In the last six articles I’ve been doing a study of what it takes to do a node app. For those who are starting here, I had a list of requirements:

  1. I need to be able to run a small web server
  2. I need to be able to handle templated views with server-side code
  3. I need to be able to do social authentication
  4. I need to be able to use an MVC architecture
  5. I need to be able to provide a Web API
  6. I need to be able to publish a node app to Azure
  7. I need to be able to edit node applications in Visual Studio 2015

This last article is about Visual Studio. By the time you read this, Visual Studio 2015 Release Candidate will have been out about a month. If you have not tried it yet, then you need to do so. It does not come (out of the box) with Node support, but that can easily be added. Just take your browser to the Node.js Tools for Visual Studio site and download the v1.1 BETA Edition. Install it, restart Visual Studio and you are all set.

While you are there, make sure you check out some of the productivity tools – especially the web / javascript ones. They are pretty much all available under Tools -> Extensions and Updates.

Creating a new project

In the last six articles I’ve created a pretty cool app that does nothing more than authenticate you against any number of social authenticators – things like Twitter, Facebook, Google and Microsoft. I’m going to leave that behind (well, sort of) and create a new app from scratch using Visual Studio.

Step 1 – “New Project…”

blog-code-0527-1

Look at all those choices! It’s good to see a wide variety of template projects, including starter web applications and azure hosting options. I’m going to choose the blank web application with support for hosting in Azure. I’m going to adjust the location to store it in my node-stuff repository. I’m going to call the project “socialauth” to represent what it does. Since I’m not going to have multiple projects in this solution, I’m going to uncheck the “Create directory for solution” box.

Note to self: Don’t check the “Add to source control” when you already have source control set up.

At this point, the project is scaffolding out. Expanding all the folders, here is what you get:

blog-code-0527-2

Note the npm/global folder. That’s a virtual folder – it isn’t really there. If you run npm install -g <something> then it places it in a global place in your environment and Visual Studio detects that and shows them to you. This is kind of awesome.

Adding Packages

There were a bunch of packages that I used in developing the basic-webapp. So I’m going to edit the package.json to add back everything there. Don’t you just love Intellisense? The Intellisense has full knowledge of the package.json format and does help suggestions for keys and their values. For instance, add "license" to the JSON blob and it drops down a list of open source licenses to include.

When you are typing in dependencies, Visual Studio Intellisense will drop down the semver versions of the latest package. If the package is not in npm, then it will say “Unavailable” – a sure fire indicator that you have typed the name wrong.

When you are done, save the package.json and watch the package manager download all the packages for you. They appear under the “npm” node in the Solution Explorer, but in reality are under node_modules on the file system.

Like to have your package.json properties sorted? There is an option for that. Just click on the multi-talented light bulb:

blog-code-0527-3

It only sorts within the block your cursor is on – so, click within the dependencies and select the Sort Properties to sort the dependencies. While we are on the subject of dependencies, light bulb options allow you to update the package and visit the home page:

blog-code-0527-4

Writing Code

Start typing. Note that when you type in require, you get intellisense based on your installed dependencies, plus an explanation:

blog-code-0527-5

There is a little gotcha here. While you are typing a long multi-line statement, the indenting will be a little weird – it re-formats on the fly to the start of the line. Once you hit that semi-colon, it re-formats it back to the proper indentation. It’s weird, but this is a beta.

Intellisense is helping throughout the process of this with context-relevant suggestions:

blog-code-0527-6

Intellisense is more than just giving suggestions on what is next – it provides in-editor documentation pop-ups so you can know what arguments are needed as well.

Could it be better? Sure. In particular, I noticed that the app and server variables were not auto-completed in the server.js file. Had I written this in TypeScript, maybe that would have happened because TypeScript has strong types. Also, the indenting immediately following the multi-line statements was off:

var express = require("express"),
    http = require("http");

    /**
     * Why is this indented here instead of flush to the left?
     */

All in all, these are things I can live with. I also bumped into a coloration bug. Hopefully, they will get these minor annoyances fixed before the final build.

In terms of improvements, you will notice that Linters and Documentation are common in all large scale projects. I would love to define a documentation standard (JSDoc) and linter (ESLint) for the project and get those run and verified on every single save.

Editing CSS and HTML is exactly the same as the experience in the Visual Studio 2015 CTP 6, so I won’t delve into it here. There are none of the annoying hangs that I experienced at the end of April, so stablility and performance have been improved significantly here.

Running Locally

Once the code is written, you might want to run it. So, hit the Google Chrome button in the top menu bar to launch the node executable and start the browser. You can also run the program by pressing F5.

blog-code-0527-7

This is where the experience is not altogether there. NTVS (Node Tools for Visual Studio) opens up a command window. Why am I not seeing this in a panel within Visual Studio?

blog-code-0527-8

As you can see, I have an error. This was deliberate since I wanted to see what would happen. I need this to persist – preferably in an error list where I can refer to it. Also, I want to click on the error and go to the file/line that is causing it. None of those things happen.

Node.js does If you get through to the serving of pages, you can set breakpoints, watch locals, etc. However, that doesn’t help you when the program terminates because of an Exception. Once you get through the initialization, the debugging environment is pretty solid (click on image for bigger view):

blog-code-0527-9

Publishing to the Cloud

The last step is to take a look at what is required to publish the app up to the cloud. Right-click on the project and select Publish…

blog-code-0527-10

You can create a new web site directly from here. Note that with the command line tools, we were limited to creating a cloud app. Here I can create a Web App:

blog-code-0527-11

Don’t forget to change the Auth0 callback address in the Auth0 management console to match the URL you are generating. After this screen, it’s a fairly straight forward process – which ultimately fails. I got the error:

blog-code-0527-12

This is actually fairly common because node_modules runs on Windows but it’s really for Linux where the directory depth doesn’t matter. As a result, I have problems dealing with node_modules on a regular basis and this is just one more symptom of that problem.

The Verdict

We’re not quite there yet!

The killer is that final problem – dealing with the directory structure that npm uses. Unfortunately, the node/npm crowd don’t seem willing to deal with this issue.

Let’s be real here: NODE AND NPM DO NOT SUPPORT WINDOWS.

They might claim to support Windows and they may even ship code that runs on Windows, but that is a far cry from supporting Windows. Unless Microsoft steps up to the plate and produces an npm lookalike that actually works, node on Windows is the walking dead. Ok, it might not be that bad since there are workarounds, but support is just not there.

Back to my original quest – could I develop Node applications on Windows? Yes I can, right up to the point where I want to use automated tools to publish them. At that point, I have to switch over to a non-Windows system.

And that’s probably fine.

MVC Architecture – Node Style

In the last three articles I’ve been doing a study of what it takes to do a node app. For those who are starting here, I had a list of requirements:

  1. I need to be able to run a small web server
  2. I need to be able to handle templated views with server-side code
  3. I need to be able to do social authentication
  4. I need to be able to use an MVC architecture
  5. I need to be able to provide a Web API
  6. I need to be able to publish a node app to Azure
  7. I need to be able to edit node applications in Visual Studio 2015

I didn’t like the way I was writing code in the first three articles. It looked a little hacky. I wanted to get a clear separation of concerns – something the MVC pattern provides and something I am using in my ASP.NET vNext projects. I finally got there, but it was a lot of code.

Let’s start with the package.json – I needed a bunch more libraries to do MVC than I was using before. Here is my new package.json file:

{
  "name": "basic-webapp",
  "version": "0.0.1",
  "description": "A Basic View-Controller Web Application",
  "main": "server.js",
  "private": true,
  "scripts": {
    "start": "node server.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/adrianhall/node-stuff"
  },
  "author": "Adrian Hall ",
  "contributors": [
    {
      "name": "Adrian Hall",
      "email": "adrian@shellmonger.com"
    }
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/adrianhall/node-stuff/issues"
  },
  "homepage": "https://github.com/adrianhall/node-stuff",
  "dependencies": {
    "body-parser": "^1.12.3",
    "cookie-parser": "^1.3.4",
    "ejs": "^2.3.1",
    "express": "^4.12.3",
    "express-partials": "^0.3.0",
    "express-session": "^1.11.1",
    "extend": "^2.0.1",
    "method-override": "^2.3.2",
    "passport": "^0.2.1",
    "passport-auth0": "^0.2.1",
    "serve-favicon": "^2.2.0"
  }
}

Some of these are not strictly necessary. For example, I could happily dispense with the favicon. However, I wanted for the setup to be as complete as possible. Note that I’ve added a script as well – to start the server I now use npm start instead of node index.js. It also starts a different file – server.js.

In order to configure the application, I added a configuration JSON file called config.json. Note that the GitHub Repository has the file config-default.json – that’s because YOU MUST EDIT THIS FILE BEFORE USE. Here is the config-default.json file:

{
  "loginRoute": "/account/login",
  "server": {
    "uri": "http://localhost:3000",
    "port": 3000
  },
  "passport": {
    "auth0": {
      "domain": "{{DOMAIN}}.auth0.com",
      "clientid": "{{CLIENTID}}",
      "clientsecret": "{{CLIENTSECRET}}",
      "connections": [ "facebook", "windowslive", "google-oauth2", "twitter" ]
    }
  }
}

If you remember from the social authentication article, I’m using Auth0 as my social authenticator. That system requires a domain, client ID and client secret from the Auth0 website to function. Replace the appropriate pieces in this file and save it as config.json

To make sure that I don’t check in MY config.json, I’ve added it to the .gitignore file.

Talking of auth0, my auth0-strategy.js file is slightly different as well:

/*eslint-env node */

"use strict";

var passport = require("passport"),
    Auth0Strategy = require("passport-auth0"),
    config = require("./config.json");

var strategy = new Auth0Strategy({
  domain: config.passport.auth0.domain,
  clientID: config.passport.auth0.clientid,
  clientSecret: config.passport.auth0.clientsecret,
  callbackURL: "/account/external-callback"
}, function(accessToken, refreshToken, extraParams, profile, done) {
  return done(null, profile);
});

passport.use(strategy);

// This is not a best practice, but we want to keep things simple for now
passport.serializeUser(function(user, done) {
  done(null, user);
});

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

module.exports = strategy;

It reads the config.json file and inserts the information you stored there into the constructor for the Auth0Strategy. Note that I’ve also set up ESLint properly – I’m in a node environment (the first line). If you run eslint auth0-strategy.js, this will pass.

I also changed my callback URL. This is to allow a controller to handle the account activities. More on that later, but you will have to insert the new callback URL into the Auth0 Management console for your app.

The final piece of application code (before we get onto the controllers and views) is the server.js file:

/*eslint-env node */

"use strict";

var express = require("express"),
    http = require("http"),
    path = require("path"),
    fs = require("fs"),
    partials = require("express-partials"),
    ejs = require("ejs"),
    passport = require("passport"),
    cookieParser = require("cookie-parser"),
    session = require("express-session"),
    bodyParser = require("body-parser"),
    methodOverride = require("method-override"),
    favicon = require("serve-favicon");

/**
 * Configure the Express server to serve up the application
 * @param {express} app - the express application object
 * @return - the express application object (pipelining allowed)
 */
function configure(app) {
  // Load the server configuration file
  console.info("Loading Server Configuration");
  var config = require("./config.json");

  // Load the authentication strategy
  console.info("Loading Authentication Strategy");
  var authStrategy = require("./auth0-strategy"); // eslint-disable-line no-unused-vars

  // Set up the port that the server will listen on
  console.info("Setting Listening port");
  app.set("port", process.env.PORT || config.server.port || 3000);

  // Set up the location of the views
  console.info("Setting view location");
  app.set("views", path.join(__dirname, "views"));

  // Set Express to use the EJS view engine
  console.info("Configuring view engine");
  app.engine("html", ejs.renderFile);
  app.set("view engine", "html");

  // Set up express to use layouts with the default layout being in
  // /views/Shared/layout.html
  console.info("Configuring layout engine");
  app.set("view options", { defaultLayout: "Shared/layout" });
  app.use(partials());

  // Set up express to use the passport authentication middleware
  console.info("Configuring Passport authentication");
  app.use(cookieParser());
  app.use(session({
    secret: "app-secret",
    resave: false,
    saveUninitialized: false,
    unset: "destroy"
  }));
  app.use(passport.initialize());
  app.use(passport.session());

  // Set up static file serving
  console.info("Configuring static file serving");
  app.use("/client", express.static(path.join(__dirname, "client")));

  // Provide middleware for decoding JSON, URL-encoded body parts
  console.info("Loading Body Parser Middleware");
  app.use(bodyParser.urlencoded({ extended: false }));
  app.use(bodyParser.json());

  // Allows a controller to override HTTP verbs such as PUT or DELETE
  console.info("Loading Method Overrides");
  app.use(methodOverride());

  // Serve up a default favicon
  console.info("Configuring favicon");
  app.use(favicon(path.join(__dirname, "client/favicon.ico")));

  // Dynamically include controllers in the controllers directory
  console.info("Loading Controllers");
  fs.readdirSync("./controllers").forEach(function (file) {
    if (file.substr(-3) === ".js") {
      console.info("Loading Controller " + file);
      var base = "/" + path.basename(file, ".js");
      var route = require("./controllers/" + file);
      app.use(base, route);
    }
  });

  console.info("Configuring Home Controller");
  app.get("/", function(req, res) {
    res.redirect("/home");
  });

  // Return the app so we can pipeline
  console.info("Finished configuring server");
  return app;
}

/*
 * Configure the application
 */
var server = configure(express());
http.createServer(server).listen(server.get("port"), function () {
  console.info("Express Server listening on port " + server.get("port"));
});

Phew – that’s a lot of code. Fortunately, this is all fairly explanatory with the comments that are in there. The only complicated bit is this piece of code that loads the controllers (highlighted at lines 80-89) The first block loads each javascript file in the controllers directory. For each controller, I compute a base URL. If the controller is called home.js, then the base will be /home. I’m expecting the controller to export an Express Router object – more on that when I get to controllers.

Once I’ve configured all the controllers, I set the default home page to redirect to the root document in the home controller. This is just like ASP.NET when you provide a route configuration that includes {controller=Home}.

The Basic Controller Pattern

Let’s take a look at the home controller (controllers/home.js) so that we can understand the logic that goes into it. The home controller has a home page that renders a view. However, the view only gets rendered if the user is authenticated. If the user is not authenticated, the user is redirected to the account controller:

/*eslint-env node */

"use strict";

var express = require("express"),
    path = require("path"),
    config = require("../config.json"),
    extend = require("extend");

var router = express.Router(), // eslint-disable-line new-cap
    controller = path.basename(__filename, ".js"),
    loginRoute = config.loginRoute || "/account/login";

/**
 * Set of default properties for the rendering engine
 */
function defaultProperties(req) {
  return {
    title: "Unknown",   // Default title in case the developer doesn't set one
    user: req.user
  };
}

/**
 * GET /{controller=Home}/index
 */
function index(req, res) {
  if (!req.isAuthenticated()) {
    res.redirect(loginRoute);
    return;
  }
  res.render(controller + "/index.html", extend({}, defaultProperties(req), {
    title: "Home"
  }));
}

// Per-route functionality
router.get("/index", index);

// Default route is to GET index
router.get("/", index);

module.exports = router;

First off, I create an Express router. I also compute the controller name based on the filename of the javascript file. Finally, I compute the loginRoute – if it is specified in the configuration, then use that, otherwise I have a default location specified.

The defaultProperties() method returns an object with some information that the layout needs. The req object is not available in the layout and I want to display my name and maybe other information from the authentication object. In addition, I want to ensure that things don’t break just because I didn’t give all the parameters the layout needs for rendering.

The index() method is the handler for the /index route. The initial part just handles redirection if the user is not authenticated. The render() statement renders a view based on the controller – In this case, this is the home controller, so it will look in views/home/index.html for its view.

The extend() statement is an interesting one and is provided by a library. It combines multiple objects together, with an order of precedence. I start with the empty object, add in the default properties, then add in the route specific properties. I suppose I could do something like:

/**
 * Render an appropriate view
 */
function view(req, res, viewName, locals) {
  res.render(controller + "/" + viewName + ".html",
    extend({}, defaultProperties(req), locals));
}

As an appropriate helper. Then the index function becomes:

/**
 * GET /{controller=Home}/index
 */
function index(req, res) {
  if (!req.isAuthenticated()) {
    res.redirect(loginRoute);
    return;
  }
  view(req, res, "index", { title: "Home" });
}

This is maybe slightly more readable, but not necessary.

The final piece of the module wires up the routes relative to the controller – I’ve given two routes, one for / and one for /index – both identical. Thus, when the user browses to /home or /home/index, they get the same page. Finally, I export the router I created. This is the object that the configuration code in server.js gets – it then links it into the main express router using the controller name as the location.

The Basic View Pattern

To go along with the basic controller, I need a basic view. All the views are relative to ./views/{controller} in this pattern. My ./views/home/index.html file is basic indeed:

<h1>Index</h1>

I want that wrapped in a layout. I included the following code in server.js to specify the default location of the layout (assuming one was not specified):

  console.info("Configuring layout engine");
  app.set("view options", { defaultLayout: "Shared/layout" });
  app.use(partials());

This tells me the layout, by default, is in ./views/Shared/layout.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="A basic web application">
  <meta name="author" content="">

  <title><%= title %> | Basic WebApp</title>

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
  <!--[if lt IE 9 ]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
  <![endif]-->

  <link rel="stylesheet" href="/client/layout.css">

</head>
<body>
  <div id="wrapper">
    <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
      <div class="navbar-header">
        <a class="navbar-brand" href="/">Basic WebApp</a>
      </div>
      <!-- Top Menu Items -->
      <ul class="nav navbar-right top-nav">
        <li><a href="/account/profile"><%= user.displayName %></a></li>
        <li><a href="/account/logout">
          <i class="fa fa-sign-out"></i>
          <span class="sr-only">Sign Out</span>
        </a></li>
      </ul>
    </nav>
    <section id="page-wrapper">
      <div class="container-fluid">
        <%- body %>
      </div> <!-- /.container-fluid -->
    </section> <!-- /#page-wrapper -->
  </div> <!-- /#wrapper -->

  <script src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
</body>
</html>

I’ve included a default navbar that includes the user display name and a link to the logout. I have not created an /account/profile in this article (and there isn’t going to be one – that’s for you to write). Note that I’ve also added in some CSS (which I’m not going to show off in the article – go check out the GitHub Repository).

This is very similar to the Razor _Shared.cshtml file in ASP.NET. The rendering is a little different due to the change in rendering engine, but it should feel familiar.

The Account Controller

In ASP.NET, the Account Controller was special. It’s no different here. I’m only doing Social Authentication in this version. In the Auth0 management portal, I’ve turned on Twitter, Facebook, Windows Live and Google authentication. I have not changed the API keys from the Auth0 development keys. If you were deploying this in production (and paying Auth0), then you would definitely want to register your own keys with Auth0.

If you click on your app and then on Connections (along the top), you should see something like this:

blog-code-0521-1

Now that is done, let’s take a look at the controller controllers/account.js:

/*eslint-env node */

"use strict";

var express = require("express"),
    passport = require("passport"),
    path = require("path"),
    config = require("../config.json");

var router = express.Router(), // eslint-disable-line new-cap
    controller = path.basename(__filename, ".js");

/**
 * Build a URL for a specific provider based on the configuration and
 * the provider name
 */
function buildurl(provider) {
  var server = config.server.uri || "http://localhost:3000";

  var url = "https://" + config.passport.auth0.domain + "/authorize" +
    "?response_type=code&scope=openid%20profile" +
    "&client_id=" + config.passport.auth0.clientid +
    "&redirect_uri=" + server + "/" + controller + "/external-callback" +
    "&connection=" + provider;

  return url;
}

Up to this point, it’s a regular controller. I’ve defined a private function for building a URL to link into the Auth0 system. I’m going to pass these URLs as locals in the login method – speaking of which:

/**
 * GET /{controller}/login
 */
function login(req, res) {
  var locals = {
    layout: false,
    connections: {}
  };
  var connections = config.passport.auth0.connections || [];
  for (var i = 0; i < connections.length; i++) {
    locals.connections[connections[i].replace("-","_")] = buildurl(connections[i]);
  }

  res.render(controller + "/login.html", locals);
}

/**
 * GET /{controller}/logout
 */
function logout(req, res) {
  req.logout();
  res.redirect("/");
}

// Wire up Per-route functionality
router.get("/login", login);
router.get("/logout", logout);

This section is all about wiring up the login and logout routes. The login route will display the ./views/account/login.html view, and I am passing in the list of connections from the config.json file. You can have as many as you want here.

// Social Identity callback - set this in the Auth0 Manage App page
router.get(
  "/external-callback",
  passport.authenticate("auth0", {
    failureRedirect: "/" + controller + "/failure-callback"
  }),
  function (req, res) {
    if (!req.user) {
      throw new Error("user is null");
    }
    res.redirect("/");
  }
);

module.exports = router;

Finally, we need to handle the callback. This is set in the auth0-strategy file and this file – in two places. Firstly, in the buildurl() method and then again in the route to handle the external callback. You can specify a new route /failure-callback that displays a “Oh Noes – Something bad happened” view, or you can just let it 404. If everything is successful, we redirect back to the home page – this time authenticated.

The ./views/account/login.html is a complete HTML page. I’ve specified “layout: false” in the locals – a signal to the rendering engine to not use a layout file:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">

  <!-- Bootstrap, Bootstrap-Social, Font-Awesome -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">

  <!-- My minimal stylesheet -->
  <link rel="stylesheet" href="/client/bootstrap-social.css">
  <link rel="stylesheet" href="/client/login.css">
  <title>Login | Basic WebApp</title>
</head>
<body>
  <div id="outer">
    <h2>Login with your Social Provider</h2>
    <div class="row">
      <div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 social-buttons">
        <a class="btn btn-block btn-social btn-twitter" href="<%= connections.twitter %>">
          <i class="fa fa-twitter"></i> Sign in with Twitter
        </a>
      </div>
      <div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 social-buttons">
        <a class="btn btn-block btn-social btn-facebook" href="<%= connections.facebook %>">
          <i class="fa fa-facebook"></i> Sign in with Facebook
        </a>
      </div>
      <div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 social-buttons">
        <a class="btn btn-block btn-social btn-microsoft" href="<%= connections.windowslive %>">
          <i class="fa fa-windows"></i> Sign in with Microsoft
        </a>
      </div>
      <div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 social-buttons">
        <a class="btn btn-block btn-social btn-google" href="<%= connections.google_oauth2 %>">
          <i class="fa fa-google"></i> Sign in with Google
        </a>
      </div>
    </div>
  </div>
</body>
</html>

I’ve downloaded bootstrap-social.css and put it in my client static area for this.

With all this code, I now have a complete basic web app that authenticates via one of four social logins and produces a nice Bootstrap-enabled front page.

You can get all the code from my GitHub Repository.

Social Authentication – Node Style

I’m currently in the middle of a Node investigation. Here was the list of my requirements:

  1. I need to be able to run a small web server
  2. I need to be able to handle templated views with server-side code
  3. I need to be able to do social authentication
  4. I need to be able to use an MVC architecture
  5. I need to be able to provide a Web API
  6. I need to be able to publish a node app to Azure
  7. I need to be able to edit node applications in Visual Studio 2015

Today is all about social authentication. Research on google tells me that I should use Passport which is a modular middleware for – you guessed it – authentication in a node/express environment. I recently met with the guys at Auth0 and their technology impressed me. You can do one call to one provider and authenticate to many environments – including enterprise accounts like Active Directory and Azure AD. I just want Twitter right now, but that is simply awesome, so I’m planning on integrating that technology as well.

Step 1: Sign up for a free Auth0 Account

You only start paying for Auth0 usage when you go into production. I’m not going into production, so I am using their free account. Just go onto their home page and click the big button to sign up.

Their process also walks you through creating a default app. This is perfectly fine for what I’m going to do – just make sure you select Twitter authentication when you do this process.

Step 2: Set up the Auth0 Default App

In the Auth0 management screen, select the Apps / APIs option on the left hand menu, then click on the Default App. A screen shot is shown below with obfuscated client credentials.

blog-code-0519-1

Note the callback URL. I have to set that to whatever my website is running as. The URL http://localhost:3000 is where my node application lives. If I were running this in Azure, then I would have something like http://shellmonger-basic-app.azurewebsites.net/. Take note of the domain, client ID and client secret that I have blacked out – these are important later.

Step 3: Write an Auth0 strategy file

While we are logged on to the auth0 management portal, click over to Quick Start and select a Regular Web Site on Node.JS. It has some boiler-plate code there. In Step 2, there is a section of code for configuring Passport for Auth0. Cut and paste that into a file in your project called auth0-strategy.js. Mine (with adjustments for obfuscating secrets) looks like this:

var passport = require('passport');
var Auth0Strategy = require('passport-auth0');

var strategy = new Auth0Strategy({
    domain:       '{{DOMAIN}}.auth0.com',
    clientID:     '{{CLIENTID}}',
    clientSecret: '{{CLIENTSECRET}}',
    callbackURL:  '/callback'
  }, function(accessToken, refreshToken, extraParams, profile, done) {
    // accessToken is the token to call Auth0 API (not needed in the most cases)
    // extraParams.id_token has the JSON Web Token
    // profile has all the information from the user
    return done(null, profile);
  });

passport.use(strategy);

// This is not a best practice, but we want to keep things simple for now
passport.serializeUser(function(user, done) {
  done(null, user);
});

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

module.exports = strategy;

The three pieces I have obfuscated are the three pieces I blacked out when configuring the default app above.

Step 4: Add a Login Page

I cheated for the login page. I have a client/login.html file with the following contents:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Login</title>
</head>
<body>
  <h1><a href="https://{{DOMAIN}}.auth0.com/authorize?response_type=code&scope=openid%20profile&client_id={{CLIENTID}}&redirect_uri=http://localhost:3000/callback&connection=twitter">Twitter</a></h1>
</html>

The URL is further down the quick start page. In the drop-down for section 6, select “Plain Link” and you will get this URL. Now I just have to click on that link and it will initiate the login sequence.

Step 5: Update the main server file

Finally, I need to update the main server file – index.js – to add in authentication. First of all, I need some new libraries. Install them with npm:

npm install --save passport passport-auth0 cookie-parser express-session

I also need to bring in the libraries at the top of index.js:

var express = require("express"),
    partials = require("express-partials"),
    ejs = require("ejs"),
    passport = require("passport"),
    cookieParser = require("cookie-parser"),
    session = require("express-session"),
    auth0strategy = require("./auth0-strategy");

There are three things I need to do in the main server file:

  1. Configure the Passport system with the Auth0 Strategy
  2. Do a redirect to the login page if the user is unauthenticated
  3. Handle the callback from Auth0

First up is configuration. I’ve added the following code (which is boilerplate from the auth0 quick start):

// Set up express to use the passport authentication middleware
app.use(cookieParser());
app.use(session({ secret: "my-little-secret" }));
app.use(passport.initialize());
app.use(passport.session());

This is placed alongside the other app.use() method calls.

Let’s say a user is unauthenticated. They browse to the home page. The first thing I want to happen is to redirect the user to the login page that I created in Step 4. To do that, I’ve added some code to the home page view handler. In fact, this was why I wanted a view handler in the first place – so I could inject some server side code:

// Serve up pages - Home Page
app.get("/", function(req, res) {
  if (!req.isAuthenticated()) {
    res.redirect("/client/login.html");
  } else {
    res.render("index", {
      title: "Index Page (from index.js)",
      user: req.user
    });
  }
});

The user will get served up the /client/login.html page which has that single Twitter link on it. They will click on that and be redirected to the Auth0 site. Auth0 will do it’s magic to authenticate the user and redirect back to the redirect_url from the Twitter link in /client/login.html (this redirect URL is also registered on the Auth0 site).

That redirection goes to /callback. I’ve added the following code directly before the home page handler to handle the callback:

// Serve up pages - Callback handler from Auth0
app.get("/callback",
  passport.authenticate("auth0", {
    failureRedirect: "/client/failure.html"
  }),
  function(req, res) {
    if (!req.user) {
      throw new Error("user is null");
    }
    console.log("User is %o", req.user);
    res.redirect("/");
  }
);

When the user is redirected back to /callback, it presents a new cookie. The passport.authenticate method calls does a call back to Auth0 to ask if the cookie is valid and if so to fill in the details of the user. If that succeeds, then the user is redirected back to the home page, but this time in an authenticated state.

Note I’ve printed the req.user object. This has all sorts of interesting information in it:

User is %o { provider: 'twitter',
  displayName: 'Adrian Hall',
  id: 'twitter|257173506',
  name: { familyName: undefined, givenName: undefined },
  picture: 'https://pbs.twimg.com/profile_images/1668098857/May_2011_Photo_normal.png',
  nickname: 'Adrian Hall',
  identities:
   [ { access_token: '**REMOVED**',
       access_token_secret: '**REMOVED**',
       provider: 'twitter',
       user_id: 257173506,
       connection: 'twitter',
       isSocial: true } ],
  _json:
   { name: 'Adrian Hall',
     picture: 'https://pbs.twimg.com/profile_images/1668098857/May_2011_Photo_normal.png',
     description: 'Big Data in IT operations.',
     lang: 'en',
     location: 'Seattle, WA, US',
     screen_name: 'FizzyInTheHall',
     time_zone: 'Pacific Time (US &amp; Canada)',
     url: 'http://t.co/7dxPr1cxdj',
     utc_offset: -25200,
     clientID: '{{CLIENTID}}',
     user_id: 'twitter|257173506',
     nickname: 'Adrian Hall',
     identities: [ [Object] ],
     created_at: '2015-04-27T17:19:41.812Z' },
  _raw: '**REMOVED**' }

I could add something like <%= user.displayName %> to my view (since I’ve added the user object to the model) and get the display name.

Final Thoughts on Authentication

There are some competing styles for authentication – OAuth 1, OAuth 2 and OpenID are some of the more visible and commonly used ones in social authentication. Which one is better? That requires an understanding of the politics and technical development that are beyond me – which is why I think a provider like Auth0 (or its arch nemesis – oauth.io) is a good idea. In the end, writing one connector and handling multiple providers makes life easier for you – the developer – with no loss of functionality for your user.

It’s a good idea to get a good grasp on how these work so you can see what is going on via Fiddler when authentication is not working.

As always, my work is published on my GitHub Repository.