---
title: Authentication
description: Secure your client-side API requests with application signing keys
---

# User API Authentication

The User API authenticates requests using your project's API key and user identification.

## Security Modes

Notiflows supports two security modes for user authentication:

### Secure Mode (Recommended)

When **Security Mode** is enabled (default), requests require:

- `x-notiflows-api-key`: Your project's public API key
- `x-notiflows-user-key`: A JWT token signed with your Application Signing Key (RS256)

How it works:
- Your backend signs JWTs with the **private key**
- Notiflows verifies JWTs with the **public key** (stored securely in your project)
- The private key never leaves your infrastructure

### Development Mode

When Security Mode is disabled, requests require:

- `x-notiflows-api-key`: Your project's public API key
- `x-notiflows-user-id`: The user's external ID (no signing required)

This mode is useful for local development and testing, as it doesn't require JWT signing.

<Callout type="warn">
Development mode is NOT recommended for production. Anyone with your API key could impersonate any user. Always enable Security Mode and use Application Signing Keys for production deployments.
</Callout>

## Setting Up Application Signing Keys

### 1. Generate a Signing Key

Navigate to your project settings in the Notiflows dashboard:

1. Go to **Projects** → **Settings** → **API Keys**
2. Find the **Client Authentication** section
3. Click **Generate signing key**
4. **Save the private key securely** - it will only be shown once

The private key is provided in two formats:
- **Base64-encoded PEM**: Single-line format, ideal for environment variables
- **Raw PEM**: Standard PEM format for file storage

### 2. Enable Security Mode

Security Mode is enabled by default. If disabled, enable it in the same **Client Authentication** section by toggling the **Security mode** switch.

## Generating User Tokens

Your backend must generate a JWT for each user that needs to access the User API. The JWT should contain:

```json
{
  "sub": "user_external_id",  // Required: The user's external ID in your system
  "iat": 1608600116,          // Recommended: Issued at timestamp
  "exp": 1608603716           // Recommended: Expiry timestamp
}
```

### Token Claims

| Claim | Required | Description |
|-------|----------|-------------|
| `sub` | Yes | The user's external ID (must match the user registered in Notiflows) |
| `iat` | Recommended | Unix timestamp when the token was issued |
| `exp` | Recommended | Unix timestamp when the token expires |
| `iss` | Optional | Issuer identifier |

<Callout type="info">
The `sub` claim must match the `externalId` of a user created in your Notiflows project via the Admin API.
</Callout>

## Code Examples

### Node.js

```javascript
import jwt from 'jsonwebtoken';

// Load your private key (from environment variable or file)
const privateKey = Buffer.from(process.env.NOTIFLOWS_SIGNING_KEY, 'base64').toString();

function generateUserToken(userExternalId) {
  const payload = {
    sub: userExternalId,
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 hour expiry
  };

  return jwt.sign(payload, privateKey, { algorithm: 'RS256' });
}

// Generate a token for a user
const userToken = generateUserToken('user_123');
```

### Python

```python
import jwt
import time
import base64
import os

# Load your private key (from environment variable)
private_key_b64 = os.environ.get('NOTIFLOWS_SIGNING_KEY')
private_key = base64.b64decode(private_key_b64).decode('utf-8')

def generate_user_token(user_external_id: str) -> str:
    payload = {
        'sub': user_external_id,
        'iat': int(time.time()),
        'exp': int(time.time()) + 3600,  # 1 hour expiry
    }
    return jwt.encode(payload, private_key, algorithm='RS256')

# Generate a token for a user
user_token = generate_user_token('user_123')
```

### Ruby

```ruby
require 'jwt'
require 'base64'

# Load your private key (from environment variable)
private_key_pem = Base64.decode64(ENV['NOTIFLOWS_SIGNING_KEY'])
private_key = OpenSSL::PKey::RSA.new(private_key_pem)

def generate_user_token(user_external_id)
  payload = {
    sub: user_external_id,
    iat: Time.now.to_i,
    exp: Time.now.to_i + 3600 # 1 hour expiry
  }
  JWT.encode(payload, private_key, 'RS256')
end

# Generate a token for a user
user_token = generate_user_token('user_123')
```

