> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grainql.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

> States which credential goes where, what the API checks, and what a page must not send.

Analytics Web has three authentication strategies. Each one fits one place: a public page, a
server, or a page with a signed-in user. This page lists the strategies, the headers and domains
that a site must allow, and the data that a page must not send.

## Authentication strategies

| Strategy | Where it runs | What the API checks | Protection |
| - | - | - | - |
| `NONE` | Public pages, demos, development | Nothing. Anyone who holds the tenant alias can send events and read configuration values. | Low |
| `SERVER_SIDE` | Node.js backends, serverless functions, server API routes | The secret key in `Authorization: Chase <secret>`. | High |
| `JWT` | Browser apps with a signed-in user | The token from `authProvider.getToken()` on every request. The user ID must match the token subject. | High |

Grain Tag sends events without a credential. It uses the tenant alias only.

Read [Authentication](/authentication) for the setup of each strategy.

<Warning>Do not use `NONE` with sensitive data. Anyone who holds the tenant alias can send
events and read every configuration value.</Warning>

## Secret keys

A secret key belongs on a server. A browser bundle is public, so a key in it is public.

```typescript theme={null}
const grain = createGrainAnalytics({
  tenantId: 'your-tenant-id',
  authStrategy: 'SERVER_SIDE',
  secretKey: process.env.GRAIN_SECRET_KEY
});
```

Rules for secrets:

1. Read the key from an environment variable. Never write it in source.
2. Never use `SERVER_SIDE` in browser JavaScript, in a mobile app, or in any code that ships to
   a client.
3. Rotate the key on a schedule.
4. Send every request over HTTPS.
5. Keep the SDK up to date.

## JWT and user IDs

With `JWT`, the API compares the user ID on each event with the `sub` claim of the token. An
event with a different user ID is rejected. This stops one user from sending events as another.

```typescript theme={null}
// The token holds { sub: "user_123" }
grain.setUserId('user_123');
grain.track('event');

grain.setUserId('user_456');
grain.track('event'); // rejected, the ID does not match the token subject
```

Return a fresh token from `getToken()` on each call. Do not cache the token in your own code.
The auth library refreshes it.

```typescript theme={null}
const authProvider = {
  async getToken() {
    return await auth0.getAccessTokenSilently();
  }
};
```

## User ID overrides

`setProperty(properties, { userId })` writes properties for a user other than the current one.
Grain can block a source that writes properties for many distinct user IDs in a short time, or
that sends an unusual pattern of property updates.

Rules for overrides:

1. Use `setUserId()` for the current user. Use the `userId` option only when the server acts
   for another user.
2. Make sure that the caller is allowed to change that user.
3. With `JWT`, the override must match the token subject.

## Sensitive data

Never send a password, a government ID number, a card number, or a health record as an event
property or a user property. Send a category, not the value.

```typescript theme={null}
grain.track('user_action', {
  action_type: 'profile_update',
  section: 'settings'
});
```

Send an email address or a location as a user property only when the user consented to it.
Read [Privacy and consent](/essentials/privacy-and-compliance).

## Allowed origins

The Grain API checks the `Origin` header of a browser request against the allowed origins of
the tenant. Set them in the dashboard under **Settings**, **Allowed Origins**.

| Setting | Value |
| - | - |
| Default | `*`, every origin |
| Production site | `https://yourapp.com`, `https://www.yourapp.com` |
| Local development | `http://localhost:3000` |

A request from an origin that is not on the list fails with a CORS error in the browser console.

## Content Security Policy

If the site sends a `Content-Security-Policy` header, allow these hosts.

| Directive | Host | Needed by |
| - | - | - |
| `connect-src` | `https://clientapis.grainql.com` | Grain Tag and Analytics Web. Every event and configuration request goes here. |
| `script-src` | `https://tag.grainql.com` | Grain Tag installed by script tag. |

```text theme={null}
Content-Security-Policy: connect-src 'self' https://clientapis.grainql.com; script-src 'self' https://tag.grainql.com
```

## Configuration values and XSS

A remote configuration value is a string that anyone with dashboard access can edit. Do not
insert it as HTML.

```typescript theme={null}
const heroText = grain.getConfig('hero_text');

element.textContent = heroText;
```

React escapes `{heroText}` in JSX. `element.innerHTML = heroText` does not escape it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.