Skip to content
Developerhome
X3

Generate tokens from code

  Less than to read

Before starting this step, please read the Authorization and Token Management topic, especially about the Exchange the authorization code for tokens.

First, you create the handlebars template for the pairing request callback.handlebars in views folder:

    <div id="alertMessage" class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
        <span id="alertMessageContent">{{message}}</span>
    </div>

This html template is used in case you want to display an error returned by the API Gateway. Then, you create the callbackHandler.js in lib folder:

    exports.handleCallback = function(req, res, bgColor, buttonColor) {
        if(req.query.error && req.query.statusCode) {
            res.render("callback", { bgColor: bgColor, btColor: buttonColor, message: req.query.error});
        } else if(req.query.code) {
            res.redirect('/generateAccessToken?code=' + req.query.code);
        } else {
            res.redirect('/');
        }
    };

This javascript code display any error returned from the API Gateway or redirect the user to new page to generate tokens from the code returned from API Gateway.

Next, you add the handlebars template for token management accessToken.handlebars in views folder:

    <h2>Generate access token</h2>

    <div id="alertMessage" class="alert alert-warning alert-dismissible fade show " style="display:none" role="alert">
        <span id="alertMessageContent"></span>
        <button type="button" class="close" onclick="$('#alertMessage').hide()">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>
    <div id="successMessage" class="alert alert-success alert-dismissible fade show " style="display:none" role="alert">
        <span id="successMessageContent"></span>
        <button type="button" class="close" onclick="$('#successMessage').hide()">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>

    <div class="input-group mb-3">
        <div class="input-group-prepend w-12-c">
            <span class="input-group-text w-100">{{inputTitle}}</span>
        </div>
        <input type="text" class="form-control" id="access_code_or_refresh_token" value="{{code}}">
    </div>

    <div id="tokenDetails" class="alert details-box alert-dismissible fade show " style="display:none" role="alert">
        <span id="tokenDetailsContent"></span>
    </div>

    <button type="button" class="btn btn-secondary" onclick="generateAccessToken({{refresh}});">Fetch token</button>
    <script type="text/javascript" src="./js/tokenHelper.js"></script>

This template has the code returned by API Gateway as input parameter. It has button to generate tokens from that code. Then you add tokenHelper.js in js folder to implement client side call to token generation API on the same server.

function generateAccessToken(refresh)
    {
        $('#alertMessage').hide();
        $('#successMessage').hide();
        var request = new XMLHttpRequest();

        var path;

        if (refresh) {
            path = '/api/refreshAccessToken';
        } else {
            path = '/api/initiateAccessToken';
        }
        console.log(path);
        request.open('POST', path, true);
        let sessionId = localStorage.getItem('sessionId');
        var data = {
            "access_code_or_refresh_token" : document.getElementById('access_code_or_refresh_token').value,
            "sessionId": sessionId
        };

        request.setRequestHeader("content-type","application/json");
        request.send(JSON.stringify(data));
        request.onreadystatechange = function() {

            if (request.readyState == 4 && (request.status == 200 || request.status == 0)) {
                var result = JSON.parse(request.responseText);
                $('#successMessageContent').html("access_token : " + result.access_token
                    + "<br />scope : " + result.scope
                    + "<br />token_type : " + result.token_type
                    + "<br />expires_in : " + result.expires_in
                    + "<br />refresh_token : " + result.refresh_token
                    + "<br />refresh_token_expires_in : " + result.refresh_token_expires_in
                    + "<br />customer_id : " + result.customerId)

                localStorage.setItem('token', JSON.stringify({ refresh_token: result.refresh_token }));
                if (refresh) {
                    document.getElementById('access_code_or_refresh_token').value = result.refresh_token;
                }
                $('#successMessage').show();
                loadCurlCommands();
            } else if (request.readyState == 4 ) {
                var result = JSON.parse(request.responseText);
                $('#alertMessageContent').text(result.message);
                $('#alertMessage').show();
                loadCurlCommands();
            }
        };
    }

