Skip to main content

Backend Configuration

Node Setup

First, initialize LunaDefend's NodeSDK. We recommend doing this once and exporting it from a module, since you might want to use the SDK from multiple places.

Install the module:

yarn add @lunasec/node-sdk

Configuration

Here's an example configuration, below we will explain what the values mean:

import { LunaSec } from '@lunasec/node-sdk'
export const lunaSec = new LunaSec({
tokenizerURL: process.env.TOKENIZER_URL, // This is the domain of the Tokenizer Backend
auth: {
secrets: { source: 'environment' }, // Reads base64 encoded RSA key from LUNASEC_SIGNING_KEY
// Provide a small middleware that knows how to read the req object and return a promise containing a session id
// or null if a user is not logged in. LunaSec uses this to automatically create and verify token grants
// and to bootstrap a session if you are using the express-auth-plugin
sessionIdProvider: lunaSecSessionIdProvider,
},
});

Session ID Provider

The session ID provider callback might look like this:

export function lunaSecSessionIdProvider(req: Request): Promise<string | null> {
// LunaSec expects this to return a promise in case we need to do something async
return new Promise((resolve) => {
if (req.session.id) {
return resolve(req.session.id);
}
return resolve(null); // Tells LunaSec there is no session. LunaSec Elements will not work in this case
});
}

Secret Signing Key

When we set up the LunaDefend Node SDK, we pass a private key that LunaDefend uses to establish trust with your app.

If the environment secret provider is used (as shown above), the LUNASEC_SIGNING_KEY environment variable must be set with a valid RSA key, base64 encoded.

Here is an example of how you would generate a secret key to use for this on arch linux:

ssh-keygen -t rsa -f lunasec_signing_key
cat lunasec_signing_key | base64 -w0

LunaDefend also supports passing a secret key directly or reading one from AWS Secrets manager. For instructions, see the secret provider page of the deployment documentation.

tip

To see full documentation of the configuration, see the config's typedoc. To see commented typedoc for everything this class exposes, see the typedoc for the class.

Now the SDK is configured and you have access to its modules, like the auth plugin:

Registering the auth plugin with express

If you want to use your own session management or use a tool like Passport, register the auth plugin with express. This will transfer the session information onto the domain that the Dedicated Tokenizer will run on.

// Attach the LunaSec authentication plugin
lunaSec.expressAuthPlugin.register(app);
info

See the session page for more information on when to use this plugin and how it works.

Checking Grants

Grants connect a user's session to a token for a short time. Grants limit what sessions on the front-end are allowed to read a specific token.
Your server must grant every token being sent to the browser and check every token that is coming in.

note

Grants are not tokenization / detokenization. They are just granting permission for the front-end to do so.

If you're using GraphQL instead of Express, this can be done automatically with the graphql plugin.

Let's say we have a LunaDefend Token representing a Social Security Number:

app.get('/get-ssn', async (req, res) => {
const ssnToken = req.user.ssn_token

await lunaSec.grants.create(req.session.id, ssnToken, '15m'); // Make a grant, optionally overriding the default expiration time

res.json({
success: true,
ssnToken,
});
});

Grants also ensure that tokens sent up to the server are "granted" to the user and can be safely stored in the database. Otherwise, if an attacker got tokens that weren't theirs, they could upload them using the below route and then fetch them using the /get-ssn route above and read them. Don't forget to check grants for uploaded tokens!

This is how we verify the user has permission to store the grant back into the database. When new tokens are created by the frontend, a grant will have been created automatically.

app.post('/set-ssn', async (req, res) => {
await lunaSec.grants.verify(req.session.id, req.body.ssn_token); // Checks this user has a grant for the token.
await models.user.setSsn(req.user.id, req.body.ssn_token); // Stores the ssn_token in the database

return res.json({
success: true,
});
});
Warning

Do not forget to verify incoming tokens. It is critical to have this on every incoming token in order to keep your data secure, as a single unchecked token could create an "oracle" for an attacker to read any token they can intercept. There will be no warning or error if you forget.

Other systems of permission management without this concern are currently in development.

Error Handling

Both of the methods grants.create() and grants.verify() can and will throw a LunaSecError, so be sure to catch and handle errors appropriately.

app.get('/get-ssn', async (req, res) => {
try {
await lunaSec.grants.create(req.session.id, req.body.ssn_token); // Make a grant

res.json({
success: true,
ssnToken,
});
} catch (e) {
res.status(400).json({
success: false,
error: e
})

}
});

Since you will be using these methods often, you probably want to simply let them throw(or call next(e)) and catch in your global error handler:

app.use((err, req, res, next) => {
// LunaSec libraries should always throw an instance of LunaSecError in normal operation
// so you can easily check if the error was created by LunaSec
if (err instanceof LunaSecError){
res.status(err.code).json({
success: false,
error: err
})
}
// ...handle other errors...
})

That's it for the backend, let's move onto the frontend.