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

# Security

> Best practices for keeping your Monei Connect integration secure in production

Connect tokens give your app real access to real money. Take these seriously.

***

## Client secret rules

Your `client_secret` is the most sensitive credential in your Connect integration. Treat it like a private key.

**Never:**

* Include it in frontend JavaScript or HTML
* Put it in a mobile app binary
* Commit it to version control (`.env` files included)
* Log it or send it in error reports
* Expose it in API responses

**Always:**

* Store it as an environment variable
* Use it only in server-side token exchange
* Rotate it immediately if you suspect it was exposed

***

## State parameter (CSRF protection)

Always generate a fresh `state` value per authorization request and validate it on callback:

<CodeGroup>
  ```javascript Node.js theme={null}
  import crypto from 'crypto';

  // On redirect
  const state = crypto.randomBytes(16).toString('hex');
  req.session.oauthState = state; // store in server-side session

  // On callback, reject if it doesn't match
  if (req.query.state !== req.session.oauthState) {
    return res.status(400).send('Invalid state — possible CSRF attack');
  }
  ```

  ```python Python theme={null}
  import secrets

  # On redirect
  state = secrets.token_hex(16)
  session["oauth_state"] = state  # store in server-side session

  # On callback, reject if it doesn't match
  if request.args.get("state") != session.get("oauth_state"):
      return "Invalid state — possible CSRF attack", 400
  ```
</CodeGroup>

Without `state` validation, an attacker can trick a user's browser into completing an OAuth flow the attacker controls.

***

## Token storage

Store access and refresh tokens encrypted at rest. See the [Token Management](/connect/tokens) page for encryption helpers in Node.js and Python.

**Don't store tokens in:**

* Browser `localStorage` or `sessionStorage`
* Unencrypted database columns
* HTTP cookies without `HttpOnly` and `Secure` flags

***

## Handle 401 from user revocation

Users can revoke your app's access from their Monei settings at any time. When this happens, any API call with that token returns `401`. Your app must handle this gracefully:

<CodeGroup>
  ```javascript Node.js theme={null}
  async function callMoneiApi(userId, endpoint) {
    const token = await getValidAccessToken(userId);

    const res = await fetch(`https://api.monei.cc/api/v1${endpoint}`, {
      headers: { Authorization: `Bearer ${token}` },
    });

    if (res.status === 401) {
      // User revoked access. Clear tokens and prompt reconnect
      await db.users.update(userId, {
        moneiAccessToken:  null,
        moneiRefreshToken: null,
        moneiTokenExpiry:  null,
        moneiScopes:       [],
      });
      throw new Error('MONEI_ACCESS_REVOKED');
    }

    return res.json();
  }
  ```

  ```python Python theme={null}
  def call_monei_api(user_id: str, endpoint: str) -> dict:
      token = get_valid_access_token(user_id)

      res = requests.get(
          f"https://api.monei.cc/api/v1{endpoint}",
          headers={"Authorization": f"Bearer {token}"},
      )

      if res.status_code == 401:
          # User revoked access. Clear tokens and prompt reconnect
          db.users.update(user_id, {
              "monei_access_token":  None,
              "monei_refresh_token": None,
              "monei_token_expiry":  None,
              "monei_scopes":        [],
          })
          raise Exception("MONEI_ACCESS_REVOKED")

      return res.json()
  ```
</CodeGroup>

***

## Production checklist

Before going live, confirm every item:

<AccordionGroup>
  <Accordion title="Credentials" icon="key">
    * [ ] `client_secret` is stored as an environment variable, not in code
    * [ ] `client_secret` is not committed to version control
    * [ ] Separate credentials for dev and production environments
    * [ ] Redirect URIs registered for production domain (not just localhost)
  </Accordion>

  <Accordion title="OAuth flow" icon="arrows-rotate">
    * [ ] `state` parameter generated fresh per request using `crypto.randomBytes` or `secrets.token_hex`
    * [ ] `state` validated on every callback before processing the code
    * [ ] `error` query param handled on callback, user-friendly message shown
    * [ ] Token exchange happens server-side only
    * [ ] Authorization code exchanged immediately after callback (expires in 10 minutes)
  </Accordion>

  <Accordion title="Token handling" icon="lock">
    * [ ] Access tokens stored encrypted at rest
    * [ ] Refresh tokens stored encrypted at rest
    * [ ] Token expiry stored and checked before each API call
    * [ ] Auto-refresh implemented with 5-minute buffer before expiry
    * [ ] `401` from API handled by clearing tokens and prompting reconnect
    * [ ] Token revocation called when user disconnects from your platform
  </Accordion>

  <Accordion title="Scope handling" icon="shield-halved">
    * [ ] `scopes` field read from token response (not assumed from request)
    * [ ] Granted scopes stored per user
    * [ ] UI disables or hides features the user hasn't granted
    * [ ] 403 from API handled gracefully, not shown as a raw error
    * [ ] Re-authorization path built for upgrading individual scopes
  </Accordion>
</AccordionGroup>

***

<Card title="Errors & Rate Limits" icon="triangle-exclamation" href="/connect/errors">
  Full error reference and rate limit table
</Card>
