WiFi Hotspot with Social OAuth

 

The Brick & Tun would like to offer guest WiFi, but need to provide a convenient way of logging into the network. Since most people have social network accounts, let’s use their OAuth API to authenticate guests. Plus, you can get public profile information from your visitors which can be used with marketing or tailored services, so everybody wins.

What is in a public profile and is this safe?  It could be sad to loose all gained site activity, especially when grown it with help of https://themarketingheaven.com/, Depending on the social network, public information will vary. It’s typical to share your full name by default, then by adjusting the profile “scope”, additional information can be gathered. This could include email, birthdate, favorite color, company, etc. By using this login option, the social credentials will not be compromised and full access to the account will not be available to the ExCap or any other type of service that uses this login method.

Most WiFi hotspot providers have a legal or policy obligation to govern who can use their Internet connection. This may require a full name and email. Since this information can be extracted from a social login, why not make this a simple process instead of having guests fill out forms? This is why using Facebook or Google to login to a shopping cart, Spotify account or the local WiFi hotspot has become so popular. It saves time for the guest and the developers don’t have to build complex authentication systems.

 

Goal

Build a Cisco Meraki external captive portal (ExCap) that accepts Email, Facebook, Twitter, LinkedIn and Google authentication options.

 

Overview

This article builds on a previous IoL blog post, WiFi Hotspot – Cisco Meraki ExCap & NodeJS, which goes into great detail of building a Captive Portal with the Cisco Meraki ExCap API using NodeJS. I suggest you have a read through that to fully understand this article and the overall application.

In this article, I will focus on adding social logins using OAuth. By modifying the Click-through method from the previous article, we can extend its functionality using the Passport node package module.

Technologies

Concept

A captive portal or “splash page”, is a web page that is presented to a guest when they join the wireless network. Sometimes this page opens up as soon as the user connects to the WiFi SSID. This functionality is called the CNA (Captive Network Assistant). Apple and Google have their own ways of detecting if a splash page is required and may trigger it by automatically connecting to a generic website in the background. Alternatively, a guest must first visit a non-SSL site. This is very important to understand. If your device simply tries to sync email in the background, before visiting a non-secure site and authenticating, the sync will fail. Also, if a guest were to visit https://www.Google.com, which is SSL, the website may fail to load without any explanation.

Meraki offers two methods for authenticating to a network via a splash page.

  1. Click-through
  2. Sign-on

1447928486_full.png

With the click-through login method, a user is granted access as soon as the server redirects the client to the splash page grant url.

With the sign-on method, a user will need to complete a form that includes their username and password. This will then be checked against a RADIUS server before being granted access.

This article will only focus on the simpler “Click-through” method, since it does not require the additional RADIUS component.

Click-through Flow

Click-through-splash-page-diagram_thumb.png

Image provided by Cisco Meraki.

 

    1. Client connects to the wireless SSID
    2. Client navigates to a non-SSL website (automatically with CNA or manually)
    3. Meraki AP intercepts the page request, and redirects client to the ExCap server
    4. The web server will receive a GET request from the client, which will also include several parameters to identify the client and other network properties.
      1. GET /click?base_grant_url=https%3A%2F%2Fn143.network-auth.com%2Fsplash%2Fgrant&user_continue_url=http%3A%2F%2Fuk.ask.com%2Fweb%3Fq%3Ddsf%26qsrc%3D0%26o%3D312%26l%3Ddir%26qo%3DhomepageSearchBox&node_id=149624927555708&node_mac=88:15:44:a8:10:7c&gateway_id=149624927555708&client_ip=10.223.205.118&client_mac=84:3a:4b:50:e2:3c
    5. The server will present a web page that typically includes branding, Ts&Cs and button to complete the sign-on.
    6. The “button” is nothing more than a redirect to the “base_grant_url”, which Meraki will use to complete the login process.
    7. Meraki will return the user to the “continue_url”, which could be their intended site or your own custom success page (which may have a link to their intended site).

To support a login method, all we need to do is verify the user before providing the “base_grant_url” at stage 6.

Passport Login Options

http://passportjs.org/

In the previous article, the click-through splash page simply delivered a terms of service with a survey form, then logged the user in. There was no verification at all.

