Security is hard. Often when we build applications we want to allow only registered users to access the application. We want to be able to manage user accounts, see when they last logged in, be able to disable suspicious accounts and have a dashboard to view and manage all this data. We might also decide to support multi-factor authentication and social login.
But security isn’t just hard, it also takes a while to implement. What if there’s a service that could take away this part of the development hassle from you? Why spend weeks or months rolling your own auth? This is where Auth0 shines. In this tutorial, I’ll show you how to build a chat application with Pusher, add user authentication with Auth0 Lock, and manage users from the Auth0 dashboard.
Auth0 is an Authentication-as-a-Service (or Identity-as-a-Service) provider focused on encapsulating user authentication and management, which provides an SDK to allow developers to easily add authentication and manage users. Its user management dashboard allows for breach detection and multifactor authentication, and Passwordless login.
We will be building a chat application that’ll allow users to communicate with each other where everyone sees every other person’s messages. It’ll work similarly to how channels work in Slack: just one channel for everybody to communicate.
Here’s what we’ll be building:
We’ll start by building the backend which will facilitate receiving and broadcasting chat messages, serving static files, and also setting up Auth0 and Pusher.
First, you’ll need to signup for a Pusher and Auth0 account. Go to pusher.com and auth0.com and sign up for an account. To use Pusher API we have to signup and create a Pusher app from the dashboard. We can create as many applications as we want and each one will get an application id and secret key which we’ll use to initialise a Pusher instance on client or server side code.
To create a new Pusher app, click the Your apps side menu, then click the Create a new app button below the drawer. This brings up the setup wizard.
Create App
button to set up your app instance.Since we’re building our backend in Node using Express, let’s initialise a new Node app and install the needed dependencies. Run the following command:
Add a new file called server.js
which will contain logic to authenticate the Pusher client and also render the static files we’ll be adding later. This file will contain the content below:
1var express = require('express'); 2 var bodyParser = require('body-parser'); 3 var Pusher = require('pusher'); 4 5 var app = express(); 6 app.use(bodyParser.json()); 7 app.use(bodyParser.urlencoded({ extended: false })); 8 9 var pusher = new Pusher({ appId: APP_ID, key: APP_KEY, secret: APP_SECRET, cluster: eu }); 10 11 app.post('/pusher/auth', function(req, res) { 12 var socketId = req.body.socket_id; 13 var channel = req.body.channel_name; 14 var auth = pusher.authenticate(socketId, channel); 15 res.send(auth); 16 }); 17 18 app.post('/message', function(req, res) { 19 var message = req.body.message; 20 var name = req.body.name; 21 pusher.trigger( 'private-chat', 'message-added', { message, name }); 22 res.sendStatus(200); 23 }); 24 25 app.get('/',function(req,res){ 26 res.sendFile('/public/index.html', {root: __dirname }); 27 }); 28 29 app.use(express.static(__dirname + '/public')); 30 31 var port = process.env.PORT || 5000; 32 app.listen(port, function () { 33 console.log(`app listening on port ${port}!`) 34 });
We instantiate Pusher by passing in an object that contains the details of our app ID and secret key, which can be found on the App Keys tab in your Pusher dashboard. Pusher also provides a mechanism for authenticating users to a channel at the point of subscription. To do this, we expose an endpoint on the server that will validate the request and respond with a success or failure. This endpoint will be called by Pusher client libraries and can be named anything. We used the default name for this endpoint on Pusher, which is /pusher/auth
. The line var auth = pusher.authenticate(socketId, channel);
authenticates the client with Pusher and returns an authentication code to the calling client.
To allow this file to run when we start npm, we update package.json with the following value:
1"scripts": { 2 "start": "node server.js", 3 "test": "echo \"Error: no test specified\" && exit 1" 4 }
To create an Auth0 client
An Auth0 client provides us with Client Id and Secret which we’ll use to interact with Auth0 from the code. On the settings tab, we can see the Name, Client Id, Secret, Client Type and many more. I want to enable CORS for my domain http://localhost:5000, set the log out URL and the URL to redirect to after the user has been authenticated with Auth0. Update the following settings with http://localhost:5000
With the backend all good to go, we build the web page that will facilitate messaging. Create a folder named public which will contain the html and javascript file. Create two new files style.css and index.html with the following content:
1**style.css** 2 3 @import url("http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"); 4 .chat 5 { 6 list-style: none; 7 margin: 0; 8 padding: 0; 9 } 10 .chat li 11 { 12 margin-bottom: 10px; 13 padding-bottom: 5px; 14 border-bottom: 1px dotted #B3A9A9; 15 } 16 .chat li.left .chat-body 17 { 18 margin-left: 60px; 19 } 20 .chat li.right .chat-body 21 { 22 margin-right: 60px; 23 } 24 25 .chat li .chat-body p 26 { 27 margin: 0; 28 color: #777777; 29 } 30 .panel .slidedown .glyphicon, .chat .glyphicon 31 { 32 margin-right: 5px; 33 } 34 .body-panel 35 { 36 overflow-y: scroll; 37 height: 250px; 38 } 39 ::-webkit-scrollbar-track 40 { 41 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 42 background-color: #F5F5F5; 43 } 44 ::-webkit-scrollbar 45 { 46 width: 12px; 47 background-color: #F5F5F5; 48 } 49 ::-webkit-scrollbar-thumb 50 { 51 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 52 background-color: #555; 53 }
1**index.html** 2 3 <!-- template from http://bootsnipp.com/snippets/6eWd --> 4 <!DOCTYPE html> 5 <html> 6 <head> 7 <!-- Latest compiled and minified CSS --> 8 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> 9 <!-- Optional theme --> 10 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> 11 <script 12 src="https://code.jquery.com/jquery-2.2.4.min.js" 13 integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" 14 crossorigin="anonymous"></script> 15 <!-- Latest compiled and minified JavaScript --> 16 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> 17 <link rel="stylesheet" href="style.css"> 18 <script src="https://cdn.auth0.com/js/lock/10.18.0/lock.min.js"></script> 19 <script src="https://js.pusher.com/4.0/pusher.min.js"></script> 20 <script src="index.js"></script> 21 </head> 22 <body> 23 <div class="container"> 24 <div class="row form-group"> 25 <div class="col-xs-12 col-md-offset-2 col-md-8 col-lg-8 col-lg-offset-2"> 26 <div class="panel panel-primary"> 27 <div class="panel-heading"> 28 <span class="glyphicon glyphicon-comment"></span> <span id="username"></span> 29 <div class="btn-group pull-right"> 30 <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown"> 31 <span class="glyphicon glyphicon-chevron-down"></span> 32 </button> 33 <ul class="dropdown-menu slidedown"> 34 <li><a><span class="glyphicon glyphicon-refresh"> 35 </span>Refresh</a></li> 36 <li><a><span class="glyphicon glyphicon-ok-sign"> 37 </span>Available</a></li> 38 <li><a><span class="glyphicon glyphicon-remove"> 39 </span>Busy</a></li> 40 <li><a><span class="glyphicon glyphicon-time"></span> 41 Away</a></li> 42 <li class="divider"></li> 43 <li><a id="logout"><span class="glyphicon glyphicon-off"></span> 44 Sign Out</a></li> 45 </ul> 46 </div> 47 </div> 48 <div class="panel-body body-panel"> 49 <ul class="chat"> 50 51 </ul> 52 </div> 53 <div class="panel-footer clearfix"> 54 <textarea id="message" class="form-control" rows="3"></textarea> 55 <span class="col-lg-6 col-lg-offset-3 col-md-6 col-md-offset-3 col-xs-12" style="margin-top: 10px"> 56 <button class="btn btn-warning btn-lg btn-block" id="btn-chat">Send</button> 57 </span> 58 </div> 59 </div> 60 </div> 61 </div> 62 </div> 63 <script id="new-message" type="text/template"> 64 <li id="{{id}}" class="right clearfix"> 65 <div class="chat-body clearfix"> 66 <div class="header"> 67 <small class="text-muted">{{name}}</small> 68 </div> 69 <p> 70 {{body}} 71 </p> 72 </div> 73 </li> 74 </script> 75 </body> 76 </html>
This file uses template from bootsnip and also includes a script reference to Auth0 Lock <script src="https://cdn.auth0.com/js/lock/10.18.0/lock.min.js"></script>
. Lock is a drop-in authentication widget that provides a standard set of behaviours required for and a customisable user interface. It provides a simple way to integrate with Auth0 with very minimal configuration.
We want to allow users to sign in when they enter the application and be able to send messages once they’re authenticated. Add a new file index.js with the following content:
1$(document).ready(function(){ 2 // Initiating our Auth0Lock 3 let lock = new Auth0Lock( 4 'CLIENT_ID', 5 'CLIENT_DOMAIN',//example: lotus.auth0.com 6 { 7 auth: { 8 params: { 9 scope: 'openid profile' 10 } 11 }, 12 autoclose: true, 13 closable: false, 14 rememberLastLogin: true 15 } 16 ); 17 18 let profile = JSON.parse(localStorage.getItem('profile')); 19 let isAuthenticated = localStorage.getItem('isAuthenticated'); 20 21 function updateValues(userProfile, authStatus) { 22 profile = userProfile; 23 isAuthenticated = authStatus; 24 } 25 26 if(!isAuthenticated && !window.location.hash){ 27 lock.show();//show Lock widget 28 } 29 30 // Listening for the authenticated event 31 lock.on("authenticated", function(authResult) { 32 // Use the token in authResult to getUserInfo() and save it to localStorage 33 lock.getUserInfo(authResult.accessToken, function(error, profile) { 34 if (error) { 35 // Handle error 36 return; 37 } 38 39 localStorage.setItem('accessToken', authResult.accessToken); 40 localStorage.setItem('profile', JSON.stringify(profile)); 41 localStorage.setItem('isAuthenticated', true); 42 updateValues(profile, true); 43 $("#username").html(profile.name); 44 }); 45 }); 46 });
We initialise Lock by passing it the Client Id of the app, your user domain which starts with your username followed by .auth0.com
or .{YOUR_SELECTED_REGION}.auth0.com
e.g lotus.eu.auth0.com
. The widget is configurable and we can send in configuration options like closable, autoClose, and auth. Within the auth option we tell it to return the openid
and profile
claims.
We check if the user is authenticated and show the widget when they’re not. Once the user is authenticated, Lock emits the authenticated
event which we’ve subscribed to. When it’s raised, we store the user profile and other credentials to localStorage and set the user’s name to be displayed on the page. Once the user is authenticated, we want to connect to Pusher and send messages across. Update index.js with the following code:
1if(!isAuthenticated && !window.location.hash){ 2 lock.show(); 3 } 4 else{ 5 6 // Enable pusher logging - don't include this in production 7 Pusher.logToConsole = true; 8 9 var pusher = new Pusher('APP_SECRET', { 10 cluster: 'e.g eu', 11 encrypted: false 12 }); 13 14 var channel = pusher.subscribe('private-chat'); 15 channel.bind('message-added', onMessageAdded); 16 } 17 18 function onMessageAdded(data) { 19 let template = $("#new-message").html(); 20 template = template.replace("{{body}}", data.message); 21 template = template.replace("{{name}}", data.name); 22 23 $(".chat").append(template); 24 }
Pusher is initialised with the APP_SECRET and CLUSTER which you can get from the app dashboard on Pusher. We subscribe to a channel called private-chat
. Pusher has 3 types of channels: Public, Private and Presence channel. Private and Presence channels let your server control access to the data you are broadcasting. Presence channels go further to force subscribers to register user information when subscribing. Private channels are named starting with private-
and authenticated in the server when subscribing.
And finally we want to send the message to the user when they click send and also log them out when they select signout. Update index.js with the code below
1$('#btn-chat').click(function(){ 2 const message = $("#message").val(); 3 $("#message").val(""); 4 //send message 5 $.post( "http://localhost:5000/message", { message, name: profile.name } ); 6 }); 7 8 $("#logout").click((e) => { 9 e.preventDefault(); 10 logout(); 11 }); 12 13 function logout(){ 14 localStorage.clear(); 15 isAuthenticated = false; 16 lock.logout({ 17 returnTo: "http://localhost:5000" 18 }); 19 }
When the user clicks the send button, we take the message and put it in an object with the user’s profile name and send it to the /message
endpoint on the server. When the logout button is clicked, it calls the logout function which clears the data stored in localStorage and call lock.logout()
which logs the user out on Auth0 and redirects them back to our website. With all these additions, index.js should have the following content:
1$(document).ready(function(){ 2 // Initiating our Auth0Lock 3 let lock = new Auth0Lock( 4 'CLIENT_ID', 5 'CLIENT_DOMAIN', 6 { 7 auth: { 8 params: { 9 scope: 'openid profile' 10 } 11 }, 12 autoclose: true, 13 closable: false, 14 rememberLastLogin: true 15 } 16 ); 17 18 // Listening for the authenticated event 19 lock.on("authenticated", function(authResult) { 20 // Use the token in authResult to getUserInfo() and save it to localStorage 21 lock.getUserInfo(authResult.accessToken, function(error, profile) { 22 if (error) { 23 // Handle error 24 console.log(error); 25 return; 26 } 27 28 localStorage.setItem('accessToken', authResult.accessToken); 29 localStorage.setItem('profile', JSON.stringify(profile)); 30 localStorage.setItem('isAuthenticated', true); 31 updateAuthenticationValues(profile, true); 32 $("#username").html(profile.name); 33 }); 34 }); 35 36 let profile = JSON.parse(localStorage.getItem('profile')); 37 let isAuthenticated = localStorage.getItem('isAuthenticated'); 38 39 function updateAuthenticationValues(userProfile, authStatus) { 40 profile = userProfile; 41 isAuthenticated = authStatus; 42 } 43 44 $("#logout").click((e) => { 45 e.preventDefault(); 46 logout(); 47 }); 48 49 function logout(){ 50 localStorage.clear(); 51 isAuthenticated = false; 52 lock.logout({ 53 returnTo: "http://localhost:5000" 54 }); 55 } 56 57 function onMessageAdded(data) { 58 let template = $("#new-message").html(); 59 template = template.replace("{{body}}", data.message); 60 template = template.replace("{{name}}", data.name); 61 62 $(".chat").append(template); 63 } 64 65 if(!isAuthenticated && !window.location.hash){ 66 lock.show(); 67 } 68 else{ 69 if(profile){ 70 $("#username").html(profile.name); 71 } 72 73 // Enable pusher logging - don't include this in production 74 Pusher.logToConsole = true; 75 76 var pusher = new Pusher('APP_SECRET', { 77 cluster: 'eu', 78 encrypted: false 79 }); 80 81 var channel = pusher.subscribe('private-chat'); 82 channel.bind('message-added', onMessageAdded); 83 84 $('#btn-chat').click(function(){ 85 const message = $("#message").val(); 86 $("#message").val(""); 87 //send message 88 $.post( "http://localhost:5000/message", { message, name: profile.name } ); 89 }); 90 } 91 });
To test the app, run npm start
on the terminal and open http://localhost:5000
on two separate browsers. Here’s a run through of it:
This is an app to show how you can use Pusher to send messages in real-time and secure the channels, add user authentication and account management with Auth0, and easily integrate to Auth0 using Auth0 Lock. On your auth0 dashboard you can see the total number of users, logins and new signups.
You can also see all your users when you click on the Users side menu. On this page you can see the list of your users and their mode of login.
Selecting a user takes you to a more detailed page where you can take various actions on the account, for example, blocking an account or sending a verification email.
Also on Pusher, you can go to your application dashboard, under the Stats, where you’ll see statistics concerning your application, such as connection frequency and how many messages were sent through that app. The combination of these two technologies makes it faster and easier to build real-time secured applications.