The API call need to be intercepted, therefore we implement the server side interceptor: you create the tokenHandler.js in lib folder:

    let request = require('./requestLib');
    let jwt = require('jsonwebtoken');

    let token = {
        access_token: "",
        refresh_token: ""
    };

    exports.decodeCurrentToken = function (req, res) {
        res.send(jwt.decode(req.body.token));
    };

    exports.accessToken = function() {
        return token.access_token;
    };
    exports.refreshToken = function() {
        return token.refresh_token;
    };
    exports.refreshTokenExpiresIn = function() {
        return token.refresh_token_expires_in;
    };
    exports.initiate = async function(req, res, bgColor, buttonColor) {
        let redirectUrl = process.env.REDIRECT_URL ? process.env.REDIRECT_URL : process.env.ENV_URL + "/callback";
        let data = {
                code: req.body.access_code_or_refresh_token,
                client_id: process.env.CLIENT_ID,
                client_secret: process.env.CLIENT_SECRET,
                grant_type: "authorization_code",
                redirect_uri: redirectUrl
            };

        console.log(process.env.ENDPOINT_URL + "/api/token" + data);


        let response = await  request.post(req, {headers: {'content-type' : 'application/json'}, url:process.env.ENDPOINT_URL + "/token", body: JSON.stringify(data)});

            if (response && response.statusCode) {
                console.log(response.statusCode);
                if (response.statusCode === 201) {
                    let result = JSON.parse(response.body);
                    token = result;
                    res.send(result);
                } else {
                    let message = "An error occurred";
                    let result = JSON.parse(response.body);
                    if (result && result.message) {
                        message = result.message;
                    }
                    res.status(400).send('{"message": "' + message + '"}');
                }
            }

    };

and requestLib.js in lib folder (this library is an improved version of the original request library by adding the follow redirection option and display on screen the equivalent in cURL command of the last application API call) :

    let request = require('request');

    let curlCommands = [];

    exports.getCurlCommands = function(req, res) {
        let id = getSessionId(req);
        if (!id) {
            res.send([]);
            return;
        }
        if (!curlCommands[id]) {
            res.send([]);
            return;
        }
        res.send(curlCommands[id]);
    };

    exports.get = async function(req, context, disableFollowRedirect = false) {
        return await doGetRequest(req, context, disableFollowRedirect);
    };

    exports.post = async function(req, context, disableFollowRedirect = false) {
        return await doPostRequest(req, context, disableFollowRedirect);
    };

    exports.put = async function(req, context, disableFollowRedirect = false) {
        return await doPutRequest(req, context, disableFollowRedirect);
    };


    exports.delete = async function(req, context, disableFollowRedirect = false) {
        return await doDeleteRequest(req, context, disableFollowRedirect);
    };


    let doPostRequest = function (req, context, disableFollowRedirect) {
        if (disableFollowRedirect) {
            context.followRedirect = false;
        }
        return new Promise(function (resolve, reject) {
            generateCurlCommand(req,"post", context);
            request.post(context, function (error, response, body) {
                if (!error) {
                    if (response.statusCode === 301 && disableFollowRedirect) {
                        context.url = response.headers.location;
                        console.log("Redirect to " + context.url);
                        doPostRequest(context).then((response, error) => {
                            console.log(response);
                            if (error) {
                                reject(error);
                            } else {
                                resolve({statusCode: response.statusCode, body: response.body});
                            }
                        });
                    } else {
                        resolve({statusCode: response.statusCode, body: response.body});
                    }
                } else {
                    reject(error);
                }
            });
        });
    };

    let doPutRequest = function (req, context, disableFollowRedirect) {
        if (disableFollowRedirect) {
            context.followRedirect = false;
        }
        return new Promise(function (resolve, reject) {
            let curlCommand = generateCurlCommand(req, "put", context);
            request.put(context, function (error, response, body) {
                if (!error) {
                    if (response.statusCode === 301 && disableFollowRedirect) {
                        context.url = response.headers.location;
                        console.log("Redirect to " + context.url);
                        doPostRequest(context).then((response, error) => {
                            console.log(response);
                            if (error) {
                                reject(error);
                            } else {
                                resolve({statusCode: response.statusCode, body: response.body});
                            }
                        });
                    } else {
                        resolve({statusCode: response.statusCode, body: response.body});
                    }
                } else {
                    reject(error);
                }
            });
        });
    };


    let doGetRequest = function (req, context, disableFollowRedirect) {
        if (disableFollowRedirect) {
            context.followRedirect = false;
        }

        return new Promise(function (resolve, reject) {

            let curlCommand = generateCurlCommand(req, "get", context);
            request.get(context, function (error, response, body) {
                if (!error) {
                    if (response.statusCode === 301 && disableFollowRedirect) {
                        context.url = response.headers.location;
                        console.log("Redirect to " + context.url);
                        doGetRequest(context, disableFollowRedirect).then((response, error) => {
                            console.log(response);
                            if (error) {
                                reject(error);
                            } else {
                                resolve({statusCode: response.statusCode, body: response.body});
                            }
                        });
                    } else {
                        resolve({statusCode: response.statusCode, body: response.body});
                    }
                } else {
                    reject(error);
                }
            });
        });
    };

    let doDeleteRequest = function (req, context, disableFollowRedirect) {
        if (disableFollowRedirect) {
            context.followRedirect = false;
        }

        return new Promise(function (resolve, reject) {

            let curlCommand = generateCurlCommand(req, "delete", context);
            request.delete(context, function (error, response, body) {
                if (!error) {
                    if (response.statusCode === 301 && disableFollowRedirect) {
                        context.url = response.headers.location;
                        console.log("Redirect to " + context.url);
                        doGetRequest(context, disableFollowRedirect).then((response, error) => {
                            console.log(response);
                            if (error) {
                                reject(error);
                            } else {
                                resolve({statusCode: response.statusCode, body: response.body});
                            }
                        });
                    } else {
                        resolve({statusCode: response.statusCode, body: response.body});
                    }
                } else {
                    reject(error);
                }
            });
        });
    };

    let generateCurlCommand = function (req, method, context) {
        let curlCommand = "curl ";

        if (context.headers) {
            for(let propt in context.headers){
                curlCommand += "-H \"" + propt + ": " + context.headers[propt] + "\" ";
            }

        }
        if (context.body) {
            curlCommand += "-d '" + context.body + "' "
        }
        switch (method) {
            case "post":
                curlCommand += "-X POST ";
                break;
            case "delete":
                curlCommand += "-X DELETE ";
                break;
            case "put":
                curlCommand += "-X PUT ";
                break;
        }

        curlCommand += "\"" + context.url + "\"";

        addCurlCommand(req, curlCommand);
    };

    let addCurlCommand = function (req, curlCommand) {
        let id = getSessionId(req);
        if (!id) {
            return;
        }

        if (!curlCommands[id]) {
            curlCommands[id] = [];
        }
        curlCommands[id].splice(0, 0, curlCommand);
        if (curlCommands[id].length > 10) {
            curlCommands[id].pop();
        }

    };

    let getSessionId = function (req) {
        if (req.body && req.body.sessionId) {
            return req.body.sessionId;
        }

        if (req.query && req.query.sessionId) {
            return req.query.sessionId;
        }
    };

