31 May 2021

Proxying Forge Viewer

Default blog image

When building Forge applications with multi-tenancy support or custom authentication, it is important to be able to proxy requests to Forge, for example, to ensure that a specific user can only access a specific OSS object or bucket.

With the standard services such as Data Management or Model Derivative, this is straightforward: you typically expose a custom endpoint in your server that calls the Forge API internally, and you filter the results based on the specific user making the request.

Now what about Forge Viewer? The viewer gets an access token with limited privileges (usually just the viewables:read scope), a base64-encoded URN of a model, and it then loads the model's assets directly from the Model Derivative service. The access token, albeit only useful for viewing, is not limited to a specific object or bucket, though, so if the client could guess the names of other buckets and objects owned by the same Forge application, they could still view their derivatives.

If this is a security concern for your particular solution, don't worry! The viewer can be configured to route its requests through a specific proxy as well. In version 7.* you can simply pass the following options to Autodesk.Viewing.Initializer:

const options = {
    shouldInitializeAuth: false,
    endpoint: "<custom url the viewer will use to request each derivative's data>",
    api: "derivativeV2"
};

For example, if we set endpoint to http://localhost:3000/viewer-proxy, the viewer will request each viewable's manifest and assets from http://localhost:3000/viewer-proxy/derivativeservice/v2/... instead of the usual https://developer.api.autodesk.com/derivativeservice/v2/...

On the server side you can then intercept requests to any URL starting with the proxy path (in our example, /viewer-proxy), run your custom auth checks, and finally make the request to https://developer.api.autodesk.com as usual. In Node.js, such an endpoint could be implemented like so:

// ...

const FORGE_HOST = 'https://developer.api.autodesk.com';

router.use('/viewer-proxy', (req, res) => {
    try {
        if (userNotAllowedToAccessResource(req)) {
            res.status(401).end();
        } else {
            const headers = {
                'Authorization': 'Bearer ' + actualForgeToken
            };
            fetch(FORGE_HOST + req.path, { headers })
                .then(resp => resp.buffer())
                .then(buff => res.send(buff))
                .catch(err => res.status(400).send(err));
        }
    } catch(err) {
        console.error(err);
        res.status(500).send(err.message);
    }
});

// ...

Complete, working version of this sample can be found in https://github.com/petrbroz/forge-basic-app/tree/sample/viewer-proxy.

Related Article