Skip to main content

Authentication

In this step we're going to extend the server implementation so that it can authenticate itself to the APS platform, guide the user through a 3-legged OAuth workflow, and generate access tokens for various needs.

tip

It is a good practice to generate an "internal" token with more capabilities (for example, allowing the owner to create or delete files in the Data Management service) that will only be used by the server, and a "public" token with fewer capabilities that can be safely shared with the client-side logic.

Access tokens

Create an aps.js file under the services folder. This is where we will be implementing all the APS logic that will be used in different areas of our server application. Let's start by adding the following code to the file:

services/aps.js
const APS = require('forge-apis');
const { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_CALLBACK_URL, INTERNAL_TOKEN_SCOPES, PUBLIC_TOKEN_SCOPES } = require('../config.js');

const internalAuthClient = new APS.AuthClientThreeLegged(APS_CLIENT_ID, APS_CLIENT_SECRET, APS_CALLBACK_URL, INTERNAL_TOKEN_SCOPES);
const publicAuthClient = new APS.AuthClientThreeLegged(APS_CLIENT_ID, APS_CLIENT_SECRET, APS_CALLBACK_URL, PUBLIC_TOKEN_SCOPES);

const service = module.exports = {};

service.getAuthorizationUrl = () => internalAuthClient.generateAuthUrl();

service.authCallbackMiddleware = async (req, res, next) => {
const internalCredentials = await internalAuthClient.getToken(req.query.code);
const publicCredentials = await publicAuthClient.refreshToken(internalCredentials);
req.session.public_token = publicCredentials.access_token;
req.session.internal_token = internalCredentials.access_token;
req.session.refresh_token = publicCredentials.refresh_token;
req.session.expires_at = Date.now() + internalCredentials.expires_in * 1000;
next();
};

service.authRefreshMiddleware = async (req, res, next) => {
const { refresh_token, expires_at } = req.session;
if (!refresh_token) {
res.status(401).end();
return;
}

if (expires_at < Date.now()) {
const internalCredentials = await internalAuthClient.refreshToken({ refresh_token });
const publicCredentials = await publicAuthClient.refreshToken(internalCredentials);
req.session.public_token = publicCredentials.access_token;
req.session.internal_token = internalCredentials.access_token;
req.session.refresh_token = publicCredentials.refresh_token;
req.session.expires_at = Date.now() + internalCredentials.expires_in * 1000;
}
req.internalOAuthToken = {
access_token: req.session.internal_token,
expires_in: Math.round((req.session.expires_at - Date.now()) / 1000)
};
req.publicOAuthToken = {
access_token: req.session.public_token,
expires_in: Math.round((req.session.expires_at - Date.now()) / 1000)
};
next();
};

service.getUserProfile = async (token) => {
const resp = await new APS.UserProfileApi().getUserProfile(internalAuthClient, token);
return resp.body;
};

The code provides a couple of helper functions:

  • the getAuthorizationUrl function generates a URL for our users to be redirected to when initiating the 3-legged authentication workflow
  • the authCallbackMiddleware function can be used as an Express.js middleware when the user logs in successfully and is redirected back to our application
  • the authRefreshMiddleware function is then used as an Express.js middleware for all requests that will need to make use of the APS access tokens
  • the getUserProfile function returns additional details about the authenticated user based on an existing access token

Server endpoints

Now let's expose this functionality via a collection of endpoints in our server.

Create an auth.js file under the routes subfolder with the following content:

routes/auth.js
const express = require('express');
const { getAuthorizationUrl, authCallbackMiddleware, authRefreshMiddleware, getUserProfile } = require('../services/aps.js');

let router = express.Router();

router.get('/api/auth/login', function (req, res) {
res.redirect(getAuthorizationUrl());
});

router.get('/api/auth/logout', function (req, res) {
req.session = null;
res.redirect('/');
});

router.get('/api/auth/callback', authCallbackMiddleware, function (req, res) {
res.redirect('/');
});

router.get('/api/auth/token', authRefreshMiddleware, function (req, res) {
res.json(req.publicOAuthToken);
});

router.get('/api/auth/profile', authRefreshMiddleware, async function (req, res, next) {
try {
const profile = await getUserProfile(req.internalOAuthToken);
res.json({ name: `${profile.firstName} ${profile.lastName}` });
} catch (err) {
next(err);
}
});

module.exports = router;

Here we implement a new Express.js router that will handle all the authentication-related endpoints. Let's "mount" the router to our server application by modifying server.js:

server.js
const express = require('express');
const session = require('cookie-session');
const { PORT, SERVER_SESSION_SECRET } = require('./config.js');

let app = express();
app.use(express.static('wwwroot'));
app.use(session({ secret: SERVER_SESSION_SECRET, maxAge: 24 * 60 * 60 * 1000 }));
app.use(require('./routes/auth.js'));
app.listen(PORT, () => console.log(`Server listening on port ${PORT}...`));

The router will now handle the following requests:

  • GET /api/auth/login will redirect the user to the Autodesk login page
  • GET /api/auth/callback is the URL our user will be redirected to after logging in successfully, and it is where we're going to generate a new set of tokens for the user
  • GET /api/auth/logout will remove any cookie-based session data for the given user, effectively logging the user out of our application
  • GET /api/auth/token will generate a public access token that will later be used by the Viewer to load our designs
  • GET /api/auth/profile will return a simple JSON with additional information about the logged in user

Try it out

If the application is still running, restart it (for example, using Run > Restart Debugging, or by clicking the green restart icon), otherwise start it again (using Run > Start Debugging, or by pressing F5).

When you navigate to http://localhost:8080/api/auth/login in the browser, you should be redirected to Autodesk login page, and after logging in, you should be redirected back to your application, for now simply showing Cannot GET /. This is expected as we haven't implemented the GET / endpoint yet. However, if you use browser dev tools and explore the cookies stored by your browser for the localhost origin, you'll notice that the application is already storing the authentication data there.

info

Here's where you can find your website cookies in different browsers:

Empty Response