By leveraging passports, we can offer a number of social OAuth or other login methods. All that needs to be done is insert a step that calls the passport “strategy” and wait for a success or failure from the provider. Based on that, the captive portal will redirect the user onto Meraki to process the login or start over.

 

login options

Walled Garden

Before the guest can be redirected to your captive portal and any of the supporting systems, be sure to update the Walled Garden within the Cisco Meraki Dashboard. After adding all of the login options, the list will look similar to this.

walled garden social auth

Let’s walk through the additional code required for this to work.

Click Route

The “route” is basically the URL where the client will connect to the captive portal. This route is named “/click” which means the complete URL would look like “https://myserver.com/click“.

As the guest connects to the server, there will be several parameters included in the URL, which can be extracted and stored to identify the user and obtain the “base_grant_url”. This session data will be stored into a local MongoDB database. Please ensure that you have a MongoDB installed and running for this server to work properly.

// serving the static click-through HTML splash page
app.get('/click', function (req, res) {

  // extract parameters (queries) from URL
  req.session.host = req.headers.host;
  req.session.base_grant_url = req.query.base_grant_url;
  req.session.user_continue_url = req.query.user_continue_url;
  req.session.node_mac = req.query.node_mac;
  req.session.client_ip = req.query.client_ip;
  req.session.client_mac = req.query.client_mac;
  req.session.splashclick_time = new Date().toString();

  // success page options instead of continuing on to intended url
  req.session.success_url = 'https://' + req.session.host + "/success";
  req.session.continue_url = req.query.user_continue_url;

  // render login page using handlebars template and send in session data
  
('click-through', req.session);

});

 Click-Through HTML page

The previous route ends with the line res.render('click-through', req.session);

This tells NodeJS to render the static HTML page ‘click-through’, using the Handlebars template system and send the req.session object. This object contains all of the extracted parameters that can then be accessed by using {{myvariable}} within the document. This page also contains all of the login method buttons, which simply point to various routes, which will perform the authentication step.

<div>
    <h3>Login Options</h3>

    <a href="/auth/signup" class="btn btn-default"><span class="fa fa-user"></span> Email</a>
    <a href="/auth/facebook" class="btn btn-primary"><span class="fa fa-facebook"></span> Facebook</a>
    <a href="/auth/twitter" class="btn btn-info"><span class="fa fa-twitter"></span> Twitter</a>
    <a href="/auth/google" class="btn btn-danger"><span class="fa fa-google-plus"></span> Google+</a>
    <a href="/auth/linkedin" class="btn btn-info"><span class="fa fa-linkedin"></span> LinkedIn</a>
    </div>
    <span class="pull-left"><a href="/terms">Terms and Conditions</a></span>
    </div>
  </div>
  <div class="footer">
    <p>Your IP: {{client_ip}}</p>
    <p>Your MAC: {{client_mac}}</p>
    <p>AP MAC: {{node_mac}}</p>
    <h3>POWERED BY</h3>
    <img class="text-center" src="/img/cisco-meraki-gray.png" style="width:10%; margin:10px;">
  </div>


excap-social click

Passport

All of the login methods will use the passport NPM module, which consists of two primary components. The route and the strategy.

There will be two routes for each passport authentication method. The first is the ‘/auth/…’ and the second route handles the callbacks, which tells the app what to do next after a success or failure.

OAuth Examples:

Google+

 

// GOOGLE ---------------------------------

// send to google to do the authentication
app.get('/auth/google', 
  passport.authenticate('google'));

// the callback after google has authenticated the user
app.get('/auth/google/callback',
  passport.authenticate('google', {
    successRedirect : '/auth/wifi',
    failureRedirect : '/auth/google'
  })
);

LinkedIn

// LINKEDIN --------------------------------

app.get('/auth/linkedin',
  passport.authenticate('linkedin'));

app.get('/auth/linkedin/callback',
  passport.authenticate('linkedin', {
    successRedirect : '/auth/wifi',
    failureRedirect : '/auth/linkedin'
  }) 
);

 

Passport Strategy

The strategy is where you configure the passport mechanics. It works by instantiating a strategy object, and provide the necessary options for it to work. Each provider will typically require a ClientID, Secret, scope and callback URL. The ID and secret will be provided by the third party service and are used to attach your application to their service securely. The “scope” is used to specify how much of the user’s profile you are attempting to pull. It’s best to keep this to a minimum for privacy concerns. Finally, the callback URL is used to redirect the client back to your service once completed.

