Show login screen when signing in
This example demonstrates a flow where you will always present a sign-in screen to the visitor. This makes sense in cases where you can’t re-use an existing authentication for your Docs site visitors, such as a case where you don’t have an existing experience that your visitors sign in to.
Overview
We will implement a simple endpoint on http://localhost:3000/signin. This URL is what we will use when configuring site restrictions later on. When the visitor tries to access the restricted site, they will be redirected to this URL.
The code for this example can be found on GitHub.
Project setup
To begin, we will create a new project using npm
:
mkdir custom-callback-login-example
cd custom-callback-login-example
npm init -y
This will create a new NPM project in a folder named custom-callback-login-example
.
Open package.json
in your code editor and add the following to the scripts
section:
...
"scripts": {
"start": "nodemon index.js"
}
...
Install dependencies
We will start by adding a few dependencies that will make building the endpoint much easier.
- ExpressJS - Web application framework to ease the implementation of an HTTP endpoint.
- jsonwebtoken - One of the implementations of JWT for NodeJS. See jwt.io for libraries for most languages.
- nodemon - Makes development easier as it automatically restarts your web server whenever you make updates to the code.
npm install --save express jsonwebtoken
npm install --save-dev nodemon
Create sign-in page
We will start by implementing the sign-in page that visitors will see when redirected from your Docs site. Create a new file called index.js
and add the following to it:
const express = require('express')
const app = express()
const port = 3000
app.get('/signin', (req, res) => {
// Help Scout will always include the path that the visitor was requesting
// as a query parameter
const returnTo = req.query.returnTo
// This could be a redirect to your already existing sign-in page
// In this example we render a simple HTML sign-in page
res.send(`
<html>
<head><title>Sign in</title></head>
<body>
<form method="post" action="/signin">
<input type="hidden" name="returnTo" value="${returnTo}" />
<p>
Email:<br />
<input type="text" name="email" required />
</p>
<p>
Password:<br />
<input type="password" name="password" required />
</p>
<p>
<input type="submit" value="Sign in" />
</p>
</form>
</body>
</html>
`)
})
app.listen(port, () => {
console.log(`Server listening on port ${port}`)
})
This will expose a GET endpoint on /signin
that shows a simple HTML form.
You can see this in action by starting the server using this command:
npm start
and opening http://localhost:3000/signin in a browser.
Handle sign-in form submission
Before adding the code for handling the form submission, we will add code that handles the authentication of the visitors:
const isValidCredentials = (email, password) => {
// Here you can integrate into your user backend to validate the credentials
// For now we just do a simple implementation to show the flow
return email === 'john@example.com' && password === '12345678'
}
In our example, we keep it simple but feel free to try to use a real user backend that can verify the credentials instead.
Next, we will add the code for creating the JSON Web Token (JWT). This code will use the shared secret that you get when enabling the site restriction through the Docs API.
const jwt = require('jsonwebtoken')
const sharedSecret = 'ENTER SHARED SECRET HERE'
const createToken = email => {
const tokenPayload = {
// Expires in 1 minute, resulting in a new call to /signin
// In your own implementation you will likely want a longer expiration
exp: Math.floor(Date.now() / 1000) + (60),
sub: email,
}
// Create signed JSON Web Token
return jwt.sign(tokenPayload,
sharedSecret,
{algorithm: 'HS512'})
}
We have some recommendations for what to include as claims in the token which can be seen here. We use the HS512 algorithm for generating and verifying signatures.
Lastly, we want to implement the form submission code. Here we need the base URL for the restricted Docs site so we can redirect back to it.
const docsSiteUrl = 'ENTER BASE URL FOR YOUR RESTRICTED DOCS SITE'
app.use(express.urlencoded({extended: true}))
app.post('/signin', (req, res) => {
const returnTo = req.body.returnTo
const email = req.body.email
const password = req.body.password
if (isValidCredentials(email, password)) {
const token = createToken(email)
const redirectUrl = `${docsSiteUrl}/authcallback?token=${token}&returnTo=${returnTo}`
res.redirect(redirectUrl)
} else {
res.send(`
<html>
<body><p>Invalid email and password, got back and try again. (Psst, it's actually "john@example.com" and "12345678" but don't tell anyone!)</p></body>
</html>
`)
}
})
We start by validating the credentials using the function we created earlier on. Then we create a signed token, that we will use in the redirect URL when redirecting back to the Docs site.
We have now implemented the entire authentication endpoint. You can see the full index.js
here.
Testing the flow
To test the flow you can use one of your own sites that you have set up restrictions on. If you want to just try this sample code, we also host an example site that is restricted and we have made the shared secret public for this specific site.
Remember to keep the shared secret private as it guarantees only your endpoint can authenticate site visitors.
Once you have the Docs site URL and the shared secret, you only need to change these two constants:
const docsSiteUrl = 'ENTER BASE URL FOR YOUR RESTRICTED DOCS SITE'
const sharedSecret = 'ENTER SHARED SECRET HERE'
Test against our sample site
We have created an example site that you can find on:
https://restricted-docs-example-site.helpscoutdocs.com/
It is restricted using the following Docs API call:
PUT /v1/sites/[SITE_ID]/restricted
Authorization: Basic [AUTHENTICATION]
Content-Type: application/json
{
"enabled": true,
"authentication": "CALLBACK",
"callbackConfiguration": {
"signInUrl": "http://localhost:3000/signin"
}
}
You can configure your local instance by setting the two constants to these values:
const docsSiteUrl = 'https://restricted-docs-example-site.helpscoutdocs.com'
const sharedSecret = 'pQKpsnVo3TS7efPYC9FcSRoCCyB3aI+MYpI+omktNzE='