Sudo mode
Proving someone is still at the keyboard, without signing them out.
A session says who signed in. It does not say who is at the keyboard right now. Sudo mode is a short window where that has just been re-proven, and it exists so that a stolen or borrowed session cannot quietly change the things that protect an account.
Which actions require it
This is configuration, not something baked into the SDK. An environment names the groups it wants protected:
manage_mfaEnrolling, removing factors, reading recovery codes.manage_credentialsCredential changes.manage_grantsInviting, revoking, blocking, changing permissions.manage_identitiesAdding or removing an email or phone.manage_sessionsRevoking sessions.manage_passwordsChanging the password.Alongside them, in Security & Protection, Enable Sudo Mode is the master switch and Timeout sets how long an elevation lasts.
Because it is configuration, do not hard-code which of your calls need sudo. An environment can turn a group on next month and your code should already cope. The next section is how.
React to the event, do not predict it
The SDK emits sudo-required when a call is rejected for this reason, so one
handler covers every protected call in the app:
nulvless.auth.on('sudo-required', async () => {
await nulvless.auth.sessions.sudo(); // sends the code
openSudoPrompt(); // your UI collects it
});Underneath, that is a 403 with the code protected_action_denied. If you
handle errors directly rather than through events, match on that.
The two ways to satisfy it
Both start the same way — sessions.sudo() sends a code and returns a
challenge. What differs is what you spend the code on.
await nulvless.auth.sessions.sudo();
const { data } = await nulvless.auth.sessions.sudoVerify({ code });
// data.sudo.expires_in → seconds the window lastsThe session is re-issued with sudo active and stored automatically. Every protected call passes until the window closes.
Right when the user is about to do several sensitive things in a row — a security settings screen, for instance.
The nonce is the code
nonce is not a separate token you fetch. It is the code the user entered,
resolved against the sudo challenge already pending for their session. It is
single-use, it goes inside the params rather than the options, and it
travels as the nlv-sudo-nonce header.
Choosing the method
sudo() takes an optional method:
await nulvless.auth.sessions.sudo('email_otp');Accepted values are totp, email_otp and sms_otp. Omit it and the platform
picks, the same way it does for an MFA challenge.
Checking without doing anything
const { error } = await nulvless.auth.sessions.checkSudo();
if (error) startSudoFlow();Useful for deciding whether a settings screen should prompt on entry rather than letting the first destructive click fail.
On your own server
Your API can require the same freshness for routes the platform knows nothing about — deleting a workspace, rotating your own API key.
The browser obtains the code exactly as above and sends it on; your handler passes it to whichever call is protected:
app.delete('/keys/:id', requireAuth, async (req, res) => {
const { error } = await nulvless.auth.sessions.revoke({
id: req.params.id,
nonce: req.headers['nlv-sudo-nonce'] as string,
});
if (error) return res.status(403).json({ error });
res.status(204).end();
});What sudo is not
It is not a permission check. Sudo answers "is this person still here?", not "are they allowed to do this?" — a user with a freshly elevated session still fails an authorization check they do not hold the grant for. The two run independently, and a sensitive action usually wants both.