### Elixir

```elixir
defmodule MyApp.Notiflows do
  def generate_user_token(user_external_id) do
    private_key_b64 = System.get_env("NOTIFLOWS_SIGNING_KEY")
    private_key_pem = Base.decode64!(private_key_b64)

    payload = %{
      "sub" => user_external_id,
      "iat" => :os.system_time(:second),
      "exp" => :os.system_time(:second) + 3600
    }

    jwk = JOSE.JWK.from_pem(private_key_pem)
    {_, token} = JOSE.JWT.sign(jwk, %{"alg" => "RS256"}, payload) |> JOSE.JWS.compact()
    token
  end
end

# Generate a token for a user
user_token = MyApp.Notiflows.generate_user_token("user_123")
```

## Making API Requests

### Secure Mode (Production)

Include the signed JWT in the headers:

```bash
curl -X GET "https://api.notiflows.com/user/v1/channels/{channel_id}/feed/entries" \
  -H "x-notiflows-api-key: pk_your_api_key" \
  -H "x-notiflows-user-key: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
```

### Development Mode

Pass the user ID directly in the header:

```bash
curl -X GET "https://api.notiflows.com/user/v1/channels/{channel_id}/feed/entries" \
  -H "x-notiflows-api-key: pk_your_api_key" \
  -H "x-notiflows-user-id: user_123"
```

### Client-Side SDK Example

```javascript
import { NotiflowsClient } from '@notiflows/client';

// Secure mode: Initialize with user token from your backend
const notiflows = new NotiflowsClient({
  apiKey: 'pk_your_api_key',
  userToken: userTokenFromBackend,
});

// Development mode: Initialize with user ID directly (NOT for production)
const notiflowsDev = new NotiflowsClient({
  apiKey: 'pk_your_api_key',
  userId: 'user_123',
});

// Fetch the user's notification feed
const entries = await notiflows.feed.list();
```

## Allowed Origins

Browser requests to the User API are subject to CORS restrictions. You must configure your domain as an allowed origin in your project settings, or the browser will block responses.

See [Allowed Origins](/docs/learn/security/allowed-origins) for setup instructions and troubleshooting.

## Best Practices

### Token Expiration

Always set a reasonable expiration time on tokens:

- **Short-lived tokens** (1 hour or less) are more secure
- Implement token refresh in your frontend when tokens expire
- Never use tokens without expiration in production

### Private Key Security

- Store the private key securely (environment variables, secrets manager)
- Never expose the private key in client-side code
- Never commit the private key to version control
- Rotate keys periodically or if compromised

### Key Rotation

To rotate your signing key:

1. Generate a new signing key in the dashboard
2. Update your backend with the new private key
3. Deploy the changes
4. Existing tokens will be invalidated immediately

<Callout type="warn">
Regenerating a signing key invalidates all existing user tokens. Users will need to re-authenticate.
</Callout>

## Troubleshooting

### Common Errors

**401 Unauthorized - Invalid user key signature**
- Verify you're using the correct private key
- Ensure Security Mode is enabled if using `x-notiflows-user-key`
- Check the token hasn't expired

**401 Unauthorized - Signing key not configured**
- Generate a signing key in your project settings
- Security Mode is enabled but no signing key has been generated

**401 Unauthorized - Missing x-notiflows-user-key header**
- Security Mode is enabled but you're not sending the JWT
- Either provide a signed JWT or disable Security Mode for development

**401 Unauthorized - Missing x-notiflows-user-id header**
- Security Mode is disabled but you're not sending the user ID header
- Provide `x-notiflows-user-id` header with the user's external ID

**401 Unauthorized - User not found**
- The user ID (from JWT `sub` claim or `x-notiflows-user-id` header) doesn't match any user in your project
- Create the user via the Admin API first
