Upload
cisco-devnet
View
550
Download
4
Embed Size (px)
Citation preview
Building a WiFi Hotspot with NodeJS Cisco Meraki – ExCap API
Cory Guynn, Consulting Systems EngineerDEVNET-2049
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 2DEVNET-2049
Cisco MerakiCloud Managed IT
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 3DEVNET-2049
Captive Portal
SplashBranding, T&Cs, advertising, survey
AuthenticationProcess login
LogStore session and form data
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 4DEVNET-2049
Meraki Splash Page Options
BrandingSurveyT&Cs
Click-through Sign-on
“Splash, agree, have a nice day” “Splash, register/login, have a nice day”
BrandingRADIUS w/ COALogout
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 8DEVNET-2049
Custom Splash URL
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 9DEVNET-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 10DEVNET-2049
Install Sample AppInstall 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 11DEVNET-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 12DEVNET-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 13DEVNET-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 14DEVNET-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 15DEVNET-2049
Click-through Splash Page
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 16DEVNET-2049
Click-through Network Flow
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 17DEVNET-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 18DEVNET-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 19DEVNET-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 20DEVNET-2049
/click// serving the static click-through HTML fileapp.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 21DEVNET-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 22DEVNET-2049
Process Login// handle form submit button and send data to Cisco Meraki - Click-throughapp.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 23DEVNET-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 25DEVNET-2049
Click-through w/Social Login
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 26DEVNET-2049
Code OverviewClick-through with Social OAuth
App
Web Services
Routes
MongoDB
Express
[get] /click
[get] /auth/google
[get] /success
Meraki
HTMLsuccess.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 27DEVNET-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 28DEVNET-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 29DEVNET-2049
/auth/google
// send to google to do the authenticationapp.get('/auth/google', passport.authenticate('google'));
// the callback after google has authenticated the userapp.get('/auth/google/callback', passport.authenticate('google', { successRedirect : '/auth/wifi', failureRedirect : '/auth/google' }));
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 30DEVNET-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
/auth/wifi
// authenticate wireless session with Cisco Merakiapp.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 35DEVNET-2049
Sign-on Splash Page
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 37DEVNET-2049
Code OverviewSign-on
App
HTMLWeb ServicesRoutes
Signon.hbs
MongoDB
Express[get]
/signon
[get] /success
[get] /logout
Meraki
RADIUS
HTML
success.hbslogout.hbs
redirect to/success
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 38DEVNET-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 39DEVNET-2049
/signonapp.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 40DEVNET-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 41DEVNET-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
/logoutapp.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
Resources
Meraki Developers Portalhttp://developers.meraki.com/
Cory GuynnTwitter: @eedionysusEmail: [email protected]
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 46DEVNET-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 47DEVNET-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 48DEVNET-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.
49DEVNET-2049
© 2016 Cisco and/or its affiliates. All rights reserved. Cisco Public 50DEVNET-2049
Continue Your Education• Demos in the Cisco campus
• Walk-in Self-Paced Labs
• Lunch & Learn
• Meet the Engineer 1:1 meetings
• Related sessions