Building a WiFi Hotspot with NodeJS ExCap...
Transcript of Building a WiFi Hotspot with NodeJS ExCap...
Building a WiFi Hotspot with NodeJSCisco Meraki – ExCap API
Cory Guynn, Consulting Systems Engineer
DEVNET-2049
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 3DEVNET-2049
Cisco MerakiCloud Managed IT
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 4DEVNET-2049
Captive Portal
Splash
Branding, T&Cs, advertising, survey
Authentication
Process login
Log
Store session and form data
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 5DEVNET-2049
Meraki Splash Page Options
Branding
Survey
T&Cs
Click-through Sign-on
“Splash, agree, have a nice day” “Splash, register/login,
have a nice day”
Branding
RADIUS w/
COA
Logout
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 6DEVNET-2049
Meraki Dashboard
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 7DEVNET-2049
Access Control
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 8DEVNET-2049
Walled Garden
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 9DEVNET-2049
Custom Splash URL
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 10DEVNET-2049
NodeJSBuilding the Webservice
• JavaScript with I/O
• Active developer community
• Rich library with NPM (Node Package Modules)
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 11DEVNET-2049
Install Sample App
Install NodeJS
Install MongoDB
Clone source code
git clone https://github.com/dexterlabora/excap.git
or
git clone https://github.com/dexterlabora/excap-social.git
Install dependencies (while in root of the cloned directory)
npm install
Run application
node app.js
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 12DEVNET-2049
NodeJS Required Modules
• express
• Web server framework
• express-session
• Store client session data
• mongodb
• No-SQL database
• handlebars
• HTML template framework
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 13DEVNET-2049
Web Services
Express
var express = require('express')
var app = express()
app.get('/', function (req, res) {
res.send('Hello World')
})
app.listen(3000)
“Fast, unopinionated, minimalist web framework”
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 14DEVNET-2049
Web Services
app.use(require('express-session')({
secret: 'supersecret', // this secret is used to encrypt cookie
cookie: {
maxAge: 1000 * 60 * 60 * 24 // 1 day
},
store: store,
resave: true,
saveUninitialized: true
}));
Express-SessionSession data is not saved in the cookie itself, just the session ID.
Session data is stored server-side.
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 15DEVNET-2049
Web Services
var MongoDBStore = require('connect-mongodb-session')(session);
var store = new MongoDBStore({
uri: 'mongodb://localhost:27017/test',
collection: 'excap'
});
MongoDBStore session data into a No-SQL database
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 16DEVNET-2049
Click-through Splash Page
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 17DEVNET-2049
Click-through Network Flow
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 18DEVNET-2049
Click-through Code FlowApp
Web ServicesRoutes
MongoDB
Express[get]
/click
[post]
/login
[get]
/success
Meraki
HTML
success.
hbs
continue_url
/success
HTML
click-
through.hbs
authbase_grant_url
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 19DEVNET-2049
Click-through ExCap APIMeraki Provided Information
• base_grant_url
• https://n143.network-auth.com/splash/grant
• user_continue_url
• node_mac
• client_ip
• client_mac
• ap_name
• ap_tags
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 20DEVNET-2049
Request from Meraki via client
http://app.internetoflego.com:1880/click
?base_grant_url=https%3A%2F%2Fn143.network-auth.com%2Fsplash%2Fgrant
&user_continue_url=http%3A%2F%2Fwww.ask.com%2F
&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
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 21DEVNET-2049
/click// serving the static click-through HTML file
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 = 'http://' + req.session.host + "/success";
req.session.continue_url = req.query.user_continue_url;
// display session data for debugging purposes
console.log("Session data at click page = " + util.inspect(req.session, false, null));
// render login page using handlebars template and send in session data
res.render('click-through', req.session);
});
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 22DEVNET-2049
Click-through.hbs{{handlebars}}
<div id="continue">
<h1>IoL Cafe</h1>
<p>Please enjoy our complimentary WiFi and a cup of joe.</p>
<p>
Brought to you by <a href="http://www.internetoflego.com" target="blank">InternetOfLego.com</a>
</p>
<form action="/login" method="post" class="form col-md-12 center-block">
<div class="form-group">
<input class="form-control input-lg" placeholder="Email" type="text" name="form1[email]" required>
</div>
<div class="form-group">
<button class="btn btn-primary btn-lg btn-block">Sign In</button>
<span class="pull-left"><a href="#">Terms and Conditions</a></span>
</div>
</form>
</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>
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 23DEVNET-2049
Process Login// handle form submit button and send data to Cisco Meraki - Click-through
app.post('/login', function(req, res){
// save data from HTML form
req.session.form = req.body.form1;
req.session.splashlogin_time = new Date().toString();
// forward request onto Cisco Meraki to grant access
// *** Send user to success page : success_url
res.writeHead(302, {
'Location': req.session.base_grant_url + "? continue_url="+req.session.success_url
});
res.end();
});
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 24DEVNET-2049
Success!Session Log Data
excap-1 Session data at login page = { cookie:
excap-1 { path: '/',
excap-1 _expires: Mon Dec 07 2015 02:11:55 GMT+0000 (UTC),
excap-1 originalMaxAge: 604800000,
excap-1 httpOnly: true,
excap-1 secure: null,
excap-1 domain: null },
excap-1 host: ’127.0.0.1:8181',
excap-1 base_grant_url: 'https://n143.network-auth.com/splash/grant',
excap-1 user_continue_url: 'http://www.google.com/',
excap-1 node_mac: '00:18:0a:13:dd:b0',
excap-1 client_ip: '10.173.154.6',
excap-1 client_mac: 'f8:95:c7:ff:86:27',
excap-1 splashclick_time: 'Mon Nov 30 2015 02:11:54 GMT+0000 (UTC)',
excap-1 _locals: {},
excap-1 form: { email: '[email protected]' },
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 26DEVNET-2049
Click-through w/Social Login
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 27DEVNET-2049
Code OverviewClick-through with Social OAuth
App
Web
Services
Routes
MongoDB
Express
[get]
/click
[get]
/auth/google
[get]
/success
Meraki
HTML
success
.hbs continue_url
/success
HTML
click-
through.hbs
auth
[get]
/auth/wifi
passport strategy
Social OAuth
success callback
/auth/wifi
OAuth
base_grant_url
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 28DEVNET-2049
OAuth
“Simple, unobtrusive authentication for Node.js"
Passport is authentication middleware for Node.js. Extremely flexible and modular,
Passport can be unobtrusively dropped in to any Express-based web application. A
comprehensive set of strategies support authentication using a username and
password, Facebook, Twitter, and more.
http://passportjs.org/
Passport
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 29DEVNET-2049
click-through.hbs
<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>
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 30DEVNET-2049
/auth/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'
})
);
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 31DEVNET-2049
Passport Strategy
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
passport.use(new GoogleStrategy({
clientID : configAuth.googleAuth.clientID,
clientSecret : configAuth.googleAuth.clientSecret,
callbackURL : configAuth.googleAuth.callbackURL,
scope : ['profile', 'email'],
passReqToCallback : true
},
function(req, token, refreshToken, profile, done) {
... SNIP ...
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 32DEVNET-2049
Google API
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 33DEVNET-2049
/auth/wifi
// 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();
});
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 34DEVNET-2049
Google Login
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 36DEVNET-2049
Sign-on Splash Page
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 37DEVNET-2049
Sign-on Flow
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 38DEVNET-2049
Code OverviewSign-on
App
HTMLWeb ServicesRoutes
Signon.h
bs
MongoDB
Express[get]
/signon
[get]
/success
[get]
/logout
Meraki
RADIUS
HTML
success.hbs
logout.hbs
redirect to
/success
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 39DEVNET-2049
ExCap APISign-on
• login_url
• https://n143.network-auth.com/splash/login?mauth=MMtoqbXZbiYvY2dkMWlEV06tIgp9mo6qkQKKcHG-0Oj4kb2bW0Vu4dLljkScAJRft95MSEA0YFLalbkUQtkt0YuL8jr_aRKOORUrbO8r8Vwq4EyRq9kfpkP2usCJL5qXRX7yrUCWtRyW0ryhTzs3lz6Gi2RVENFDo_vukBWh2Dcvso4AAl-mJJ2c8KaEnFlFCYS-gPn4ZhDA8&continue_url=http%3A%2F%2Fconnectivitycheck.android.com%2Fgenerate_204
• continue_url
• node_mac
• client_ip
• client_mac
• ap_name
• ap_tags
• logout_url
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 40DEVNET-2049
/signon
app.get('/signon', function (req, res) {
// extract parameters (queries) from URL
req.session.host = req.headers.host;
req.session.login_url = req.query.login_url;
req.session.continue_url = req.query.continue_url;
req.session.ap_name = req.query.ap_name;
req.session.ap_tags = req.query.ap_tags;
req.session.client_ip = req.query.client_ip;
req.session.client_mac = req.query.client_mac;
req.session.success_url = req.protocol + "://" + req.session.host + "/success"; req.session.signon_time =
new Date();
// render login page using handlebars template and send in session data
res.render('sign-on', req.session);
});
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 41DEVNET-2049
sign-on.hbs{{handlebars}}
<form action={{login_url}} method="post" class="form col-md-12 center-block">
<input type="hidden" name="success_url" value={{success_url}} />
<div class="form-group">
<div class="error">
{{recent_error}}
</div>
<input class="form-control input-lg" type="text" name="username" placeholder="Username or email">
<i class="icon-user icon-large"></i>
</div>
<div class="form-group">
<input class="form-control input-lg" type="password" name="password" placeholder="Password">
<i class="icon-lock icon-large"></i>
</div>
<div class="form-group">
<button class="btn btn-primary btn-lg btn-block">Sign In</button>
<span class="pull-left"><a href="#">Terms and Conditions</a></span>
... snip …
<div class="footer">
<p>Client IP: {{client_ip}}</p>
<p>Client MAC: {{client_mac}}</p>
<p>AP Tags: {{ap_tags}}</p>
<p>AP Name: {{ap_name}}</p>
<p>AP MAC: {{node_mac}}</p>
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 42DEVNET-2049
/success
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 + "&continue_url=" +
req.protocol + "://" + req.session.host + "/logout";
req.session.success_time = new Date();
// render sucess page using handlebars template and send in session data
res.render('success', req.session);
});
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 43DEVNET-2049
Success!
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 44DEVNET-2049
/logout
app.get('/logout', function (req, res) {
// determine session duration
req.session.loggedout_time = new Date();
req.session.duration = {};
req.session.duration.ms = Math.abs(req.session.loggedout_time - req.session.success_time) ;
req.session.duration.sec = Math.floor((req.session.duration.ms/1000) % 60);
req.session.duration.min = (req.session.duration.ms/1000/60) << 0;
// extract parameters (queries) from URL
req.session.host = req.headers.host;
req.session.logout_url = req.query.logout_url + "&continue_url=" + req.protocol + "://" + req.session.host +
"/logged-out";
// render sucess page using handlebars template and send in session data
res.render('logged-out', req.session);
});
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 45DEVNET-2049
Logged Out
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 46DEVNET-2049
Resources
Meraki Developers Portal
http://developers.meraki.com/
Cory Guynn
Twitter: @eedionysus
Email: [email protected]
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 47DEVNET-2049
Resources
• Captive Portal Solution Guide• https://meraki.cisco.com/lib/pdf/meraki_whitepaper_captive_portal.pdf
• Write-ups and source code• ExCap
• http://www.internetoflego.com/wifi-hotspot-cisco-meraki-excap-nodejs/• https://github.com/dexterlabora/excap
• Node-RED version• http://flows.nodered.org/flow/e80275ccd499c2edaf43
• ExCap-Social• http://www.internetoflego.com/wifi-hotspot-with-social-oauth-passport-mongodb/• https://github.com/dexterlabora/excap-social
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 48DEVNET-2049
Install Sample App
Install NodeJS
Install MongoDB
Clone source code
git clone https://github.com/dexterlabora/excap.git
or
git clone https://github.com/dexterlabora/excap-social.git
Install dependencies (while in root of the cloned directory)
npm install
Run application
node app.js
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 49DEVNET-2049
logged-out.hbs
<h1>Logged Out!</h1>
<p>
Total session duration: {{duration.min}} minutes
{{duration.sec}} seconds
</p>
</div>
</div>
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public
Complete Your Online Session Evaluation
Don’t forget: Cisco Live sessions will be available for viewing on-demand after the event at CiscoLive.com/Online
• Give us your feedback to be entered into a Daily Survey Drawing. A daily winner will receive a $750 Amazon gift card.
• Complete your session surveys through the Cisco Live mobile app or from the Session Catalog on CiscoLive.com/us.
50DEVNET-2049
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public
Continue Your Education
• Demos in the Cisco campus
• Walk-in Self-Paced Labs
• Lunch & Learn
• Meet the Engineer 1:1 meetings
• Related sessions
51DEVNET-2049
Thank you