Creating a Smartsheet OAuth Flow in Node.js
Published on 23 January 2018
This article will cover OAuth at a conceptual level—following the flow of information through the authentication process. The snippets of code in this article are from Smartsheet’s Node OAuth Flow Sample. The sample is in Node, but the concepts carry over to other languages.
Accessing Smartsheet through the API requires an access token to be included in the Authorization header of each request. For standalone applications that only access your personal Smartsheet data, you can generate an access token in the Smartsheet UI. However, if your application needs to let multiple users login with their own account, then you will need to implement the full 3-legged OAuth flow.
Please refer to the diagram below for an overview of Smartsheet’s OAuth flow.
Register Third Party App with Smartsheet
To build an OAuth flow, you need to have a Smartsheet developer account and register your application with Smartsheet. In Smartsheet, select Account → Developer Tools → Create New App and fill out the details to confirm your app registration.
When registering your app, pay close attention to the app redirect URL: the specific address that we are giving Smartsheet permission to send the authorization code. The redirect URL will contain sensitive information and must be an address you as the developer has control over. In this sample, we’re using localhost:3000/callback, but your application will have its own redirect URL.
Requesting an Authorization Code
The OAuth flow starts when the application requests an authorization code from Smartsheet. That initial request contains values in the URL that communicate important information to Smartsheet. This information does things like specify the expected response (response type), verify the application making the request (client id), and define the level of access that the user should have to their Smartsheet data (scope). There is also an optional value called state that allows you to pass an arbitrary string with the request that gets returned with the response.
If you need to redirect users logging into your application, then using the state parameter is an effective way to apply business logic to your OAuth flow.
In the sample below, the authorization URL is assembled when the variable is used. Once the request is sent, The user is taken to Smartsheet’s consent page (first image in next section).
Here’s a sample authorization URL:
// helper function to assemble authorization url
function authorizeURL(params) {
const authURL = 'https://app.smartsheet.com/b/authorize';
return `${authURL}?${qs.stringify(params)}`;
}
const authorizationUrl = authorizeURL({
response_type: 'code',
client_id: config.APP_CLIENT_ID,
scope: config.ACCESS_SCOPE
});
// route redirecting to authorization page
app.get('/auth', (req, res) => {
console.log('Your authorization url: ', authorizationUrl);
res.redirect(authorizationUrl);
});
Processing the Auth Code and Requesting an Access Token
When the user clicks ‘Allow’, Smartsheet sends the authorization code to the redirect URL registered with the application. The secure, developer-facing redirect URL captures the authorization code for use in our OAuth flow.
In our sample, our callback endpoint receives the authCode, and then prepares to request an access token from Smartsheet. Compared to standard OAuth implementations, Smartsheet has an extra step at this point—creating an SHA-256 hash that is passed back with the access token request. The hash contains the app secret and authorization code separated by a pipe, “app_secret|auth_code”.
// callback service parses the authorization code, requests access token, and saves it
app.get('/callback', (req, res) => {
const authCode = req.query.code;
const generated_hash = require('crypto')
.createHash('sha256')
.update(config.APP_SECRET + "|" + authCode)
.digest('hex');
const options = {
queryParameters: {
client_id: config.APP_CLIENT_ID,
code: authCode,
hash: generated_hash
}
};
smartsheet.tokens.getAccessToken(options, processToken)
.then((token) => {
return res
.status(200)
.json(token);
});
});
Token Handling
Obtaining an access token is the goal—that’s what allows your user to access their Smartsheet data. Let’s assume your user’s request for an access token was successful and Smartsheet sent you this token in response:
{
"token": {
"access_token": "new52u9jujauoqz4gstvsae05",
"token_type": "bearer",
"refresh_token": "new352a9mp4151le2505",
"expires_in": 604799,
"expires_at": "2017-11-21T23:32:22.180Z"
}
}
The whole response should be saved. The token object contains the access token, but also the expiration and refresh token. Once the access token expires, the refresh token can be used to obtain a new access token without requiring the user to complete a full OAuth flow again. In production, your application should store the user’s access token in a secure location, like a database.
This sample app, running on localhost and not production, saves the token to a token_priv.json. The token storage solution used in the sample should not be used on a production application. The only reason it is present in this article is to demonstrate how to force an expired token. For reference, this is how the token is saved in token_priv.json:
{"ACCESS_TOKEN":"1go4c93wnpen6ayrkbyh03hjsg","EXPIRES_IN":1516321017058,"REFRESH_TOKEN":"4rqnzx4j88n4cxcorw8l7dz861"}
Simulating an expired token allows you to test your refresh token method. To force an expired token, type console.log(Date.now()) in the developer’s console, copy the output, and use it to replace the previous EXPIRES_IN value.
Refreshing an Expired Access Token
Every token generated through the OAuth flow will expire in seven days, and will need to be refreshed. By using the refresh token, Smartsheet can quickly issue a new access token in a single request, rather than an entire OAuth flow.
Refreshing an access token is similar to the initial access token request, but with a couple key differences: the refresh token is used in place of the authorization code and our request is sent to a different endpoint. Using the Smartsheet Node SDK, we called the smartsheet.tokens.refreshAccessToken().
// if current date is past expiration date...
if (Date.now() > old_token.EXPIRES_IN) {
const generated_hash = require('crypto')
.createHash('sha256')
.update(config.APP_SECRET + "|" + old_token.REFRESH_TOKEN)
.digest('hex');
const options = {
queryParameters: {
client_id: config.APP_CLIENT_ID,
refresh_token: old_token.REFRESH_TOKEN,
hash: generated_hash
}
};
smartsheet.tokens.refreshAccessToken(options, processToken)
.then((token) => {
return res
.status(200)
.json(token);
});
}
Build Your Own
The samples used in this article are from Smartsheet’s node oauth flow sample and while they’re helpful to understand concepts, this implementation of OAuth should not be used ‘as is’ on a production application. By walking through each step of the process, this article aims to give you a better understanding of Smartsheet’s Oauth flow.
In summary, to build a Smartsheet OAuth flow, the developer must create a Smartsheet developer account and register their application with Smartsheet. The first step of the 3-legged OAuth flow is requesting an authorization code using the client id and access scope. After Smartsheet responds with the authorization code, the second step is using the client id, auth code, and user-generated hash to request an access token. The third and final step is using the access token to call the Smartsheet API (outside the scope of this article).