Google+

First you must create a Web App on Google’s developer portal. You will then have access to your Client ID and Client Secret. You will also need to specify the JavaScript origins and the Authorized redirect URIs. This should be your ExCap (splash server) and respective callback URL route.

Google OAuth dev

Now that we have the necessary info, we can modify the passport code to match.

...
 // =========================================================================
    // GOOGLE ==================================================================
    // =========================================================================
    passport.use(new GoogleStrategy({

        clientID        : configAuth.googleAuth.clientID,
        clientSecret    : configAuth.googleAuth.clientSecret,
        callbackURL     : configAuth.googleAuth.callbackURL,
        scope           : ['profile','email'],
        passReqToCallback : true // allows us to pass in the req from our route (is the user logged in?)

    },
    function(req, token, refreshToken, profile, done) {
        
        // asynchronous
        process.nextTick(function() {

            // check if the user is already logged in
            if (!req.user) {

                User.findOne({ 'google.id' : profile.id }, function(err, user) {
                    if (err)
                        return done(err);

                    if (user) {

                        // if there is a user id already but no token (user was linked at one point and then removed)
                        if (!user.google.token) {
                            user.google.token       = token;
                            user.google.name        = profile.displayName;
                            user.google.email       = (profile.emails[0].value || '').toLowerCase(); // pull the first email
                            user.google.profile     = profile;

                            user.save(function(err) {
                                if (err)
                                    return done(err);

                                return done(null, user);
                            });
                        }

                        return done(null, user);
                    }
...

LinkedIn

LinkedIn OAuth settings

// =========================================================================
 // LinkedIn
 // =========================================================================
 passport.use(new LinkedInStrategy({
     consumerKey     : configAuth.linkedinAuth.consumerKey,
     consumerSecret  : configAuth.linkedinAuth.consumerSecret,
     callbackURL     : configAuth.linkedinAuth.callbackURL,
     scope           : [ 'r_basicprofile', 'r_emailaddress'],
 },
 function(req, token, tokenSecret, profile, done) {
     console.log("passport LinkedIn, profile: " + util.inspect(profile));
     // asynchronous
     process.nextTick(function() {

         // check if the user is already logged in
         if (!req.user) {

             User.findOne({ 'linkedin.id' : profile.id }, function(err, user) {
                 if (err)
                     return done(err);

                 if (user) {
                     // if there is a user id already but no token (user was linked at one point and then removed)
                     if (!user.linkedin.token) {
                         user.linkedin.token       = token;
                         user.linkedin.name        = profile.displayName;                    
                         user.linkedin.profile     = profile;

                         user.save(function(err) {
                             if (err)
                                 return done(err);

                             return done(null, user);
                         });
                     }

                     return done(null, user); // user found, return that user
                 } else {
                     // if there is no user, create them
                     var newUser                 = new User();

                     newUser.linkedin.id          = profile.id;
                     newUser.linkedin.token       = token;
                     newUser.linkedin.profile     = profile;

                     newUser.save(function(err) {
                         if (err)
                             return done(err);

                         return done(null, newUser);
                     });
                 }
             });

         } else {
             // user already exists and is logged in, we have to link accounts
             var user                 = req.user; // pull the user out of the session

             user.linkedin.id          = profile.id;
             user.linkedin.token       = token;
             user.linkedin.profile = profile;

             user.save(function(err) {
                 if (err)
                     return done(err);

                 return done(null, user);
             });
         }
     });
 }));

Storing User Data

Upon completion of the passport authentication, a token and profile will be returned, along with other objects. The token will be used to preserve the authentication session and the profile will contain the user’s public profile data requested via the scope parameter. This profile data will have a standard set of information, such as ID, first name and last name, that will then be tied to the “User” stored in the local database. All other data, such as profile images, links, etc, will be stored stored in the user’s catch-all “profile” object.  user.linkedin.profile = profile;

Storing API credentials

Note that the clientID and clientSecret is pointing to a variable which is where the API keys and other sensitive information is stored configAuth.linkedinAuth.consumerKey.  The variable is referring to the “./config/auth.js” file that will be exported as a module, to be used in other parts of the application via a require statement. var configAuth = require('./auth'); 

This is ideally where you will add your API passport information. More info can be found in the Installation section near the bottom of this article.

For a more in-depth look at using passports, I highly recommend reading this article from Scotch.io. Much of the passport strategy code came from their wonderful examples.

 

 

Process WiFi Auth with Cisco Meraki

Now, when a user clicks on the Google auth method for example, they will see a familiar login screen.

Google OAuth login

Once the user has been authenticated via the passport, the callback URL will be called. The following ‘/auth/wifi’ route will build the continue url, which consists of the Meraki provided “base_grant_url” and the success_url. This is effectively as 302 redirect, which the client will be forwarded to.

// ====================================================
// WiFi Auth ---------------------------------
// ====================================================

// authenticate wireless session with Cisco Meraki
app.get('/auth/wifi', function(req, res){
  req.session.splashlogin_time = new Date().toString();

  // debug - monitor : display all session data on console
  console.log("Session data at login page = " + util.inspect(req.session, false, null));
  
    // *** redirect user to Meraki to process authentication, then send client to success_url
  res.writeHead(302, {'Location': req.session.base_grant_url + "?continue_url="+req.session.success_url});
  res.end();
});

SUCCESS!

Once Meraki receives this base_grant_url, it will redirect the user to the continue_url, which in our case is the ‘/success’ route. This route renders the final “success” html page and again uses Handlebars to send the req.session object.

// #############
// success page
// #############
app.get('/success', function (req, res) {
  // extract parameters (queries) from URL
  req.session.host = req.headers.host;
  req.session.logout_url = req.query.logout_url;
  req.user_logout_url = req.query.user_logout_url + "&continue_url=" + 'http://' + req.session.host + "/logout";
  req.session.success_time = new Date();

  console.log("Session data at success page = " + util.inspect(req.session, false, null));

  // render sucess page using handlebars template and send in session data
  res.render('success', req.session);
});

The HTML page is where we can deliver any final messages and optionally include the {{continue_url}}, where the guest can click the link to continue onto their destination.

Disregard the logout_url in this code, since only the ExCap “Sign-on” method supports this operation. The only way to log out a user with the Click-through method is by revoking the client in the Meraki Dashboard, which is handy while troubleshooting.

 <h1>Success!</h1>
 <p>
        Continue on to
        <a href={{continue_url}}>{{continue_url}}</a>
</p>

excap-social success

 

Although I just included examples from Google and LinkedIn, they each have the same pattern. They may differ in scope options and setting up the OAuth API on their developer portal, but it is a straightforward process. All of the social and local options are included in the source code for this project.

Data Storage

MongoDB

This system uses MongoDB, which is a NoSQL database system. It’s free and easy to use. The application will use this database to store session data and the user data received from the passport login.

The session data is stored using the connect-mongo-db NPM module, which provides an extension to the Express web service.

The user data is also stored in MongoDB, but first we create a schema to store the data in an organized manner. Since Mongo is schemaless by default, the Mongoose NPM module will be used to easily handle this function.

Just like the passport API credentials, the database info should be stored in a separate configuration file and referenced on-demand.

// config/database.js
module.exports = {

    'url' : 'mongodb://localhost/excap' 

};

To store the session info, we include this code where we build the Express app.

// =================================
// session state
// =================================

var session = require('express-session');
var MongoDBStore = require('connect-mongodb-session')(session);
var store = new MongoDBStore({
    //uri: 'mongodb://localhost:27017/excap',
    uri: configDB.url,
    collection: 'sessions'
});
// Catch errors
store.on('error', function(error) {
    console.log("error connecting to MongoDBStore: "+error);
});

app.use(session({
  secret: 'supersecret',  // this secret is used to encrypt cookie and session state. Client will not see this.
  cookie: {
    maxAge: 1000 * 60 * 60 * 24 // 1 day
  },
  store: store,
  resave: true,
  saveUninitialized: true
}));

To store the user info, we first build a mongoose “model” which defines the schema.

// load the things we need
var mongoose = require('mongoose');
var bcrypt   = require('bcrypt-nodejs');

// define the schema for our user model
var userSchema = mongoose.Schema({

    local            : {
        email        : String,
        password     : String,  
    },
    facebook         : {
        id           : String,
        token        : String,
        email        : String,
        name         : String,
        profile      : Object
    },
    twitter          : {
        id           : String,
        token        : String,
        displayName  : String,
        username     : String,
        profile      : Object
    },
    google           : {
        id           : String,
        token        : String,
        email        : String,
        name         : String,
        profile      : Object
    },
    linkedin         : {
        id           : String,
        token        : String,
        email        : String,
        name         : String,
        profile      : Object
    }

});

// generating a hash
userSchema.methods.generateHash = function(password) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

// checking if password is valid
userSchema.methods.validPassword = function(password) {
    return bcrypt.compareSync(password, this.local.password);
};

// create the model for users and expose it to our app
module.exports = mongoose.model('User', userSchema);

Finally, to see all of the session data, we just need to query the Mongoose database. To make it simple for DEMO PURPOSES, this app includes a simple REST interface for the local database. By simply navigating to your server and appending the ‘/api/v1/users’ or ‘/api/v1/sessions’ to the URL, you can see all of the session and user information. I personally use Postman to work with the JSON output.

var expressMongoRest = require('express-mongo-rest');
app.use('/api/v1', expressMongoRest('mongodb://localhost:27017/excap'));

Sample JSON output for /api/v1/users

This data has been altered for demo/privacy reasons

[
    {
        "linkedin": {
            "profile": {
                "_json": {
                    "lastName": "Smith",
                    "id": "WERasdf",
                    "firstName": "Joe"
                },
                "_raw": "{\n  \"firstName\": \"Joe\",\n  \"id\": \"WERasdf\",\n  \"lastName\": \"Smith\"\n}",
                "name": {
                    "givenName": "Joe",
                    "familyName": "Smith"
                },
                "displayName": "Joe Smith",
                "id": "WERasdf",
                "provider": "linkedin"
            },
            "token": "1a2a4322-6ad3-yyy-xxx-bc093a2465c2",
            "id": "WERasdf"
        },
        "__v": 0,
        "id": "56ec27axxxxxxb1d719abcde1"
    },
    {
        "google": {
            "profile": {
                "_json": {
                    "verified": false,
                    "circledByCount": 86,
                    "language": "en",
                    "isPlusUser": true,
                    "image": {
                        "isDefault": false,
                        "url": "https://lh3.googleusercontent.com/-zEm_wSuSXgM/AAAAAAAAAAI/AAAAAAAABx0/xxxxx/photo.jpg?sz=50"
                    },
                    "url": "https://plus.google.com/xxxxxxxxx",
                    "name": {
                        "givenName": "Joe",
                        "familyName": "Smith"
                    },
                    "displayName": "Joe Smith",
                    "id": "12345678",
                    "objectType": "person",
                    "emails": [
                        {
                            "type": "account",
                            "value": "jsmith1234@gmail.com"
                        }
                    ],
                    "gender": "male",
                    "etag": "\"4OZ_Kt6ujOh1jaML_U6RM6APqoE1234567\"",
                    "kind": "plus#person"
                },
                "_raw": "{\n \"kind\": \"plus#person\",\n \"etag\": \"\"4OZ_Kt6ujOh1jaML_U6RM6APqoE/ZzNfbXlilgj4oqRm3IktMz_6J7U\"\",\n \"gender\": \"male\",\n \"emails\": [\n  {\n   \"value\": \"jsmith@gmail.com\",\n   \"type\": \"account\"\n  }\n ],\n \"objectType\": \"person\",\n \"id\": \"123456789\",\n \"displayName\": \"Joe Smith\",\n \"name\": {\n  \"familyName\": \"Smith\",\n  \"givenName\": \"Joe\"\n },\n \"url\": \"https://plus.google.com/115761369946712953093\",\n \"image\": {\n  \"url\": \"https://lh3.googleusercontent.com/-zEm_xxxxxx/AAAAAAAAAAI/AAAAAAAABx0/XNF8c1IEfGA/photo.jpg?sz=50\",\n  \"isDefault\": false\n },\n \"isPlusUser\": true,\n \"language\": \"en\",\n \"circledByCount\": 86,\n \"verified\": false\n}\n",
                "provider": "google",
                "gender": "male",
                "photos": [
                    {
                        "value": "https://lh3.googleusercontent.com/-zEm_wSuSXgM/xxxxxxx/AAAAAAAABx0/XNF8c1IEfGA/photo.jpg?sz=50"
                    }
                ],
                "emails": [
                    {
                        "type": "account",
                        "value": "jsmith@gmail.com"
                    }
                ],
                "name": {
                    "givenName": "Joe",
                    "familyName": "Smith"
                },
                "displayName": "Joe Smith",
                "id": "115761123412953093"
            },
            "email": "jsmith@gmail.com",
            "name": "Joe Smith",
            "token": "ya29.qQI7-NfVVoh1QWh1234234325145qnqAQxvyatPBXSRRG2XpBL9tJuNx-0hYrPJxw",
            "id": "11576136912341242953093"
        },
        "__v": 0,
        "id": "56ec2bc5234324781c600a45"
    },
    {
        "local": {
            "password": "$2a$08$Nr8ipBP8z/V/6D74xIVlaOsv4gm4xWtdosLBsG2kd4dfj7hnSH9W.",
            "email": "657@test.com"
        },
        "__v": 0,
        "id": "56ec2c8595b93d781c600a46"
    },

Sample JSON output for session data

[
    {
        "session": {
            "cookie": {
                "originalMaxAge": 86399999,
                "expires": "2016-03-19T16:27:56.937Z",
                "secure": null,
                "httpOnly": true,
                "domain": null,
                "path": "/"
            }
        },
        "expires": "2016-03-19T16:27:56.937Z",
        "id": "v2jgFDcSHRbh4Y2c-hXiobuaLImwLeIl"
    },
    {
        "session": {
            "cookie": {
                "originalMaxAge": 86400000,
                "expires": "2016-03-19T15:08:57.586Z",
                "secure": null,
                "httpOnly": true,
                "domain": null,
                "path": "/"
            }
        },
        "expires": "2016-03-19T15:08:57.586Z",
        "id": "rqhJrJGuY-9tQbmPffLznDV1ClVzRQPq"
    },
    {
        "session": {
            "cookie": {
                "originalMaxAge": 86400000,
                "expires": "2016-03-19T12:47:40.191Z",
                "secure": null,
                "httpOnly": true,
                "domain": null,
                "path": "/"
            },
            "host": "app.internetoflego.com:8181",
            "base_grant_url": "https://n143.network-auth.com/splash/grant",
            "user_continue_url": "http://www.ask.com/",
            "node_mac": "88:15:44:a8:10:7c",
            "client_ip": "10.223.205.118",
            "client_mac": "84:3a:4b:50:e2:3c",
            "splashclick_time": "Fri Mar 18 2016 12:47:32 GMT+0000 (GMT)",
            "success_url": "https://excap.internetoflego.com:8181/success",
            "continue_url": "http://www.ask.com/",
            "_locals": {}
        },
        "expires": "2016-03-19T12:47:40.191Z",
        "id": "FBT9eyN3N126io91o_X12XAJ0jAsKF4I"
    },
    {
        "session": {
            "cookie": {
                "originalMaxAge": 86400000,
                "expires": "2016-03-19T16:05:40.618Z",
                "secure": null,
                "httpOnly": true,
                "domain": null,
                "path": "/"
            },
            "host": "excap.internetoflego.com:8181",
            "base_grant_url": "https://n143.network-auth.com/splash/grant",
            "user_continue_url": "http://www.ask.com/",
            "node_mac": "88:15:44:a8:10:7c",
            "client_ip": "10.223.205.118",
            "client_mac": "84:3a:4b:50:e2:3c",
            "splashclick_time": "Fri Mar 18 2016 16:05:28 GMT+0000 (GMT)",
            "success_url": "https://excap.internetoflego.com:8181/success",
            "continue_url": "http://www.ask.com/",
            "_locals": {}
        },
        "expires": "2016-03-19T16:05:40.618Z",
        "id": "nUbwNskAjww3FgLAxEB87PctLL39J3zO"
    },

 

The session and passport data can be linked by matching the “id” fields. This way, you can understand which passport user completed the WiFi authentication session.

Security Best Practices

  • The ExCAP server (your web server), should be hosted using SSL.
  • The MongoDB REST interface should be disabled.
  • The MongoDB data should be on a hardened server and require administrative access to query the data.
  • When collecting user profile data, keep the “scope” to a minimum, and specify your intended use within your terms and conditions.
  • Don’t be creepy.

 

Installation

Download Source Code

GitHub Repo

Using GitHub, clone the repository by opening your terminal shell or GitHub UI and navigating to the directory where you want to clone the application to. The clone will create its own directory called “excap-social”

git clone "https://github.com/dexterlabora/excap-social"

 

Install NPM packages

Change into the “/excap-social” directory and install the remaining packages.

cd excap-social
npm install

 

Install MongoDB

Install MongoDB, following one of these instructions.

Mac OSX

Windows

Linux (Ubuntu)

Create Config files

These files are used to store your API credentials, MongoDB connection, and configs (Express options). They are not included in the GitHub repo, so please copy and paste these samples into a new file.

config/auth.js

// config/auth.js


// expose our config directly to our application using module.exports
module.exports = {

    'facebookAuth' : {
        'clientID'        : 'xxxxxxxxxxxxx', // your App ID
        'clientSecret'    : 'yyyyyyyyyyyyyyyy', // your App Secret
        'callbackURL'     : 'https://excap.internetoflego.com:8181/auth/facebook/callback'
    },

    'twitterAuth' : {
        'consumerKey'        : 'xxxxxxxxxxxxxxxxx',
        'consumerSecret'     : 'yyyyyyyyyyyyyyyyyy',
        'callbackURL'        : 'https://excap.internetoflego.com:8181/auth/twitter/callback'
    },

    'linkedinAuth' : {
        'consumerKey'        : 'xxxxxxxxxxxxxxxxx',
        'consumerSecret'     : 'yyyyyyyyyyyyyyyyy',
        'callbackURL'        : 'https://excap.internetoflego.com:8181/auth/linkedin/callback'
    },

    'googleAuth' : {
        'clientID'         : 'xxxxxxxxxxxxxxxxxx',
        'clientSecret'     : 'yyyyyyyyyyyyyyyyyyy',
        'callbackURL'      : 'https://excap.internetoflego.com:8181/auth/google/callback'
    }

};

config/config.js

This is where you will define the web port that the server listens on (i.e. 8181) and the SSL certificate information. You will need to create your own self-signed certificate or purchase one. I used a GoDaddy SSL certificate for this example and placed the files in an SSL directory I created. For more information, this article was really helpful to get using SSL with the NodeJS.

// Configure the app settings here such as Web port, certificates, etc.



// read from the file system (used for SSL certs)
var fs = require('fs');

module.exports = {
  port: 8181,
  key  : fs.readFileSync('./ssl/excap.yourserver.com.key'),
  cert : fs.readFileSync('./ssl/excap.yourserver.com-gd-signed.crt'),
  ca: [fs.readFileSync('./ssl/gd_bundle-g2-g1.crt')]
};

 

config/database.js

Store the connection string to your MongoDB database here

// config/database.js
module.exports = {

    'url' : 'mongodb://localhost/excap' 

};

Start the server and test

While in the /excap-social directory, use “node” to start the application.

node app.js

To run this app in production, I highly suggest using PM2. This will run the NodeJS application as a service, which supports logging and auto-restart.

pm2 start app.js --name "excap-social"

 

Disable MongoDB REST interface

Find the following code and comment out the middleware to prevent unauthorized access to sensitive session and user data after you have examined the data and place this into production. At that point, you may want to build a separate application to utilize this data or use a third party application. The MongoDB website offers several admin and reporting UI options.

// =================================
// Admin Site mongodb API access
// =================================

// provide MongoDB REST API for JSON session data
// SECURITY WARNING -- ALL USER and SESSION DATA IS AVAILABLE BY ACCESSING THIS ROUTE ! ! ! !
// need to implement tokens / auth solution
  // example to pull JSON data http://yourserver:8181/api/v1/users
var expressMongoRest = require('express-mongo-rest');
//app.use('/api/v1', expressMongoRest('mongodb://localhost:27017/excap'));

 

Disclaimer

This ExCap server is intended as a proof of concept and comes with no guarantee. Please feel free to use, abuse or contribute to this code. Feedback is appreciated!

Gallery

 

This slideshow requires JavaScript.

Images of The Brick & Tun

Enjoy and leave comments!