Then add new path of your application to main.js :

    const express = require('express');
    const exphbs = require("express-handlebars");
    const fileUpload = require("express-fileupload");
    const app = express();
    const bodyParser = require('body-parser');
    const fs = require('fs')
    const https = require('https')

    const config = require('./lib/config.js');
    const callbackHandler = require('./lib/callbackHandler');
    const tokenHandler = require('./lib/tokenHandler');
    const port = 5050;

    let envColor = process.env.COLOR;
    let bgColor;
    let buttonColor;
    switch (envColor) {
        case "red":
            bgColor = "danger";
            buttonColor = "warning";
            break;
        case "grey":
            bgColor = "dark";
            buttonColor = "secondary";
            break;
        default:
            bgColor = "primary";
            buttonColor = "info";
            break;
    }

    app.use('/', express.static('app'));
    app.use(bodyParser.json());       // to support JSON-encoded bodies
    app.use(fileUpload());


    app.get('/config', function (req, res) {
        config.get(req, res);
    });

    app.get('/settings', function (req, res) {
        res.render("settings", {bgColor: bgColor, btColor: buttonColor});
    });

    let redirectUrl = process.env.REDIRECT_URL ? process.env.REDIRECT_URL : process.env.ENV_URL + "/callback";
    let urlParams = process.env.ENDPOINT_URL + "/token/authorise?client_id=" + process.env.CLIENT_ID + "&state=astate&redirect_uri=" + redirectUrl;
    app.get('/pairingRequest', function (req, res) {
        res.render("pairingRequest", {bgColor: bgColor, btColor: buttonColor, url: urlParams, config_url: process.env.CONFIG_URL});
    });

    app.get('/callback', function (req, res) {
        callbackHandler.handleCallback(req, res, bgColor, buttonColor);
    });

    app.post('/api/initiateAccessToken', async function (req, res) {
        tokenHandler.initiate(req, res);
    });

    app.get('/generateAccessToken', function (req, res) {
        res.render("accessToken", {
            bgColor: bgColor,
            btColor: buttonColor,
            code: req.query.code,
            refresh: false,
            inputTitle: "Access code"
        });
    })

    app.engine('handlebars', exphbs({defaultLayout: 'main', layoutsDir: 'views/layouts'}));
    app.set('view engine', 'handlebars');
    app.set('views', 'views');

    https.createServer({
        key: fs.readFileSync('server.key'),
        cert: fs.readFileSync('server.cert')
    }, app).listen(port, function () {
    });

  • In the end of this step we have this file structure:

    Step 2

In VSCode, launch the debug to see the result at https://localhost:5050/generateAccessToken.

Step 2

Fill up the X3 URL and choose the API scope as in the previous screenshot then click “Create pairing request”. You will be guided to log in and accept the pairing request. Once you accept the pairing request, you will be redirected back to the application with the code:

Step 2

Then click on “Fetch token”

Step 2

Congratulation, you obtain your access and refresh token.

You can also download the VSCode project at the end of this step here. Please note, after downloading the zip file, you have to run npm i to install all package dependencies before debugging with VSCode.