Abrova
☰ Docs menu · Security best practices
Coming soon. Abrova Storage is coming soon. This page describes how it will work; it can’t be used yet.

Security Best Practices

How to keep Abrova Storage credentials and files secure — never expose the server secret, server-only buckets and rules, a signed Abrova user token on every file request, short-lived signed URLs, file type, size and executable checks, safe serving, environment variables, separate projects for development and production.

Last updated: 27 Oct 2026

Keep your files and credentials secure with these AbrovaStorage best practices.

Never Expose Server Secret#

Critical.

The serverSecret must never be included in client-side code, mobile apps, or frontend applications. Requests with it skip your access rules and can manage every bucket in the project.

Don't do this

javascript
// In React/Vue/Angular component
const storage = new AbrovaStorage({
  apiKey: 'ab_live_xxx',
  serverSecret: 'ab_srv_xxx', // NEVER!
});

Do this

javascript
// Call your backend API, which holds the server secret
const { uploadUrl } = await fetch(
  '/api/get-upload-url',
  { method: 'POST', body: JSON.stringify({ filename, contentType }) }
).then(r => r.json());

Buckets and Rules Are Server-Only#

The API key ships inside your app, so treat it as public. For every project:

  • creating, updating and deleting buckets, creating, updating and deleting access rules, applying a rule template, and making signed download and upload links need the server secret; the API key alone (with or without a user token) gets 403 with code server_secret_required;
  • uploading, reading, listing and deleting files (including deleteMany) and image transforms work with the API key plus a signed user token, and follow your access rules.

There is no setting to change this. Do bucket and rule work from your server (Node.js SDK or REST with the server secret) or in the Console.

Write Access Rules#

A bucket with no access rules lets any signed-in user of your app read, upload, delete and list every file in it. Add rules to every bucket an app can reach — see Bucket Policies. For a bucket only your server should touch, apply the closed template: apps are denied everything, and your server (server secret) still works.

Sign users in (one token for every Abrova product)#

Every file request from an app needs a signed Abrova user token, and rules that use {userId} or authenticated see the user in it. A plain user id from the app is never trusted.

Requires the Storage SDK 0.2.1 or newer.

Use Storage SDK 0.2.1 or newer in every app. An app that doesn't send a user token can't read or write files.

CredentialWhere it livesWhat it proves
API key (ab_live_…)Inside your app. Public.Which project the request belongs to.
Server secret (ab_srv_…)Your server only. Never in an app.That a request comes from your backend.
User tokenYour app, one per signed-in user.Which user is acting. This is the user access rules see.

Your backend mints the token with the server secret:

typescript
// Node.js SDK
const { token, expiresAt } = await storage.createUserToken(session.userId); // optional 2nd arg: lifetime in seconds
bash
# Any backend
curl -X POST https://auth.abrova.ir/users/token \
  -H "x-api-key: $ABROVA_API_KEY" \
  -H "x-server-secret: $ABROVA_SERVER_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"userId": "u_4821", "expiresIn": 3600}'

# → { "token": "eyJhbGciOi…", "userId": "u_4821", "expiresIn": 3600, "expiresAt": "…" }

It is the same token Abrova Sync, Chat, Push and A/B Testing accept; Abrova Auth's sign-in token is this same token too. The app hands it to the SDK through its token provider, and the SDK sends it as x-user-token and refreshes it when it expires — see the platform pages.

Keep it on the device.

A token is valid until it expires, so store it with the user's session (Keychain, Keystore or secure storage) and reuse it across app launches. Storage has no revocation, so keep the lifetime short if a signed-out user must lose access quickly.

Without a token (no one signed in), the SDKs don't send file requests: they wait until your app signs a user in (isAwaitingUserToken). A request without a token gets 401 token_required; a bad or expired token gets 401 token_invalid / token_expired.

Use Short-Lived Signed URLs#

Set appropriate expiration times for signed URLs based on your use case:

  • Upload URLs — 5 minutes by default, at most 15 minutes (a longer expiresIn is cut to 15 minutes).
  • Download URLs (normal) — 1 hour.
  • Download URLs (sensitive) — 5-15 minutes.
  • Maximum for download URLs — 24 hours.

Validate File Types and Sizes#

typescript
// Server-side validation before generating upload URL
app.post('/api/upload-url', async (req, res) => {
  const { filename, contentType, size } = req.body;

  // Validate file type
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
  if (!allowedTypes.includes(contentType)) {
    return res.status(400).json({ error: 'File type not allowed' });
  }

  // Validate file size (10MB max)
  if (size > 10 * 1024 * 1024) {
    return res.status(400).json({ error: 'File too large' });
  }

  // Validate filename (prevent path traversal)
  if (filename.includes('..') || filename.includes('/')) {
    return res.status(400).json({ error: 'Invalid filename' });
  }

  // Signed upload URL: the bucket's allowed types apply to contentType
  const { url } = await storage.createSignedUploadUrl(
    'user-uploads',
    `uploads/${Date.now()}-${sanitize(filename)}`,
    { contentType, expiresIn: 300 }
  );

  res.json({ uploadUrl: url });
});

A file uploaded through a signed link is registered when it is first read or listed. Abrova Storage then checks the bucket's size limit and allowed types, and your access rules, again against the real file; a file that fails is deleted. Checking type and size on your server first still gives your users a clear answer before they upload.

Let the bucket check too

  • Allowed types and size. Set them in Console › Storage › your bucket › Settings. The type is read from the file's content, not only from its name or declared type, and a refused upload gets 400 type_not_allowed or file_too_large.
  • Block executable files. Block executable files is on for new buckets; turn it on in bucket Settings for older buckets that take user uploads. Executable files (by name or content, also inside ZIP files) are then refused with 400 executable_blocked.

Serve Files Safely#

Files are served with X-Content-Type-Options: nosniff and a sandbox content security policy, so an uploaded HTML or SVG file can't run scripts on an Abrova address. From a public link, images (except SVG), video, audio, PDF and plain text open in the browser; every other type, including HTML and SVG, is sent as a download.

Environment Variables#

bash
# .env.local (never commit to git!)
ABROVA_STORAGE_API_KEY=ab_live_xxxxxxxxxxxx
ABROVA_STORAGE_SERVER_SECRET=ab_srv_xxxxxxxxxxxx

# Add to .gitignore
.env
.env.local
.env.*.local
Test vs live keys.

Use a separate project for development, so its data and usage stay apart from production. Each project has its own ab_live_xxx key.

Was this page helpful?
Report a problem