In this article, you’ll learn how to set up Typeform and capture data from a webhook in the Node.js framework Express.js. You’ll use Passport.js to authenticate a user, use Nexmo’s Node.js Server SDK to register a user, and generate a JWT to use with Nexmo’s JavaScript Client SDK.
You’ll be starting from a pre-built chat application built using Nexmo’s JavaScript Client SDK and Bootstrap.
This tutorial starts from the master branch and ends at the tutorial-finish branch. You can skip to the end by checking out tutorial-finish
and following the README to get up and running quickly.
Prerequisites
Node & NPM
To follow this guide, you’ll need Node.js and NPM installed. This guide uses Node.js 13.1 and NPM 6.12. Check you have stable or long-term support versions of Node.js installed, at least.
node --version
npm --version
If you don’t have Node.js or NPM, or you have older versions, head over to nodejs.org and install the correct version if you don’t have it.
Nexmo Account
Sign up for a free Nexmo account.
Nexmo CLI
To set up your application, you’ll need to install the Nexmo CLI. Install it using NPM in terminal.
npm install -g nexmo-cli@beta
Now, configure the CLI using your API key and secret, found on your Nexmo account dashboard.
nexmo setup <your_api_key> <your_api_secret>
MongoDB
We’ll be storing information in MongoDB. If you don’t have MongoDB installed, follow the correct MongoDB Community Edition installation guide for your system.
Ngrok
Because you’ll be receiving information from a 3rd party, you’ll need to expose the application running on your local machine, but in a safe way. Ngrok is a safe way to use a single command for an instant, secure URL that allows you to access your local machine, even through a NAT or firewall.
Sign up and configure ngrok by following the instructions on their site.
Typeform
You’ll use Typeform to capture input from users, so sign-up now for a free Typeform account.
Email SMTP Provider
You’ll be sending emails. You’ll need the hostname, port, a login and a password for an SMTP provider.
You can use Google Mail to send email from an app.
Git (optional)
You can use git to clone the demo application from GitHub.
If you’re not comfortable with git, this guide also contains instructions on downloading the project as a ZIP file.
Follow this guide to install git
Starting Out
The application you’re starting with is a chat application built using Bootstrap and the Nexmo JavaScript Client SDK. It’s configurable through editing static files, but launched using Express.js, a lightweight Node.js based http server.
Basic Installation
Clone the demo application straight from GitHub.
git clone https://github.com/nexmo-community/nexmo-chat-typeform-magiclinks.git
Or, for those not comfortable with git commands, you can download the demo application as a zip file and unpack it locally.
Once cloned or unpacked, change into the new demo application directory.
cd nexmo-chat-typeform-magiclinks
Install the npm dependencies.
npm install
Installed alongside Node.js is a package called nodemon
, that will automatically reload your server if you edit any files.
Start the application the standard way.
npm start
Start the application, but with nodemon
instead.
npm run dev
Tip: If you’re running the application with
nodemon
for the rest of this tutorial, whenever I suggest restarting the application you won’t need to do that becausenodemon
does it for you. However, if you need to reauthenticate with the application, you will still need to do that, as the session information is stored in memory and not configured to use any other storage.
Whichever way you choose to run the application, once it’s running you can try it out in your favourite browser, which should be able to find it running locally: http://0.0.0.0:3000/.
As the application is unconfigured, you’ll see a very plain empty chat application that you cannot submit messages too. In the real world with error handling, you might show the user a connection error.
But, if you check the browser console now, you’ll just see a Nexmo API error for a missing token. This means the application tried to connect but didn’t provide a user token permitting access the API.
Test ngrok is configured properly, by running ngrok in a separate tab or window to npm
.
ngrok http 3000
You need to run this ngrok
command, and npm
at the same time. This means you need two terminal windows or tabs available, both at the application directory.
Tip: If you need to repeat any quests later, like submitting data from Typeform to the webhook, you can open up ngrok’s web interface at http://127.0.0.1:4040 while it’s running and Replay a request.
One thing to remember is that until you pay for ngrok, your URL will be different every time you start it. Remember this when configuring your Typeform webhook later on. If you stop ngrok, you will need to reconfigure Typeform with the new URL when you start it again.
Tip: If you’re confident with using a tool like Postman or writing manual cURL requests, and once you have your first webhook request from Typeform, you could create a request to be able to repeat that request later.
Get Chatting
In the prerequisites, you setup your CLI using your Nexmo API key and secret. Now, you can run CLI commands to create a Nexmo application, user, conversation, join the user to the conversation and generate a JWT so your user can chat.
Nexmo Configuration
You’ll need to use some of the IDs returned once you’ve ran some of the commands. Keep a note, by copying and pasting your application, conversation, and user IDs.
Create Nexmo Application
This command creates a new Nexmo application with RTC (real-time communication) capabilities. You won’t be capturing the events in your application, so you can provide an example web address for the event URL. The private key will be output to a file path of your choice.
nexmo app:create "Nexmo RTC Chat" --capabilities=rtc --rtc-event-url=http://example.com --keyfile=private.key
# Application created: 4556dbae-bf...f6e33350d8
# Credentials written to .nexmo-app
# Private Key saved to: private.key
Tip: Your application is also output to a config file (
.nexmo-app
) in the directory you ran this command. This means that some further commands from this directory will be relevative to this application, like creating users and conversations.
Create Nexmo Conversation
With an application created, you can create a conversation. The conversation will be what your users join to send messages to and fro.
nexmo conversation:create display_name="Typeform Chatroom"
# Conversation created: CON-a57b0...11e57f56d
Create Your User
Now, create a user. This will be the user you authenticate with. For the moment you just need a user name and display name.
nexmo user:create name=<USER_NAME> display_name=<DISPLAY_NAME>
# User created: USR-6eaa4...e36b8a47f
Add User To Conversation
With your conversation ID and user ID, run this command to join the conversation with your user.
nexmo member:add <CONVERSATION_ID> action=join channel='{"type":"app"}' user_id=<USER_ID>
# Member added: MEM-df772...1ad7fa06
Generate User Token
Use this command to generate a user token in the form of a JWT, usable by the API but also by Nexmo’s JavaScript Client SDK. It will return a JWT for you to use which expires in 24 hours, or 86400 seconds.
nexmo jwt:generate ./private.key sub=<USER_NAME> exp=$(($(date +%s)+86400)) acl='{"paths":{"/*/users/**":{},"/*/conversations/**":{},"/*/sessions/**":{},"/*/devices/**":{},"/*/image/**":{},"/*/media/**":{},"/*/applications/**":{},"/*/push/**":{},"/*/knocking/**":{}}}' application_id=<APPLICATION_ID>
# eyJhbGciOi...XVCJ9.eyJpYXQiOjE1NzM5M...In0.qn7J6...efWBpemaCDC7HtqA
Configure The Application
To configure your application, edit the views/layout.hbs
file and find the JavaScript configuration around line 61.
<script>
var userName = '';
var displayName = '';
var conversationId = '';
var clientToken = '';
</script>
Firstly, configure the application like this, but by the end of the guide you’ll be able to authenticate with a magic link and the clientside application with get your user token from your authorized session.
Edit the config with the values you’ve generated in the commands above.
<script>
var userName = 'luke.oliff@vonage.com';
var displayName = 'Luke Oliff';
var conversationId = 'CON-123...y6346';
var clientToken = 'eyJhbG9.eyJzdWIiO.Sfl5c';
</script>
Now, you can start the application again and start chatting… with yourself… because no one else can log in.
npm start
Creating a Typeform
You can capture as much data as you like from your Typeform. But, for this guide, ensure you have a least an email field on the form.
Once you have created your Typeform, click over to the Connect tab on your Typeform edit page and click on Webhooks.
Click on Add a webhook and enter the URL as https://<your_url>.ngrok.io/webhooks/magiclink
. Then click Save webhook.
Once created, you can go back and add a secret to verify requests reaching your webhook are actually coming from Typeform.
If you complete your Typeform now and submit it while your application is running, the Typeform will receive a 404 Not Found
error and retry. If a webhook request fails for any reason, Typeform will retry the request to your endpoint three times using a back-off mechanism after 5, 10, and 20 minutes.
Environment Variables
From here on in, you’re going to be configuring your application with credentials that not only might differ between environments but also that you won’t want to commit along with your source code.
dotenv
was already a dependency of the starting project, so check out the .env
file where it already contains the default port for the application. You’ll be coming back to this file soon to add more environment variables.
Add a Webhook
Now, to fix your potential 404 Not Found
error, add the webhook by creating a new file in the application called routes/webhook.js
. In the new file, add the following code.
var express = require('express');
var router = express.Router();
/* POST webhook generates a magic link email to the provided email address */
router.post('/magiclink', (req, res, next) => {
console.log(req.body);
// always return a response...
res.sendStatus(200);
});
module.exports = router;
Edit app.js
and add in the webhook router.
// ...
var indexRouter = require('./routes/index');
var webhookRouter = require('./routes/webhook');
// ...
app.use('/', indexRouter);
app.use('/webhooks', webhookRouter);
// ...
With npm and ngrok running you should now be able to complete your Typeform and receive a webhook request. The payload will contain data that looks like this and it will be output in the window where you started the application with npm.
{
...
"form_response": {
...
"answers": [
{
"type": "email",
"email": "email@example.com",
"field": {
"type": "email",
}
}
]
}
}
Capture the Answer
Before editing the webhook, configure some variables for the Typeform and question inside your environment file .env
. For FORM_FIELD_REF
, you’ll need to edit your Typeform question and find the Question reference inside your question settings. FORM_URL
is the public URL to complete the form.
# ... port etc
# typeform config
FORM_URL=https://username.typeform.com/to/123456
FORM_FIELD_TYPE=email
FORM_FIELD_REF=e8bafec6-5...ee-21bfe1254e81
Now, going back to your webhook route at routes/webhook.js
and edit it to include code that will extract the email address.
//...
require('dotenv').config();
/* POST webhook generates a magic link email to the provided email address */
router.post('/magiclink', (req, res, next) => {
// find answers from the typeform response
let { answers } = req.body.form_response;
const answer = answers
.find(answer => process.env.FORM_FIELD_TYPE === answer.type && answer.field.ref === process.env.FORM_FIELD_REF);
// it'll probably be an email
const email = answer[process.env.FORM_FIELD_TYPE];
console.log(email);
// always return a response...
res.sendStatus(200);
});
This code will find an answer of type email
type with the matching Question reference (just in case you capture more than one email address in your form!) and finally returns the value of the answer. The type and reference were set in the .env
file.
The output of this will be the string submitted to the Typeform question.
Store Users
This tutorial will continue to assume you’re only capturing a single email field from Typeform and no further user information. It will store other derived information on the user as it is created.
You’ll use Mongoose for storing your users in the database. Mongoose provides a straight-forward, schema-based solution to model your application data. It includes built-in type casting, validation, query building, business logic hooks and more, out of the box.
Install Mongoose
To capture user creation and details, install mongoose
to your project.
npm install mongoose
Configure MongoDB Connection
Configure the project so that Mongoose will be able to connect to the MongoDB database. This guide uses default MacOS values, which could differ from what you need, all depending on the development environment you’re using.
Edit .env
and add the following configuration.
# ... port and typeform etc
# mongodb config
MONGO_URL=mongodb://127.0.0.1:27017/your-database-name
You can decide your-database-name
here, because it will create it if it doesn’t already exist.
Connect to MongoDB
Now, configure your application to connect to Mongoose when it is run by editing the bin/www
file and placing this code at the end.
// ...
/**
* Database config
*/
const mongoose = require('mongoose');
// Set mongoose promises to global
mongoose.Promise = global.Promise
// Set up default mongoose connection
mongoose.connect(process.env.MONGO_URL, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false });
// Get the default connection
const db = mongoose.connection;
// Bind connection to error event (to get notification of connection errors)
db.on('error', onError);
User Schema and Model
Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection. While MongoDB is Schema-less, Mongoose uses Schema’s to formalise the standard object before modification.
Create a new file for the schema at schemas/user.js
and add the following code.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name: {
type: String,
required: true
},
display_name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
user_id: {
type: String
},
member_id: {
type: String
}
});
module.exports = UserSchema;
A model is what is used to create documents that you can use to create, edit, update and delete items on a MongoDB collection. Create a new file for the model at models/user.js
and add the following code.
const mongoose = require('mongoose');
const UserSchema = require('../schemas/user');
const User = mongoose.model('User', UserSchema);
module.exports = User;
Notice how the model includes the schema to return a User
document.
Finding and Saving Users
In this instance, you’re going to use the email as your users string identifier, or username. Their email address will eventually also become their display name. You could choose to capture both of these things individually on your Typeform if you wished.
Edit routes/webhook.js
and add the following code to find users by their username and create them if they don’t already exist.
// ...
var User = require('../models/user');
/* POST webhook generates a magic link email to the provided email address */
router.post('/magiclink', (req, res, next) => {
// ...
User.findOne({ name: email }, (err, user) => {
// error handling here
// if our user is new, save it and output it
if (null === user) {
user = new User({
name: email,
email: email,
display_name: email
});
user.save((err) => {
// error handling here
console.log(user);
res.sendStatus(200);
});
// otherwise, just output it
} else {
console.log(user);
res.sendStatus(200);
}
});
});
This code is going to attempt to find a user by their email address, creating one if one didn’t already exist. This doesn’t support updating an existing user. If they already existed, you could error. Later, we’ll generate a magic link to login, rather than give them an error.
Generate a Magic Link
Your webhook is going to email your user a magic link that can be used to authenticate them with the service.
Install jsonwebtoken
using npm.
npm install jsonwebtoken
Edit .env
to create a secret key that can be used for token generation.
# ... port etc
SECRET=whatever-you-want-it-be-a-b-c-1-2-3
# ... typeform and mongo etc
So, now edit routes/webhook.js
to generate the magic link and output it to the server.
// ...
var jwt = require('jsonwebtoken');
var createMagicLink = (req, payload) => {
var token = jwt.sign(payload, process.env.SECRET);
return `${req.protocol}://${req.get('host')}/auth?token=${token}`;
}
/* POST webhook generates a magic link email to the provided email address */
router.post('/magiclink', (req, res, next) => {
// ...
// ...
if (null === user) {
// ...
user.save((err) => {
// ...
console.log(createMagicLink(req, user.toObject()));
res.sendStatus(200);
});
// otherwise, just output it
} else {
console.log(createMagicLink(req, user.toObject());
res.sendStatus(200);
}
// ...
});
We’re adding a JWT to a magic link URL as a method for identifying the user when they try to access the site.
It’s important to note that a JWT guarantees data ownership, not encryption. It’s a URL-safe way of representing claims by encoding them as JSON objects which can be digitally signed or encrypted. Digitally signing a JWT allows validation against modifications. Encryption, on the other hand, makes sure the content of the JWT is only readable by certain parties.
In this instance, the guide doesn’t use RSA or other asymmetric encryption, choosing only to sign the data instead using the JWT library’s default HMAC SHA256 synchronous signing.
Using a JWT in this way verifies the magic link originated from your application, signed by your SECRET
and cannot be modified.
When you submit data to the webhook from Typeform now, the output should be a link to the application that looks like a much longer version of this:
https://.ngrok.io/webhooks/auth?token=eyJhbCJ9.eyEflLxN.N9eq6b5o
Click the link for a 404 error. Let’s fix that.
Authenticate with Passport.js
Passport.js describes itself as unobtrusive authentication for Node.js. It is incredibly flexible and modular and can be unobtrusively dropped into an application like this.
Install Passport.js
Install passport
, the passport-jwt
strategy and express-session
so it can be used for authentication and maintaining a session.
npm install passport passport-jwt express-session
Create an Authentication Endpoint
Create a new file named routes/auth.js
with this source code.
var express = require('express');
var router = express.Router();
/* GET authenticate user with magic link and direct to home */
router.get('/', (req, res, next) => {
res.redirect(req.protocol + '://' + req.get('host') + '/');
});
module.exports = router;
This router is going to redirect you to the homepage. You’ll only reach this router, though, if you’re authorised by the JWT when you request the page.
Edit app.js
and add this code to add passport authentication to a new auth route.
// ...
var indexRouter = require('./routes/index');
var webhookRouter = require('./routes/webhook');
var authRouter = require('./routes/auth');
// ...
var User = require('./models/user');
var session = require('express-session');
var passport = require('passport');
var jwtStrategy = require('passport-jwt').Strategy;
var jwtExtractor = require('passport-jwt').ExtractJwt;
app.use(session({
secret: process.env.SECRET,
resave: true,
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
passport.use(new jwtStrategy({
jwtFromRequest: jwtExtractor.fromUrlQueryParameter('token'),
secretOrKey: process.env.SECRET
}, (payload, done) => {
return done(null, payload);
}))
app.use('/', indexRouter);
app.use('/webhooks', webhookRouter);
app.use('/auth', passport.authenticate('jwt', { session: true }), authRouter);
// ...
This code will authenticate any request to the /auth
endpoint using the JWT extractor from passport-jwt
strategy. It will try to validate the token
from a query string parameter.
Once authenticated, the application will create a session and the user data becomes available as req.user
.
To test this, edit routes/index.js
and add this code before the res.render()
line.
console.log(req.user);
Now, restart the application and generate a magic link using your Typeform request. When you click on the link, you’re redirected back to the chat after authentication. But in your console, you’ll have output some user data that looks like this:
{
_id: 5dd0215a03174a4d8b920952,
name: 'luke.oliff@vonage.com',
email: 'luke.oliff@vonage.com',
display_name: 'luke.oliff@vonage.com',
member_id: null,
user_id: null,
__v: 0
}
Make sure no one can access the chat, unless they’re authenticated, by editing the routes/index.js
to look exactly like this.
var express = require('express');
var router = express.Router();
require('dotenv').config();
var isAuthenticated = (req, res, next) => {
if(req.isAuthenticated()){
next();
} else{
res.redirect(process.env.FORM_URL);
}
}
/* GET home */
router.get('/', isAuthenticated, (req, res, next) => {
res.render('index', { title: 'Nexmo Typeform Chat', user: req.user.display_name });
});
module.exports = router;
Removing the console.log output you just added above; the chat will no longer log the current user data to console. Instead, the display name is added to the scope of the templates to render. This change will also redirect to the Typeform if they’re not logged in.
Edit views/layout.hbs
and output the display name. Find username
and replace it with {{user}}
, the surrounding code should end up looking like this.
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="#">
<span data-feather="home"></span>
{{user}}
</a>
</li>
</ul>
When they’re logged in, let’s also show the members of chat (out of the database) on the page. Edit routes/index.js
and wrap the res.render
in the User.find
which returns all the registered users.
// ...
var User = require('../models/user');
// ...
/* GET home */
router.get('/', isAuthenticated, (req, res, next) => {
User.find((err, users) => {
res.render('index', { title: 'Nexmo Typeform Chat', members: users, user: req.user.display_name });
})
});
Edit views/layout.hbs
again and find this entire block:
{{!-- {{#each members}} --}}
<li class="nav-item">
<a class="nav-link text-muted" href="#">
<span data-feather="file-text"></span>
other member
</a>
</li>
{{!-- {{/each}} --}}
Replace it with this functional code.
{{#each members}}
<li class="nav-item">
<a class="nav-link text-muted" href="#">
<span data-feather="file-text"></span>
{{this.display_name}}
</a>
</li>
{{/each}}
Restart the application and access it once again through your magic link. Now, you should see some user information on the page.
You’re still accessing chat on the using the hardcoded test data. It’s time to register your users to Nexmo and let them access the conversation, too.
Get Registered Users Chatting on Nexmo
At the moment you have users signing up but only using the chat through your hardcoded user information.
Install and Configure Nexmo Node
At this point, you’re going to start interacting with the Nexmo service from within your node application for the first time.
Install nexmo
now with this command.
npm install nexmo@beta
Configure some variables for Nexmo inside your environment file .env
. You’ll need the same API key and secret you used to configure nexmo-cli
at the very start. You’ll also need the application ID and private key path from when you ran nexmo app:create
, as well as the conversation ID from when you ran nexmo conversation:create
.
# ... app, typeform and mongodb etc
# nexmo config
NEXMO_API_KEY=<your_api_key>
NEXMO_API_SECRET=<your_api_secret>
NEXMO_APP_ID=4556dbae-bf...f6e33350d8
NEXMO_PRIVATE_KEY_PATH=./private.key
NEXMO_CONVERSATION_ID=CON-a57b0...11e57f56d
Create a utility file at util/nexmo.js
that is going to configure the nexmo
library.
const Nexmo = require('nexmo');
require('dotenv').config();
let options = {};
module.exports = new Nexmo({
apiKey: process.env.NEXMO_API_KEY,
apiSecret: process.env.NEXMO_API_SECRET,
applicationId: process.env.NEXMO_APP_ID,
privateKey: process.env.NEXMO_PRIVATE_KEY_PATH
}, options);
Create Nexmo User
First things first, you need to create a Nexmo user in parallel to your local user when they sign up.
Edit routes/webhook.js
and completely replace the file with this code:
var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
require('dotenv').config();
var User = require('../models/user');
var nexmo = require('../util/nexmo');
var createMagicLink = (req, payload) => {
var token = jwt.sign(payload, process.env.SECRET);
return `${req.protocol}://${req.get('host')}/auth?token=${token}`;
}
/* POST webhook generates a magic link email to the provided email address */
router.post('/magiclink', (req, res, next) => {
// find answers from the typeform response
let { answers } = req.body.form_response;
const answer = answers
.find(answer => process.env.FORM_FIELD_TYPE === answer.type && answer.field.ref === process.env.FORM_FIELD_REF);
// it'll probably be an email
const email = answer[process.env.FORM_FIELD_TYPE];
User.findOne({ name: email }, (err, user) => {
// error handling here
// if we can't find an existing user, prepare a new user document
if (null === user) {
user = new User({
name: email,
email: email,
display_name: email
});
}
if (null === user.user_id) {
nexmo.users.create(user.toObject(), (err, nexmoUser) => {
// error handling here
user.user_id = nexmoUser.id;
nexmo.conversations.members.create(process.env.NEXMO_CONVERSATION_ID, {
action: 'join',
user_id: nexmoUser.id,
channel: { type: 'app' }
}, (err, member) => {
// error handling here
user.member_id = member.id;
user.save((err) => {
// error handling here
console.log(createMagicLink(req, user.toObject()));
res.sendStatus(200);
});
});
});
} else {
console.log(createMagicLink(req, user.toObject()));
res.sendStatus(200);
}
});
});
module.exports = router;
This new webhook code is going to check for a database user and create one where it’s new, just as it had before. But now, it will create a Nexmo user and connect the user to the conversation, updating their database record with the Nexmo user ID and a member ID.
Restart the application and generate a new magic link for your user. Click it to authenticate. It will now see there is no Nexmo user, create one, add it to the conversation, and save it to the user record.
When redirected to the chat application, you’ll now see that your created user has joined the conversation. You’re still chatting as your hardcoded user, though.
Generate a Token for the Client SDK
Your users can sign up, login and even join the conversation. But right now, they’ll only chat using hardcoded user data. It’s time to fix that and allow them to talk as themselves.
Open routes/index.js
and create a new route /jwt
, because primarily you’ll expose a new JWT specifically for the Nexmo service, usable by the Client SDK.
// ...
var nexmo = require('../util/nexmo');
/* GET home */
// ...
/* GET user data and jwt */
router.get('/jwt', isAuthenticated, (req, res, next) => {
const aclPaths = {
"paths": {
"/*/users/**": {},
"/*/conversations/**": {},
"/*/sessions/**": {},
"/*/devices/**": {},
"/*/image/**": {},
"/*/media/**": {},
"/*/applications/**": {},
"/*/push/**": {},
"/*/knocking/**": {}
}
};
const expires_at = new Date();
expires_at.setDate(expires_at.getDate() + 1);
const jwt = nexmo.generateJwt({
application_id: process.env.NEXMO_APP_ID,
sub: req.user.name,
exp: Math.round(expires_at/1000),
acl: aclPaths
});
res.json({
user_id: req.user.user_id,
name: req.user.name,
member_id: req.user.member_id,
display_name: req.user.display_name,
client_token: jwt,
conversation_id: process.env.NEXMO_CONVERSATION_ID,
expires_at: expires_at
});
})
// ...
This new route uses the users existing session to provide data to the browser. The homepage provides this as HTML, but this new endpoint returns JSON.
Restart the application, follow the magic link and then browse to https://<your_url>.ngrok.io/jwt
. You’ll see information based on your current user, including a client_token
to use in the Client SDK.
Remove the Hardcoded Configuration
It is time to stop hardcoding config inside the application. Edit the views/layout.hbs
file, finding the configuration you added inside the <script>
tags. It looked something like this.
<script>
var userName = 'luke.oliff@vonage.com';
var displayName = 'Luke Oliff';
var conversationId = 'CON-123...y6346';
var clientToken = 'eyJhbG9.eyJzdWIiO.Sfl5c';
</script>
Delete the script tags and their contents, totally.
If you want to see what it’s done to your app, restart and authenticate to find that it’s almost back to the very beginning, with broken chat. At least you’re still logged in!
Request User Client Token
You can access the user’s client token from a URL as JSON data. So, edit public/javascripts/chat.js
and change the authenticateUser
method so that it fetches this data, to use it when connecting to the conversation.
// ...
authenticateUser() {
var req = new XMLHttpRequest();
req.responseType = 'json';
req.open('GET', '/jwt', true);
var obj = this;
req.onload = function() {
obj.joinConversation(req.response);
};
req.send(null);
}
// ...
Restart the application, authenticate and enjoy a quick game of spot the difference!
You see, now you’re logged in as a different user. Messages from other users are formatted differently. So when you join in the conversation, it’ll look like this.
Send the Magic Link by Email
You’ve got a magic link, but it is still output in the console. It is time to send that by email instead.
Install and Configure an SMTP Library
Install nodemailer
now with this command.
npm install nodemailer
Configure some variables for the nodemailer
library inside your environment file .env
.
# ... app, typeform, mongodb, nexmo etc
# smtp config
SMTP_HOST=
SMTP_PORT=
SMTP_AUTH_USER=
SMTP_AUTH_PASS=
If you’re using Google or other well known mail host with 2-Step Verification turned on, you’ll probably need to setup an application password. It will let you authenticate from the application without a need for 2-Step Verification.
Create a new utility file that will configure nodemailer
at util/mailer.js
with this code:
const mailer = require('nodemailer');
require('dotenv').config();
let options = {
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
secure: true,
auth: {
user: process.env.SMTP_AUTH_USER,
pass: process.env.SMTP_AUTH_PASS
}
};
module.exports = mailer.createTransport(options);
Send Magic Links by Email
The final edit of routes/webhook.js
will be to add the sendEmail
function and use it to replace the console.log
commands completely.
// ...
var mailer = require('../util/mailer');
// ...
var sendEmail = (magicLink, email) => {
var mailOptions = {
to: email,
subject: 'Magic Link',
text: 'Click to login: ' + magicLink,
html: `<a href="${magicLink}">Click to Login</a>`
};
mailer.sendMail(mailOptions);
}
/* POST webhook generates a magic link email to the provided email address */
router.post('/magiclink', (req, res, next) => {
// ...
if (null === user.user_id) {
// ...
// ...
user.save((err) => {
// ...
sendEmail(createMagicLink(req, user.toObject()), user.email);
res.sendStatus(200);
});
// ...
// ...
} else {
sendEmail(createMagicLink(req, user.toObject()), user.email);
res.sendStatus(200);
}
// ...
});
// ...
For the final type, restart the application and send a webhook request using Typeform data.
With everything working as expected, you’ll receive an email to the address you submitted to Typeform with a magic link. Click the magic link to authenticate with the application and join the conversation.
Time to invite some friends!
That’s all Folks!
Coming soon: a tutorial on styling a chat application using Bootstrap 4.3.
Some things to consider if you’re building this for real-world use:
- Use a separate form to handle authentication after a user has already registed.
- Capture a display name and user image inside your Typeform.
- Use a revokable opaque string instead of a JWT inside a magic link.
- Allow users to update their data once authenticated.
- Show all currently online in the side menu.
- Allow users to sign out.
- Allow users to delete messages.
- Allow users to share media.
- Expand shared URLs as previews.
If you want to enable audio inside an existing chat application like this, you can check out my guide for Adding Voice Functionality to an Existing Chat Application.
Thanks for reading and let me know what you think in the Community Slack or in the comments section below
The post Register to Chat with Typeform appeared first on Nexmo Developer Blog.
Top comments (0)