Security Rules

Nhost Storage Security

#Rules

All files in Nhost Storage are protected with Security Rules.

With security rules, you can set granular access on who is allowed to do what based on the user and the file.

All storage rules can be by-passed by admins using the Admin Secret. This is done by sending the Admin Secret as a header like: x-admin-secret: <admin-secret>. The Admin Secret is the same Admin Secret used to login to the Hasura console.

Security rules are written in yaml format.

Here is an example security rules.

functions:
  isAuthenticated: "return !!request.auth"
  isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
  /public*:
    read: "true"
    write: "isAuthenticated()"
  /users/:userId/:fileName:
    read: "public()"
    write: "isOwner(userId)"

In the above example we have the following rules:

  • Anyone can read files located in the /public* folder. Because we use a * all paths that starts with /public will be covered by this rule. Ex: /public/image.png and /public/other-path/cv.pdf.
  • Only logged in users are allowed to write to /public*.
  • Anyone can read files from /users/:userId/:fileName. Ex: /users/1/image.png.
  • Only logged in users can write to their own directory. Ex user with id 1 can upload a file to /users/1/image.png but not to /users/2/image.png.

Structure

Storage Rules follows this structure:

functions:
  functionName: "one line javascript that returns true or false"
paths:
  /path/to/file.png:
    operation: "true/false or functionName()"
    operation: "true/false or functionName()"
    operation: "true/false or functionName()"
  /path/to/folder:
    operation: "true/false or functionName()"
    operation: "true/false or functionName()"
    operation: "true/false or functionName()"
  /path/to/folder*:
    operation: "true/false or functionName()"
    operation: "true/false or functionName()"
    operation: "true/false or functionName()"

Paths

Paths are specified under the paths: directive and specifies that path to the file or folder that you want to set rules for.

Path Variables

You can use path variables in the path to capture values specified at their position in the URL. These path variables can be used in the security rules functions.

Below is an example where we capture :userId as a variable in the URL and use that same userId variable to resolve userId === request.auth['user-id'] in the isOwner function.

functions:
  isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
  /users/:userId/:fileName:
    write: "isOwner(userId)"

You can not nest paths like this:

paths:
  /path/to/:
    read: "true/false or functionName()"
    /then/:
      read: "true/false or functionName()"

Functions

You can use a function to resolve if a request should be allowed or not. A function can access information from the user making the request, file metadata and path variables.

A function must return true or false.

Functions are placed after the functions: directive and can be used after an operation like this:

functions:
  public: "return true"
paths:
  /open-access*:
    read: "public()"
    write: "public()"

Functions can take arguments from the file path. In the example below we'll allow anyone to read a file located in /users/:userId but only users are allowed to write in their own folder with the name of their user id.

functions:
  public: "return true"
  isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
  /users/:userId/:fileName:
    read: "public()"
    write: "isOwner(userId)"

Who is allowed to do what given the rules above?

User idPathOperationAllowed?
1/users/1/image.pngread (GET)Yes
1/users/2/image.pngread (GET)Yes
1/users/1/image.pngwrite (POST)Yes
1/users/2/image.pngwrite (POST))No
Not logged in/users/2/image.pngread (GET))Yes
Not logged in/users/1/image.pngwrite (POST))No
Not logged in/users/2/image.pngwrite (POST))No

Available variables

Path variables

Path variables can be accessed in function if they are present in the path (/users/:myVariable) and sent as a parameter to the function isOwner(myVariable).

User

You can access user information at request.auth['']. All variables from the JWT-token claim can be accessed here without the x-hasura. For example, thi is how you access the user's id: request.auth['user-id'].

#Operations

The following operations can be used:

  • create Upload new file
  • update Overwrite existing file
  • get - Get file
  • list - List files in directory
  • delete - Delete file

You can also use helper operations:

  • write is the same as create and update and delete.
  • read is the same as get and list.

#Access Token

When a file gets uploaded, Nhost Storage automatically generates a token (access token) for the file. The token will be added to the file's metadata.

Let's say you upload a file to /data/document.txt and get this as return data:

{
  "key": "/data/document.txt",
  "AcceptRanges": "bytes",
  "LastModified": "2020-01-01T01:02:03.000Z",
  "ContentLength": 12345,
  "ETag": "Etag",
  "ContentType": "text/plain",
  "Metadata": {
    "token": "6a587e56-04d4-415d-bb2c-9a39491e31d2"
  }
}

And have the following storage rules:

functions:
  validToken: 'return request.query.token === resource.Metadata.token'`
paths:
  /data*:
    read: 'validToken()'

Now, given our validToken function, anyone can access /data/document.txt with the token like this: https://backend-xxx.nhost.app/storage/o/data/document.txt?token=6a587e56-04d4-415d-bb2c-9a39491e31d2.

Revoke access tokens

Access tokens can be revoked which means the old access token gets deleted and a new access token created. Only admins with the admin secret (same as Hasura's admin secret) can revoke tokens.

Request to revoke an access token:

Request

POST /storage/m/<path-to-file>

Headers

x-admin-secret: <admin-secret>

Body

{
  "action": "revoke-token"
}

Response

{
  "key": "<path-to-file>",
  "AcceptRanges": "bytes",
  "LastModified": "2020-01-01T01:02:03.000Z",
  "ContentLength": 12345,
  "ETag": "Etag",
  "ContentType": "<content-type>",
  "Metadata": {
    "token": "<new-auto-generated-access-token-uuid>"
  }
}