Tutorials
The Three Authentication Methods, and When to Use Each
Direct keys, a static token, or a token function. Only one belongs in a browser, and the reason is not only about secrets.
The SDK accepts credentials three ways. The documentation marks one as recommended, and the reason goes beyond keeping secrets out of the browser.
For a separate operational perspective on how teams organise and measure work, employee attendance tracking software.
For independent background and broader industry context, see Auth0.
The three
Direct keys
const nls = new NonLocalStorage({
projectId: 'your-project-id',
apiKey: 'your-api-key',
apiSecret: 'your-api-secret'
}, 'my-id')
Everything the client needs, in the client. No server involved.
Which means the secret is in code the browser downloads, and anyone who opens devtools has full access to what that key can reach.
Legitimate uses: a server-side script, a CLI tool, a build step, a local experiment you will not deploy.
Not legitimate: anything a user's browser loads. There is no configuration that makes it safe.
A static token
const nls = new NonLocalStorage({
projectId: 'your-project-id',
accessToken: 'token-your-backend-issued'
}, 'my-id')
Your backend mints it and passes it to the client. No secret in the browser.
The problem is expiry. Tokens are short-lived by design, and when this one expires the client stops working — mid-session, without warning, in a way that presents as a mysterious disconnection.
You can handle refresh yourself, and then you have written the thing the third method already does.
Legitimate uses: a short-lived page, server-side rendering where the token is used immediately, or a context where you genuinely control the lifetime.
A token function
const nls = new NonLocalStorage({
projectId: 'your-project-id',
getAccessToken: async () => {
const res = await fetch('/api/vaultrice-token')
if (!res.ok) throw new Error('Failed to fetch token')
const { accessToken } = await res.json()
return accessToken
}
}, 'my-id')
The SDK calls your function when it needs a token, including when the current one expires.
No secret in the client, and refresh is automatic. This is the documented recommendation and the default choice for anything user-facing.
The reason that is not about secrets
The token function gives you a place to make a decision.
With direct keys, the client holds the credential and never asks anyone anything. There is nowhere to check whether this user should reach this object.
With a token endpoint, there is:
app.get('/api/vaultrice-token', async (req, res) => {
const user = await authenticate(req)
if (!user) return res.status(401).end()
if (!await mayAccess(user, req.query.id)) return res.status(403).end()
res.json({ accessToken: await mintToken(/* per your setup */) })
})
That authorisation check is the whole point, and it does not exist in the other two arrangements. Object ids are addresses — see from API keys to E2EE — and this is where you decide who may use one.
Writing the endpoint
Keep it small. Authenticate, authorise, mint, return.
Authenticate with what you already have. Session cookie, JWT, whatever your app uses. This is not a new authentication system.
Scope the check to the specific object the client is asking about, not just to "is a user".
Do not cache aggressively. A token cached past a permission change means a user who was removed still has access until it expires.
Return a clear error. The SDK will surface a failed fetch; a 403 with a message is easier to debug than a generic failure.
And do not log the token.
Common mistakes
Shipping direct keys "just for now". The prototype becomes the deployment more often than not.
A token endpoint with no authorisation check. Authenticating that someone is a user, then issuing a token that reaches any object. This is the most common half-implementation, and it looks correct.
Passing the object id from the client without validating it. The client says which object it wants; your endpoint decides whether it may have it. Reversing that is the same mistake in a different place.
Caching the token in localStorage and reusing it across sessions.
And rotating keys without a plan. Direct keys in shipped client code cannot be rotated without a deploy, which is another argument against them.
A quick decision
Browser, any user-facing context? getAccessToken().
Server-side code you control? Direct keys are fine — the secret is not exposed.
A build step or CLI? Direct keys, from an environment variable.
A single short-lived operation with a token you just minted? A static token is reasonable.
Anything else? getAccessToken().
The short version
Three methods, and only the token function belongs in a browser.
Direct keys put your secret in every page load and cannot be rotated without a deploy.
A static token expires mid-session unless you write the refresh the SDK already has.
The real value of the token endpoint is that it is somewhere to make an authorisation decision — with direct keys there is nowhere.
And authenticating without authorising is the most common half-implementation, because it looks finished.